From de14d3aa3cd2845b621faf32b599766a66e158cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jul 2023 16:51:50 +0000 Subject: [PATCH 001/373] Bump external/libbtf from `e532c3f` to `1b467a9` Bumps [external/libbtf](https://github.com/Alan-Jowett/libbtf) from `e532c3f` to `1b467a9`. - [Release notes](https://github.com/Alan-Jowett/libbtf/releases) - [Commits](https://github.com/Alan-Jowett/libbtf/compare/e532c3f53bdc4a99d506052abdedafdf14e7ebcd...1b467a9e22f9b812abb0593a2d20458fe05e7951) --- updated-dependencies: - dependency-name: external/libbtf dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/libbtf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/libbtf b/external/libbtf index e532c3f53..1b467a9e2 160000 --- a/external/libbtf +++ b/external/libbtf @@ -1 +1 @@ -Subproject commit e532c3f53bdc4a99d506052abdedafdf14e7ebcd +Subproject commit 1b467a9e22f9b812abb0593a2d20458fe05e7951 From 63bf8e13494216be1ecb4f081aeffc961e350bcf Mon Sep 17 00:00:00 2001 From: Alan Jowett Date: Thu, 3 Aug 2023 12:36:35 -0700 Subject: [PATCH 002/373] Add test case to verify ebpf-samples/build/prog_array.o Signed-off-by: Alan Jowett --- ebpf-samples | 2 +- external/libbtf | 2 +- src/test/test_verify.cpp | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ebpf-samples b/ebpf-samples index ac3262d7f..e6486118e 160000 --- a/ebpf-samples +++ b/ebpf-samples @@ -1 +1 @@ -Subproject commit ac3262d7f3e02e5fa377a7e9984965cb57d8ae7c +Subproject commit e6486118e4997a9780a30fd4bfeedcefd1567c8b diff --git a/external/libbtf b/external/libbtf index 1b467a9e2..f5fab5a0b 160000 --- a/external/libbtf +++ b/external/libbtf @@ -1 +1 @@ -Subproject commit 1b467a9e22f9b812abb0593a2d20458fe05e7951 +Subproject commit f5fab5a0b36869c104fa45a6ffb6c87295a22888 diff --git a/src/test/test_verify.cpp b/src/test/test_verify.cpp index 8fb5f1c72..450a2acbb 100644 --- a/src/test/test_verify.cpp +++ b/src/test/test_verify.cpp @@ -469,6 +469,7 @@ TEST_SECTION("build", "map_in_map_legacy.o", ".text") TEST_SECTION("build", "twomaps.o", ".text"); TEST_SECTION("build", "twostackvars.o", ".text"); TEST_SECTION("build", "twotypes.o", ".text"); +TEST_SECTION("build", "prog_array.o", ".text"); // Test some programs that ought to fail verification. TEST_SECTION_REJECT("build", "badhelpercall.o", ".text") From d51166fad872644860807e2d24bc67d77e6839c6 Mon Sep 17 00:00:00 2001 From: Alan Jowett Date: Thu, 3 Aug 2023 15:09:32 -0700 Subject: [PATCH 003/373] Update ebpf-samples Signed-off-by: Alan Jowett --- ebpf-samples | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebpf-samples b/ebpf-samples index e6486118e..e052151f8 160000 --- a/ebpf-samples +++ b/ebpf-samples @@ -1 +1 @@ -Subproject commit e6486118e4997a9780a30fd4bfeedcefd1567c8b +Subproject commit e052151f8cf3b3d65bfaade6255318a566457fbb From fe238de2a0dbd9548c2b8ae6959822ce8c47fdd8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Sep 2023 16:54:28 +0000 Subject: [PATCH 004/373] Bump external/ELFIO from `8ae6cec` to `73a2410` Bumps [external/ELFIO](https://github.com/serge1/ELFIO) from `8ae6cec` to `73a2410`. - [Release notes](https://github.com/serge1/ELFIO/releases) - [Commits](https://github.com/serge1/ELFIO/compare/8ae6cec5d60495822ecd57d736f66149da9b1830...73a241079a040710481e9b840248ec0425e46c7c) --- updated-dependencies: - dependency-name: external/ELFIO dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/ELFIO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/ELFIO b/external/ELFIO index 8ae6cec5d..73a241079 160000 --- a/external/ELFIO +++ b/external/ELFIO @@ -1 +1 @@ -Subproject commit 8ae6cec5d60495822ecd57d736f66149da9b1830 +Subproject commit 73a241079a040710481e9b840248ec0425e46c7c From 3300cea59b977682e2e51bedf493a85120dad4bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Sep 2023 16:17:37 +0000 Subject: [PATCH 005/373] Bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 4 ++-- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/coverage.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6d16a8ebe..b4b3f8e6c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ jobs: run: | sudo apt install libboost-dev libboost-filesystem-dev libboost-program-options-dev libyaml-cpp-dev valgrind - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: 'recursive' @@ -57,7 +57,7 @@ jobs: BUILD_CONFIGURATION: ${{matrix.configurations}} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: 'recursive' diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index bf4de1354..f33915f9b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,7 +35,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: 'recursive' diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index a715139f5..5874a3ae1 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -26,7 +26,7 @@ jobs: sudo apt install libboost-dev libboost-filesystem-dev libboost-program-options-dev libyaml-cpp-dev lcov pip install gcovr - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: 'recursive' From fd85b8a6ac2c0cb5621d9c1f38368089d616b8b9 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Sat, 23 Sep 2023 19:23:51 +0300 Subject: [PATCH 006/373] Address linter / compiler warnings (#519) Signed-off-by: Elazar Gershuni --- CMakeLists.txt | 4 +-- src/asm_files.cpp | 14 ++++------ src/asm_unmarshal.cpp | 14 ---------- src/assertions.cpp | 1 - src/config.cpp | 1 - src/crab/array_domain.cpp | 1 - src/crab/ebpf_domain.cpp | 48 ++++++++++++-------------------- src/crab/ebpf_domain.hpp | 49 +++++++++++++++++---------------- src/crab_utils/adapt_sgraph.hpp | 17 ++++++------ src/ebpf_vm_isa.hpp | 6 +--- src/spec_type_descriptors.hpp | 43 +++++++++++++++-------------- src/string_constraints.hpp | 2 +- 12 files changed, 83 insertions(+), 117 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d7cf25631..2f1c82543 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") if (NOT NUGET) message("ERROR: You must first install nuget.exe from https://www.nuget.org/downloads") else () - exec_program(${NUGET} ARGS install "Boost" -Version 1.81.0 -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages) + execute_process(COMMAND ${NUGET} ARGS install "Boost" -Version 1.81.0 -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages) set(BOOST_VERSION 1.81.0) endif() set(Boost_INCLUDE_DIRS ${CMAKE_BINARY_DIR}/packages/boost/lib/native/include) @@ -93,7 +93,7 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(COMMON_FLAGS -Wall -Wfatal-errors -DSIZEOF_VOID_P=8 -DSIZEOF_LONG=8) - set(RELEASE_FLAGS -O2 -flto -ffat-lto-objects) + set(RELEASE_FLAGS -O2 -flto=auto -ffat-lto-objects) set(DEBUG_FLAGS -O0 -g3 -fno-omit-frame-pointer) diff --git a/src/asm_files.cpp b/src/asm_files.cpp index 9bae2c1e7..b05928991 100644 --- a/src/asm_files.cpp +++ b/src/asm_files.cpp @@ -1,23 +1,21 @@ // Copyright (c) Prevail Verifier contributors. // SPDX-License-Identifier: MIT -#include #include #include +#include #include #include -#include -#include #include -#include "asm_files.hpp" #include "libbtf/btf.h" #include "libbtf/btf_json.h" #include "libbtf/btf_map.h" #include "libbtf/btf_parse.h" -#include "platform.hpp" - #include "elfio/elfio.hpp" +#include "asm_files.hpp" +#include "platform.hpp" + using std::cout; using std::string; using std::vector; @@ -183,7 +181,7 @@ vector read_elf(std::istream& input_stream, const std::string& path .key_size = map.key_size, .value_size = map.value_size, .max_entries = map.max_entries, - .inner_map_fd = map.inner_map_type_id != 0 ? map.inner_map_type_id : -1, + .inner_map_fd = map.inner_map_type_id != 0 ? map.inner_map_type_id : DEFAULT_MAP_FD, }); } map_record_size_or_map_offsets = map_offsets; @@ -203,7 +201,7 @@ vector read_elf(std::istream& input_stream, const std::string& path // Replace the typeids with the pseudo-fds. for (auto &map_descriptor : info.map_descriptors) { map_descriptor.original_fd = type_id_to_fd_map[map_descriptor.original_fd]; - if (map_descriptor.inner_map_fd != -1) { + if (map_descriptor.inner_map_fd != DEFAULT_MAP_FD) { map_descriptor.inner_map_fd = type_id_to_fd_map[map_descriptor.inner_map_fd]; } } diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index 3536136f1..4a9609d46 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -1,7 +1,6 @@ // Copyright (c) Prevail Verifier contributors. // SPDX-License-Identifier: MIT #include -#include // memcmp #include #include #include @@ -429,19 +428,6 @@ struct Unmarshaller { break; } } - /* - vector marshalled = marshal(new_ins[0], pc); - ebpf_inst actual = marshalled[0]; - if (std::memcmp(&actual, &inst, sizeof(inst))) { - std::cerr << "new: " << new_ins[0] << "\n"; - compare("opcode", actual.opcode, inst.opcode); - compare("dst", actual.dst, inst.dst); - compare("src", actual.src, inst.src); - compare("offset", actual.offset, inst.offset); - compare("imm", actual.imm, inst.imm); - std::cerr << "\n"; - } - */ if (pc == insts.size() - 1 && fallthrough) note("fallthrough in last instruction"); diff --git a/src/assertions.cpp b/src/assertions.cpp index 7a74218bb..e3c55ae9d 100644 --- a/src/assertions.cpp +++ b/src/assertions.cpp @@ -6,7 +6,6 @@ #include #include "asm_syntax.hpp" -#include "ebpf_vm_isa.hpp" #include "platform.hpp" #include "crab/cfg.hpp" diff --git a/src/config.cpp b/src/config.cpp index 342e4ebd3..3849162c5 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1,7 +1,6 @@ // Copyright (c) Prevail Verifier contributors. // SPDX-License-Identifier: MIT #include "config.hpp" -#include "platform.hpp" const ebpf_verifier_options_t ebpf_verifier_default_options = { .check_termination = false, diff --git a/src/crab/array_domain.cpp b/src/crab/array_domain.cpp index 31a56668f..ec5be435d 100644 --- a/src/crab/array_domain.cpp +++ b/src/crab/array_domain.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 #include -#include #include #include #include diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index 157822d37..d49d23977 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -4,7 +4,6 @@ // This file is eBPF-specific, not derived from CRAB. #include -#include #include #include #include @@ -12,8 +11,6 @@ #include "boost/endian/conversion.hpp" #include "boost/range/algorithm/set_algorithm.hpp" -#include "crab_utils/stats.hpp" - #include "crab/array_domain.hpp" #include "crab/ebpf_domain.hpp" @@ -289,10 +286,6 @@ assume_signed_64bit_lt(const NumAbsDomain& inv, bool strict, variable_t left_sva using crab::interval_t; using namespace crab::dsl_syntax; - auto rlb = right_interval.lb(); - auto lnub = left_interval_negative.truncate_to_sint(true).ub(); - auto lnlb = left_interval_negative.truncate_to_sint(true).lb(); - if (right_interval <= interval_t::negative_int(true)) { // Interval can be represented as both an svalue and a uvalue since it fits in [INT_MIN, -1]. return {strict ? (left_svalue < right_svalue) : (left_svalue <= right_svalue), number_t{0} <= left_uvalue, @@ -316,9 +309,6 @@ assume_signed_32bit_lt(const NumAbsDomain& inv, bool strict, variable_t left_sva using crab::interval_t; using namespace crab::dsl_syntax; - auto rlb = right_interval.lb(); - auto lpub = left_interval_positive.truncate_to_sint(false).ub(); - if (right_interval <= interval_t::negative_int(false)) { // Interval can be represented as both an svalue and a uvalue since it fits in [INT_MIN, -1], // aka [INT_MAX+1, UINT_MAX]. @@ -327,7 +317,8 @@ assume_signed_32bit_lt(const NumAbsDomain& inv, bool strict, variable_t left_sva strict ? (left_svalue < right_svalue) : (left_svalue <= right_svalue)}; } else if ((left_interval_negative | left_interval_positive) <= interval_t::nonnegative_int(false) && right_interval <= interval_t::nonnegative_int(false)) { - // Interval can be represented as both an svalue and a uvalue since it fits in [0, INT_MAX]. + // Interval can be represented as both an svalue and a uvalue since it fits in [0, INT_MAX] + auto lpub = left_interval_positive.truncate_to_sint(false).ub(); return {left_svalue >= 0, strict ? left_svalue < right_svalue : left_svalue <= right_svalue, left_svalue <= left_uvalue, @@ -384,9 +375,6 @@ assume_signed_32bit_gt(const NumAbsDomain& inv, bool strict, variable_t left_sva using crab::interval_t; using namespace crab::dsl_syntax; - auto rlb = right_interval.lb(); - auto lpub = left_interval_positive.truncate_to_sint(false).ub(); - if (right_interval <= interval_t::nonnegative_int(false)) { // Interval can be represented as both an svalue and a uvalue since it fits in [0, INT_MAX]. auto lpub = left_interval_positive.truncate_to_sint(false).ub(); @@ -1271,24 +1259,24 @@ bool ebpf_domain_t::TypeDomain::implies_type(const NumAbsDomain& inv, const line return inv.when(a).entail(b); } -bool ebpf_domain_t::TypeDomain::is_in_group(const NumAbsDomain& m_inv, const Reg& r, TypeGroup group) const { +bool ebpf_domain_t::TypeDomain::is_in_group(const NumAbsDomain& inv, const Reg& r, TypeGroup group) const { using namespace crab::dsl_syntax; variable_t t = reg_pack(r).type; switch (group) { - case TypeGroup::number: return m_inv.entail(t == T_NUM); - case TypeGroup::map_fd: return m_inv.entail(t == T_MAP); - case TypeGroup::map_fd_programs: return m_inv.entail(t == T_MAP_PROGRAMS); - case TypeGroup::ctx: return m_inv.entail(t == T_CTX); - case TypeGroup::packet: return m_inv.entail(t == T_PACKET); - case TypeGroup::stack: return m_inv.entail(t == T_STACK); - case TypeGroup::shared: return m_inv.entail(t == T_SHARED); - case TypeGroup::non_map_fd: return m_inv.entail(t >= T_NUM); - case TypeGroup::mem: return m_inv.entail(t >= T_PACKET); - case TypeGroup::mem_or_num: return m_inv.entail(t >= T_NUM) && m_inv.entail(t != T_CTX); - case TypeGroup::pointer: return m_inv.entail(t >= T_CTX); - case TypeGroup::ptr_or_num: return m_inv.entail(t >= T_NUM); - case TypeGroup::stack_or_packet: return m_inv.entail(t >= T_PACKET) && m_inv.entail(t <= T_STACK); - case TypeGroup::singleton_ptr: return m_inv.entail(t >= T_CTX) && m_inv.entail(t <= T_STACK); + case TypeGroup::number: return inv.entail(t == T_NUM); + case TypeGroup::map_fd: return inv.entail(t == T_MAP); + case TypeGroup::map_fd_programs: return inv.entail(t == T_MAP_PROGRAMS); + case TypeGroup::ctx: return inv.entail(t == T_CTX); + case TypeGroup::packet: return inv.entail(t == T_PACKET); + case TypeGroup::stack: return inv.entail(t == T_STACK); + case TypeGroup::shared: return inv.entail(t == T_SHARED); + case TypeGroup::non_map_fd: return inv.entail(t >= T_NUM); + case TypeGroup::mem: return inv.entail(t >= T_PACKET); + case TypeGroup::mem_or_num: return inv.entail(t >= T_NUM) && inv.entail(t != T_CTX); + case TypeGroup::pointer: return inv.entail(t >= T_CTX); + case TypeGroup::ptr_or_num: return inv.entail(t >= T_NUM); + case TypeGroup::stack_or_packet: return inv.entail(t >= T_PACKET) && inv.entail(t <= T_STACK); + case TypeGroup::singleton_ptr: return inv.entail(t >= T_CTX) && inv.entail(t <= T_STACK); } assert(false); return false; @@ -2581,7 +2569,6 @@ void ebpf_domain_t::operator()(const Bin& bin) { break; case Bin::Op::LSH: if (m_inv.entail(type_is_number(src_reg))) { - auto src = reg_pack(src_reg); auto src_interval = m_inv.eval_interval(src.uvalue); if (std::optional sn = src_interval.singleton()) { uint64_t imm = sn->cast_to_uint64() & (bin.is64 ? 63 : 31); @@ -2600,7 +2587,6 @@ void ebpf_domain_t::operator()(const Bin& bin) { break; case Bin::Op::RSH: if (m_inv.entail(type_is_number(src_reg))) { - auto src = reg_pack(src_reg); auto src_interval = m_inv.eval_interval(src.uvalue); if (std::optional sn = src_interval.singleton()) { uint64_t imm = sn->cast_to_uint64() & (bin.is64 ? 63 : 31); diff --git a/src/crab/ebpf_domain.hpp b/src/crab/ebpf_domain.hpp index 4b5c890c4..4e4aa3710 100644 --- a/src/crab/ebpf_domain.hpp +++ b/src/crab/ebpf_domain.hpp @@ -31,8 +31,8 @@ class ebpf_domain_t final { static ebpf_domain_t bottom(); void set_to_top(); void set_to_bottom(); - bool is_bottom() const; - bool is_top() const; + [[nodiscard]] bool is_bottom() const; + [[nodiscard]] bool is_top() const; bool operator<=(const ebpf_domain_t& other); bool operator==(const ebpf_domain_t& other) const; void operator|=(ebpf_domain_t&& other); @@ -102,7 +102,7 @@ class ebpf_domain_t final { void apply_signed(crab::domains::NumAbsDomain& inv, crab::binop_t op, variable_t xs, variable_t xu, variable_t y, variable_t z, int finite_width); void apply_unsigned(crab::domains::NumAbsDomain& inv, crab::binop_t op, variable_t xs, variable_t xu, variable_t y, - variable_t z, int finite_width); + variable_t z, int finite_width); void add(const Reg& reg, int imm, int finite_width); void add(variable_t lhs, variable_t op2); @@ -147,15 +147,15 @@ class ebpf_domain_t final { void havoc_offsets(NumAbsDomain& inv, const Reg& reg); static std::optional get_type_offset_variable(const Reg& reg, int type); - std::optional get_type_offset_variable(const Reg& reg, const NumAbsDomain& inv) const; - std::optional get_type_offset_variable(const Reg& reg) const; + [[nodiscard]] std::optional get_type_offset_variable(const Reg& reg, const NumAbsDomain& inv) const; + [[nodiscard]] std::optional get_type_offset_variable(const Reg& reg) const; void scratch_caller_saved_registers(); - std::optional get_map_type(const Reg& map_fd_reg) const; - std::optional get_map_inner_map_fd(const Reg& map_fd_reg) const; - crab::interval_t get_map_key_size(const Reg& map_fd_reg) const; - crab::interval_t get_map_value_size(const Reg& map_fd_reg) const; - crab::interval_t get_map_max_entries(const Reg& map_fd_reg) const; + [[nodiscard]] std::optional get_map_type(const Reg& map_fd_reg) const; + [[nodiscard]] std::optional get_map_inner_map_fd(const Reg& map_fd_reg) const; + [[nodiscard]] crab::interval_t get_map_key_size(const Reg& map_fd_reg) const; + [[nodiscard]] crab::interval_t get_map_value_size(const Reg& map_fd_reg) const; + [[nodiscard]] crab::interval_t get_map_max_entries(const Reg& map_fd_reg) const; void forget_packet_pointers(); void havoc_register(NumAbsDomain& inv, const Reg& reg); void do_load_mapfd(const Reg& dst_reg, int mapfd, bool maybe_null); @@ -178,14 +178,15 @@ class ebpf_domain_t final { void recompute_stack_numeric_size(NumAbsDomain& inv, const Reg& reg); void recompute_stack_numeric_size(NumAbsDomain& inv, variable_t type_variable); - void do_load_stack(NumAbsDomain& inv, const Reg& target_reg, const linear_expression_t& addr, int width, const Reg& src_reg); + void do_load_stack(NumAbsDomain& inv, const Reg& target_reg, const linear_expression_t& addr, int width, + const Reg& src_reg); void do_load_ctx(NumAbsDomain& inv, const Reg& target_reg, const linear_expression_t& addr_vague, int width); void do_load_packet_or_shared(NumAbsDomain& inv, const Reg& target_reg, const linear_expression_t& addr, int width); void do_load(const Mem& b, const Reg& target_reg); template - void do_store_stack(crab::domains::NumAbsDomain& inv, const number_t& width, const linear_expression_t& addr, X val_type, Y val_svalue, Z val_uvalue, - const std::optional& opt_val_reg); + void do_store_stack(crab::domains::NumAbsDomain& inv, const number_t& width, const linear_expression_t& addr, + X val_type, Y val_svalue, Z val_uvalue, const std::optional& opt_val_reg); template void do_mem_store(const Mem& b, Type val_type, SValue val_svalue, UValue val_uvalue, @@ -195,7 +196,6 @@ class ebpf_domain_t final { static void initialize_packet(ebpf_domain_t& inv); - private: /// Mapping from variables (including registers, types, offsets, /// memory locations, etc.) to numeric intervals or relationships @@ -228,16 +228,17 @@ class ebpf_domain_t final { [[nodiscard]] bool has_type(const NumAbsDomain& inv, const number_t& t, type_encoding_t type) const; [[nodiscard]] bool same_type(const NumAbsDomain& inv, const Reg& a, const Reg& b) const; - [[nodiscard]] bool implies_type(const NumAbsDomain& inv, const linear_constraint_t& a, const linear_constraint_t& b) const; - - NumAbsDomain join_over_types(const NumAbsDomain& inv, const Reg& reg, - const std::function& transition) const; - NumAbsDomain join_by_if_else(const NumAbsDomain& inv, const linear_constraint_t& condition, - const std::function& if_true, - const std::function& if_false) const; + [[nodiscard]] bool implies_type(const NumAbsDomain& inv, const linear_constraint_t& a, + const linear_constraint_t& b) const; + + [[nodiscard]] NumAbsDomain + join_over_types(const NumAbsDomain& inv, const Reg& reg, + const std::function& transition) const; + [[nodiscard]] NumAbsDomain join_by_if_else(const NumAbsDomain& inv, const linear_constraint_t& condition, + const std::function& if_true, + const std::function& if_false) const; void selectively_join_based_on_type(NumAbsDomain& dst, NumAbsDomain& src) const; - void add_extra_invariant(NumAbsDomain& dst, - std::map& extra_invariants, + void add_extra_invariant(NumAbsDomain& dst, std::map& extra_invariants, variable_t type_variable, type_encoding_t type, crab::data_kind_t kind, const NumAbsDomain& other) const; @@ -248,4 +249,4 @@ class ebpf_domain_t final { std::string current_assertion; }; // end ebpf_domain_t -} +} // namespace crab diff --git a/src/crab_utils/adapt_sgraph.hpp b/src/crab_utils/adapt_sgraph.hpp index 23741a770..c24ae935c 100644 --- a/src/crab_utils/adapt_sgraph.hpp +++ b/src/crab_utils/adapt_sgraph.hpp @@ -22,7 +22,6 @@ class TreeSMap final { col map; public: - using elt_t = std::pair; using elt_iter_t = col::const_iterator; [[nodiscard]] size_t size() const { return map.size(); } @@ -142,7 +141,7 @@ class AdaptGraph final { } struct vert_const_iterator { - vert_id v; + vert_id v{}; const std::vector& is_free; vert_id operator*() const { return v; } @@ -401,14 +400,14 @@ class AdaptGraph final { // Ick. This'll have another indirection on every operation. // We'll see what the performance costs are like. - std::vector _preds; - std::vector _succs; - std::vector _ws; + std::vector _preds{}; + std::vector _succs{}; + std::vector _ws{}; - size_t edge_count; + size_t edge_count{}; - std::vector is_free; - std::vector free_id; - std::vector free_widx; + std::vector is_free{}; + std::vector free_id{}; + std::vector free_widx{}; }; } // namespace crab diff --git a/src/ebpf_vm_isa.hpp b/src/ebpf_vm_isa.hpp index c9716ec1c..0f1f0974c 100644 --- a/src/ebpf_vm_isa.hpp +++ b/src/ebpf_vm_isa.hpp @@ -57,9 +57,7 @@ enum { INST_OP_JA = (INST_CLS_JMP | 0x00), INST_OP_CALL = (INST_CLS_JMP | 0x80), INST_OP_EXIT = (INST_CLS_JMP | 0x90), -}; -enum { INST_ALU_OP_ADD = 0x00, INST_ALU_OP_SUB = 0x10, INST_ALU_OP_MUL = 0x20, @@ -74,10 +72,8 @@ enum { INST_ALU_OP_MOV = 0xb0, INST_ALU_OP_ARSH = 0xc0, INST_ALU_OP_END = 0xd0, - INST_ALU_OP_MASK = 0xf0 -}; + INST_ALU_OP_MASK = 0xf0, -enum { R0_RETURN_VALUE = 0, R1_ARG = 1, R2_ARG = 2, diff --git a/src/spec_type_descriptors.hpp b/src/spec_type_descriptors.hpp index 437f0a02f..cad589fa7 100644 --- a/src/spec_type_descriptors.hpp +++ b/src/spec_type_descriptors.hpp @@ -32,13 +32,16 @@ struct EbpfMapDescriptor { unsigned int inner_map_fd; }; +constexpr unsigned int DEFAULT_MAP_FD = 0xffffffff; + struct EbpfProgramType { - std::string name; // For ease of display, not used by the verifier. - const ebpf_context_descriptor_t* context_descriptor {}; - uint64_t platform_specific_data {}; // E.g., integer program type. - std::vector section_prefixes; - bool is_privileged {}; + std::string name{}; // For ease of display, not used by the verifier. + const ebpf_context_descriptor_t* context_descriptor{}; + uint64_t platform_specific_data{}; // E.g., integer program type. + std::vector section_prefixes{}; + bool is_privileged{}; }; + void print_map_descriptors(const std::vector& descriptors, std::ostream& o); using EquivalenceKey = std::tuple< @@ -48,25 +51,25 @@ using EquivalenceKey = std::tuple< uint32_t /* max_entries */>; struct program_info { - const struct ebpf_platform_t* platform = nullptr; - std::vector map_descriptors; - EbpfProgramType type; - std::map cache; + const struct ebpf_platform_t* platform{}; + std::vector map_descriptors{}; + EbpfProgramType type{}; + std::map cache{}; }; -typedef struct _btf_line_info { - std::string file_name; - std::string source_line; - uint32_t line_number; - uint32_t column_number; -} btf_line_info_t; +struct btf_line_info_t { + std::string file_name{}; + std::string source_line{}; + uint32_t line_number{}; + uint32_t column_number{}; +}; struct raw_program { - std::string filename; - std::string section; - std::vector prog; - program_info info; - std::vector line_info; + std::string filename{}; + std::string section{}; + std::vector prog{}; + program_info info{}; + std::vector line_info{}; }; extern thread_local crab::lazy_allocator global_program_info; diff --git a/src/string_constraints.hpp b/src/string_constraints.hpp index ead071c34..278c8f414 100644 --- a/src/string_constraints.hpp +++ b/src/string_constraints.hpp @@ -26,7 +26,7 @@ enum type_encoding_t { }; struct string_invariant { - std::optional> maybe_inv; + std::optional> maybe_inv{}; string_invariant() = default; From ef234a6f25b7ca2c551b5792cd0ec4039b9457a7 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Mon, 16 Oct 2023 18:38:18 +0300 Subject: [PATCH 007/373] narrow to constant bounds once (#479) Signed-off-by: Elazar Gershuni --- CMakeLists.txt | 2 - src/asm_marshal.cpp | 4 + src/asm_ostream.cpp | 3 + src/asm_ostream.hpp | 1 + src/asm_syntax.hpp | 7 +- src/assertions.cpp | 2 + src/crab/ebpf_domain.cpp | 67 ++++++--- src/crab/ebpf_domain.hpp | 9 +- src/crab/fwd_analyzer.cpp | 84 ++++++----- src/crab/fwd_analyzer.hpp | 3 +- src/crab/var_factory.cpp | 10 +- src/crab/variable.hpp | 4 +- src/crab_verifier.cpp | 119 ++++++--------- src/ebpf_yaml.cpp | 9 +- src/ebpf_yaml.hpp | 2 +- src/test/test_loop.cpp | 33 ----- src/test/test_termination.cpp | 69 --------- src/test/test_yaml.cpp | 1 + test-data/assign.yaml | 2 +- test-data/loop.yaml | 268 ++++++++++++++++++++++++++++++++++ 20 files changed, 446 insertions(+), 253 deletions(-) delete mode 100644 src/test/test_loop.cpp delete mode 100644 src/test/test_termination.cpp create mode 100644 test-data/loop.yaml diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f1c82543..26d9ae3bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,10 +80,8 @@ file(GLOB LIB_SRC file(GLOB ALL_TEST "./src/test/test.cpp" "./src/test/test_conformance.cpp" - "./src/test/test_loop.cpp" "./src/test/test_marshal.cpp" "./src/test/test_print.cpp" - "./src/test/test_termination.cpp" "./src/test/test_verify.cpp" "./src/test/test_wto.cpp" "./src/test/test_yaml.cpp" diff --git a/src/asm_marshal.cpp b/src/asm_marshal.cpp index 82e4bc207..36bbe783e 100644 --- a/src/asm_marshal.cpp +++ b/src/asm_marshal.cpp @@ -229,6 +229,10 @@ struct MarshalVisitor { .offset = static_cast(b.access.offset), .imm = 0}}; } + + vector operator()(IncrementLoopCounter const& ins) { + return {}; + } }; vector marshal(const Instruction& ins, pc_t pc) { return std::visit(MarshalVisitor{label_to_offset(pc)}, ins); } diff --git a/src/asm_ostream.cpp b/src/asm_ostream.cpp index 1abf83fd9..2ecdda978 100644 --- a/src/asm_ostream.cpp +++ b/src/asm_ostream.cpp @@ -329,6 +329,9 @@ struct InstructionPrinterVisitor { os_ << "assert " << a.cst; } + void operator()(IncrementLoopCounter const& a) { + os_ << crab::variable_t::instruction_count(to_string(a.name)) << "++"; + } }; string to_string(label_t const& label) { diff --git a/src/asm_ostream.hpp b/src/asm_ostream.hpp index 2766c16bf..7ab2c059e 100644 --- a/src/asm_ostream.hpp +++ b/src/asm_ostream.hpp @@ -48,5 +48,6 @@ inline std::ostream& operator<<(std::ostream& os, Mem const& a) { return os << ( inline std::ostream& operator<<(std::ostream& os, LockAdd const& a) { return os << (Instruction)a; } inline std::ostream& operator<<(std::ostream& os, Assume const& a) { return os << (Instruction)a; } inline std::ostream& operator<<(std::ostream& os, Assert const& a) { return os << (Instruction)a; } +inline std::ostream& operator<<(std::ostream& os, IncrementLoopCounter const& a) { return os << (Instruction)a; } std::ostream& operator<<(std::ostream& os, AssertionConstraint const& a); std::string to_string(AssertionConstraint const& constraint); diff --git a/src/asm_syntax.hpp b/src/asm_syntax.hpp index 0e4ba3a2a..09da5b6a4 100644 --- a/src/asm_syntax.hpp +++ b/src/asm_syntax.hpp @@ -309,7 +309,11 @@ struct Assert { Assert(AssertionConstraint cst): cst(cst) { } }; -using Instruction = std::variant; +struct IncrementLoopCounter { + label_t name; +}; + +using Instruction = std::variant; using LabeledInstruction = std::tuple>; using InstructionSeq = std::vector; @@ -382,6 +386,7 @@ DECLARE_EQ5(ValidAccess, reg, offset, width, or_null, access_type) DECLARE_EQ3(ValidMapKeyValue, access_reg, map_fd_reg, key) DECLARE_EQ1(ZeroCtxOffset, reg) DECLARE_EQ1(Assert, cst) +DECLARE_EQ1(IncrementLoopCounter, name) } diff --git a/src/assertions.cpp b/src/assertions.cpp index e3c55ae9d..a76d5906d 100644 --- a/src/assertions.cpp +++ b/src/assertions.cpp @@ -38,6 +38,8 @@ class AssertExtractor { vector operator()(Assert const& ins) const { assert(false); return {}; } + vector operator()(IncrementLoopCounter ins) const { assert(false); return {}; } + vector operator()(LoadMapFd const& ins) const { return {}; } /// Packet access implicitly uses R6, so verify that R6 still has a pointer to the context. diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index d49d23977..072ffad67 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -909,8 +909,39 @@ ebpf_domain_t ebpf_domain_t::operator&(const ebpf_domain_t& other) const { return ebpf_domain_t(m_inv & other.m_inv, stack & other.stack); } -ebpf_domain_t ebpf_domain_t::widen(const ebpf_domain_t& other) { - return ebpf_domain_t(m_inv.widen(other.m_inv), stack | other.stack); +ebpf_domain_t ebpf_domain_t::calculate_constant_limits() { + ebpf_domain_t inv; + using namespace crab::dsl_syntax; + for (int i : {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) { + auto r = reg_pack(i); + inv += r.svalue <= std::numeric_limits::max(); + inv += r.svalue >= std::numeric_limits::min(); + inv += r.uvalue <= std::numeric_limits::max(); + inv += r.uvalue >= 0; + inv += r.stack_offset <= EBPF_STACK_SIZE; + inv += r.stack_offset >= 0; + inv += r.shared_offset <= r.shared_region_size; + inv += r.shared_offset >= 0; + inv += r.packet_offset <= variable_t::packet_size(); + inv += r.packet_offset >= 0; + if (thread_local_options.check_termination) { + for (variable_t counter : variable_t::get_instruction_counters()) { + inv += counter <= std::numeric_limits::max(); + inv += counter >= 0; + inv += counter <= r.svalue; + } + } + } + return inv; +} + +static const ebpf_domain_t constant_limits = ebpf_domain_t::calculate_constant_limits(); + +ebpf_domain_t ebpf_domain_t::widen(const ebpf_domain_t& other, bool to_constants) { + ebpf_domain_t res{m_inv.widen(other.m_inv), stack | other.stack}; + if (to_constants) + return res & constant_limits; + return res; } ebpf_domain_t ebpf_domain_t::narrow(const ebpf_domain_t& other) { @@ -1333,19 +1364,10 @@ void ebpf_domain_t::overflow_unsigned(NumAbsDomain& inv, variable_t lhs, int fin overflow_bounds(inv, lhs, span, finite_width, false); } -void ebpf_domain_t::operator()(const basic_block_t& bb, bool check_termination) { +void ebpf_domain_t::operator()(const basic_block_t& bb) { for (const Instruction& statement : bb) { std::visit(*this, statement); } - if (check_termination) { - // +1 to avoid being tricked by empty loops - add(variable_t::instruction_count(), crab::number_t((unsigned)bb.size() + 1)); - } -} - -bound_t ebpf_domain_t::get_instruction_count_upper_bound() { - const auto& ub = m_inv[variable_t::instruction_count()].ub(); - return ub; } void ebpf_domain_t::check_access_stack(NumAbsDomain& inv, const linear_expression_t& lb, @@ -2681,7 +2703,7 @@ void ebpf_domain_t::initialize_packet(ebpf_domain_t& inv) { ebpf_domain_t ebpf_domain_t::from_constraints(const std::set& constraints, bool setup_constraints) { ebpf_domain_t inv; if (setup_constraints) { - inv = ebpf_domain_t::setup_entry(false, false); + inv = ebpf_domain_t::setup_entry(false); } auto numeric_ranges = std::vector(); for (const auto& cst : parse_linear_constraints(constraints, numeric_ranges)) { @@ -2696,7 +2718,7 @@ ebpf_domain_t ebpf_domain_t::from_constraints(const std::set& const return inv; } -ebpf_domain_t ebpf_domain_t::setup_entry(bool check_termination, bool init_r1) { +ebpf_domain_t ebpf_domain_t::setup_entry(bool init_r1) { using namespace crab::dsl_syntax; ebpf_domain_t inv; @@ -2719,10 +2741,21 @@ ebpf_domain_t ebpf_domain_t::setup_entry(bool check_termination, bool init_r1) { } initialize_packet(inv); - if (check_termination) { - inv.assign(variable_t::instruction_count(), 0); - } return inv; } +void ebpf_domain_t::initialize_instruction_count(const label_t label) { + m_inv.assign(variable_t::instruction_count(to_string(label)), 0); +} + +bound_t ebpf_domain_t::get_instruction_count_upper_bound() { + crab::bound_t ub{number_t{0}}; + for (variable_t counter : variable_t::get_instruction_counters()) + ub += std::max(ub, m_inv[counter].ub()); + return ub; +} + +void ebpf_domain_t::operator()(const IncrementLoopCounter& ins) { + this->add(variable_t::instruction_count(to_string(ins.name)), 1); +} } // namespace crab diff --git a/src/crab/ebpf_domain.hpp b/src/crab/ebpf_domain.hpp index 4e4aa3710..c8bba9197 100644 --- a/src/crab/ebpf_domain.hpp +++ b/src/crab/ebpf_domain.hpp @@ -41,20 +41,20 @@ class ebpf_domain_t final { ebpf_domain_t operator|(const ebpf_domain_t& other) const&; ebpf_domain_t operator|(const ebpf_domain_t& other) &&; ebpf_domain_t operator&(const ebpf_domain_t& other) const; - ebpf_domain_t widen(const ebpf_domain_t& other); + ebpf_domain_t widen(const ebpf_domain_t& other, bool to_constants); ebpf_domain_t widening_thresholds(const ebpf_domain_t& other, const crab::iterators::thresholds_t& ts); ebpf_domain_t narrow(const ebpf_domain_t& other); typedef bool check_require_func_t(NumAbsDomain&, const linear_constraint_t&, std::string); void set_require_check(std::function f); bound_t get_instruction_count_upper_bound(); - static ebpf_domain_t setup_entry(bool check_termination, bool init_r1); + static ebpf_domain_t setup_entry(bool init_r1); static ebpf_domain_t from_constraints(const std::set& constraints, bool setup_constraints); string_invariant to_set(); // abstract transformers - void operator()(const basic_block_t& bb, bool check_termination); + void operator()(const basic_block_t& bb); void operator()(const Addable&); void operator()(const Assert&); @@ -77,7 +77,10 @@ class ebpf_domain_t final { void operator()(const ValidSize&); void operator()(const ValidStore&); void operator()(const ZeroCtxOffset&); + void operator()(const IncrementLoopCounter&); + void initialize_instruction_count(label_t label); + static ebpf_domain_t calculate_constant_limits(); private: // private generic domain functions void operator+=(const linear_constraint_t& cst); diff --git a/src/crab/fwd_analyzer.cpp b/src/crab/fwd_analyzer.cpp index f79e86135..5a760bae4 100644 --- a/src/crab/fwd_analyzer.cpp +++ b/src/crab/fwd_analyzer.cpp @@ -48,44 +48,37 @@ class interleaved_fwd_fixpoint_iterator_t final { wto_t _wto; invariant_table_t _pre, _post; - /// number of iterations until triggering widening - const unsigned int _widening_delay{1}; - /// number of narrowing iterations. If the narrowing operator is /// indeed a narrowing operator this parameter is not /// needed. However, there are abstract domains for which an actual /// narrowing operation is not available so we must enforce /// termination. - const unsigned int _descending_iterations; + static constexpr unsigned int _descending_iterations = 2000000; /// Used to skip the analysis until _entry is found bool _skip{true}; - /// Whether the domain tracks instruction count; the invariants are somewhat easier to read without it - /// Generally corresponds to the check_termination flag in ebpf_verifier_options_t - const bool check_termination; - private: - inline void set_pre(const label_t& label, const ebpf_domain_t& v) { _pre[label] = v; } + void set_pre(const label_t& label, const ebpf_domain_t& v) { _pre[label] = v; } - inline void transform_to_post(const label_t& label, ebpf_domain_t pre) { + void transform_to_post(const label_t& label, ebpf_domain_t pre) { basic_block_t& bb = _cfg.get_node(label); - pre(bb, check_termination); + pre(bb); _post[label] = std::move(pre); } - [[nodiscard]] - ebpf_domain_t extrapolate(const label_t& node, unsigned int iteration, ebpf_domain_t before, - const ebpf_domain_t& after) const { - if (iteration <= _widening_delay) { + [[nodiscard]] static ebpf_domain_t extrapolate(ebpf_domain_t before, const ebpf_domain_t& after, + unsigned int iteration) { + /// number of iterations until triggering widening + constexpr auto _widening_delay = 2; + + if (iteration < _widening_delay) { return before | after; - } else { - return before.widen(after); } + return before.widen(after, iteration == _widening_delay); } - static ebpf_domain_t refine(const label_t& node, unsigned int iteration, ebpf_domain_t before, - const ebpf_domain_t& after) { + static ebpf_domain_t refine(ebpf_domain_t before, const ebpf_domain_t& after, unsigned int iteration) { if (iteration == 1) { return before & after; } else { @@ -102,8 +95,7 @@ class interleaved_fwd_fixpoint_iterator_t final { } public: - explicit interleaved_fwd_fixpoint_iterator_t(cfg_t& cfg, unsigned int descending_iterations, bool check_termination) - : _cfg(cfg), _wto(cfg), _descending_iterations(descending_iterations), check_termination(check_termination) { + explicit interleaved_fwd_fixpoint_iterator_t(cfg_t& cfg) : _cfg(cfg), _wto(cfg) { for (const auto& label : _cfg.labels()) { _pre.emplace(label, ebpf_domain_t::bottom()); _post.emplace(label, ebpf_domain_t::bottom()); @@ -114,17 +106,28 @@ class interleaved_fwd_fixpoint_iterator_t final { ebpf_domain_t get_post(const label_t& node) { return _post.at(node); } - void operator()(const label_t& vertex); + void operator()(const label_t& node); void operator()(std::shared_ptr& cycle); - friend std::pair run_forward_analyzer(cfg_t& cfg, const ebpf_domain_t& entry_inv, bool check_termination); + friend std::pair run_forward_analyzer(cfg_t& cfg, ebpf_domain_t entry_inv); }; -std::pair run_forward_analyzer(cfg_t& cfg, const ebpf_domain_t& entry_inv, bool check_termination) { +std::pair run_forward_analyzer(cfg_t& cfg, ebpf_domain_t entry_inv) { // Go over the CFG in weak topological order (accounting for loops). - constexpr unsigned int descending_iterations = 2000000; - interleaved_fwd_fixpoint_iterator_t analyzer(cfg, descending_iterations, check_termination); + interleaved_fwd_fixpoint_iterator_t analyzer(cfg); + if (thread_local_options.check_termination) { + std::vector cycle_heads; + for (auto& component : analyzer._wto) { + if (std::holds_alternative>(*component)) { + cycle_heads.push_back(std::get>(*component)->head()); + } + } + for (const label_t& label : cycle_heads) { + entry_inv.initialize_instruction_count(label); + cfg.get_node(label).insert(IncrementLoopCounter{label}); + } + } analyzer.set_pre(cfg.entry_label(), entry_inv); for (auto& component : analyzer._wto) { std::visit(analyzer, *component); @@ -164,46 +167,41 @@ void interleaved_fwd_fixpoint_iterator_t::operator()(std::shared_ptr cycle_nesting)) { - pre |= get_post(prev); + invariant |= get_post(prev); } } } for (unsigned int iteration = 1;; ++iteration) { // Increasing iteration sequence with widening - set_pre(head, pre); - transform_to_post(head, pre); + set_pre(head, invariant); + transform_to_post(head, invariant); for (auto& component : *cycle) { wto_component_t c = *component; if (!std::holds_alternative(c) || (std::get(c) != head)) std::visit(*this, *component); } ebpf_domain_t new_pre = join_all_prevs(head); - if (new_pre <= pre) { + if (new_pre <= invariant) { // Post-fixpoint reached set_pre(head, new_pre); - pre = std::move(new_pre); + invariant = std::move(new_pre); break; } else { - pre = extrapolate(head, iteration, pre, new_pre); + invariant = extrapolate(invariant, new_pre, iteration); } } - if (this->_descending_iterations == 0) { - // no narrowing - return; - } - for (unsigned int iteration = 1;; ++iteration) { // Decreasing iteration sequence with narrowing - transform_to_post(head, pre); + transform_to_post(head, invariant); for (auto& component : *cycle) { wto_component_t c = *component; @@ -211,14 +209,14 @@ void interleaved_fwd_fixpoint_iterator_t::operator()(std::shared_ptr _descending_iterations) break; - pre = refine(head, iteration, pre, new_pre); - set_pre(head, pre); + invariant = refine(invariant, new_pre, iteration); + set_pre(head, invariant); } } } diff --git a/src/crab/fwd_analyzer.hpp b/src/crab/fwd_analyzer.hpp index 4f01c3841..a6e35e476 100644 --- a/src/crab/fwd_analyzer.hpp +++ b/src/crab/fwd_analyzer.hpp @@ -13,7 +13,6 @@ namespace crab { using invariant_table_t = std::map; -std::pair run_forward_analyzer(cfg_t& cfg, const ebpf_domain_t& entry_inv, - bool check_termination); +std::pair run_forward_analyzer(cfg_t& cfg, ebpf_domain_t entry_inv); } // namespace crab diff --git a/src/crab/var_factory.cpp b/src/crab/var_factory.cpp index 94d55d5d3..2418a0b96 100644 --- a/src/crab/var_factory.cpp +++ b/src/crab/var_factory.cpp @@ -88,7 +88,7 @@ variable_t variable_t::kind_var(data_kind_t kind, variable_t type_variable) { variable_t variable_t::meta_offset() { return make("meta_offset"); } variable_t variable_t::packet_size() { return make("packet_size"); } -variable_t variable_t::instruction_count() { return make("instruction_count"); } +variable_t variable_t::instruction_count(const std::string& label) { return make("pc[" + label + "]"); } static bool ends_with(const std::string& str, const std::string& suffix) { @@ -108,4 +108,12 @@ bool variable_t::is_in_stack() const { return this->name()[0] == 's'; } +std::vector variable_t::get_instruction_counters() { + std::vector res; + for (const std::string& name: *names) { + if (name.find("pc") == 0) + res.push_back(make(name)); + } + return res; +} } // end namespace crab diff --git a/src/crab/variable.hpp b/src/crab/variable.hpp index 12dd13671..f51055c42 100644 --- a/src/crab/variable.hpp +++ b/src/crab/variable.hpp @@ -75,8 +75,8 @@ class variable_t final { static variable_t kind_var(data_kind_t kind, variable_t type_variable); static variable_t meta_offset(); static variable_t packet_size(); - static variable_t instruction_count(); - + static std::vector get_instruction_counters(); + static variable_t instruction_count(const std::string& label); [[nodiscard]] bool is_in_stack() const; struct Hasher { diff --git a/src/crab_verifier.cpp b/src/crab_verifier.cpp index f4702869b..7904415cd 100644 --- a/src/crab_verifier.cpp +++ b/src/crab_verifier.cpp @@ -4,10 +4,7 @@ * This module is about selecting the numerical and memory domains, initiating * the verification process and returning the results. **/ -#include -#include -#include #include #include #include @@ -23,8 +20,8 @@ #include "crab_verifier.hpp" #include "string_constraints.hpp" -using std::string; using crab::ebpf_domain_t; +using std::string; thread_local crab::lazy_allocator global_program_info; thread_local ebpf_verifier_options_t thread_local_options; @@ -34,12 +31,9 @@ struct checks_db final { std::map> m_db{}; int total_warnings{}; int total_unreachable{}; - crab::bound_t max_instruction_count{crab::number_t{0}};; - std::set maybe_nonterminating; + crab::bound_t max_instruction_count{crab::number_t{0}}; - void add(const label_t& label, const std::string& msg) { - m_db[label].emplace_back(msg); - } + void add(const label_t& label, const std::string& msg) { m_db[label].emplace_back(msg); } void add_warning(const label_t& label, const std::string& msg) { add(label, msg); @@ -51,11 +45,6 @@ struct checks_db final { total_unreachable++; } - void add_nontermination(const label_t& label) { - maybe_nonterminating.insert(label); - total_warnings++; - } - [[nodiscard]] int get_max_instruction_count() const { auto m = this->max_instruction_count.number(); if (m && m->fits_sint32()) @@ -66,60 +55,47 @@ struct checks_db final { checks_db() = default; }; -static checks_db generate_report(cfg_t& cfg, - crab::invariant_table_t& pre_invariants, +static checks_db generate_report(cfg_t& cfg, crab::invariant_table_t& pre_invariants, crab::invariant_table_t& post_invariants) { checks_db m_db; for (const label_t& label : cfg.sorted_labels()) { basic_block_t& bb = cfg.get_node(label); ebpf_domain_t from_inv(pre_invariants.at(label)); - from_inv.set_require_check([&m_db, label](auto& inv, const crab::linear_constraint_t& cst, - const std::string& s) { - if (inv.is_bottom()) - return true; - if (cst.is_contradiction()) { - m_db.add_warning(label, s); - return false; - } - - if (inv.entail(cst)) { - // add_redundant(s); - return true; - } else if (inv.intersect(cst)) { - // TODO: add_error() if imply negation - m_db.add_warning(label, s); - return false; - } else { - m_db.add_warning(label, s); - return false; - } - }); - - if (thread_local_options.check_termination) { - // Pinpoint the places where divergence might occur. - crab::bound_t min_instruction_count_upper_bound{crab::bound_t::plus_infinity()}; - for (const label_t& prev_label : bb.prev_blocks_set()) { - crab::bound_t instruction_count = pre_invariants.at(prev_label).get_instruction_count_upper_bound(); - min_instruction_count_upper_bound = std::min(min_instruction_count_upper_bound, instruction_count); - } - - crab::bound_t max_instructions{100000}; - crab::bound_t instruction_count_upper_bound = from_inv.get_instruction_count_upper_bound(); - if ((min_instruction_count_upper_bound < max_instructions) && - (instruction_count_upper_bound >= max_instructions)) - m_db.add_nontermination(label); - - m_db.max_instruction_count = std::max(m_db.max_instruction_count, instruction_count_upper_bound); - } + from_inv.set_require_check( + [&m_db, label](auto& inv, const crab::linear_constraint_t& cst, const std::string& s) { + if (inv.is_bottom()) + return true; + if (cst.is_contradiction()) { + m_db.add_warning(label, s); + return false; + } + + if (inv.entail(cst)) { + // add_redundant(s); + return true; + } else if (inv.intersect(cst)) { + // TODO: add_error() if imply negation + m_db.add_warning(label, s); + return false; + } else { + m_db.add_warning(label, s); + return false; + } + }); bool pre_bot = from_inv.is_bottom(); - from_inv(bb, thread_local_options.check_termination); + from_inv(bb); if (!pre_bot && from_inv.is_bottom()) { m_db.add_unreachable(label, std::string("Code is unreachable after ") + to_string(bb.label())); } } + + if (thread_local_options.check_termination) { + auto last_inv = post_invariants.at(cfg.exit_label()); + m_db.max_instruction_count = last_inv.get_instruction_count_upper_bound(); + } return m_db; } @@ -146,12 +122,9 @@ static void print_report(std::ostream& os, const checks_db& db, const Instructio } } os << "\n"; - if (!db.maybe_nonterminating.empty()) { - os << "Could not prove termination on join into: "; - for (const label_t& label : db.maybe_nonterminating) { - os << label << ", "; - } - os << "\n"; + crab::number_t max_instructions{100000}; + if (db.max_instruction_count > max_instructions) { + os << "Could not prove termination.\n"; } } @@ -178,9 +151,8 @@ static checks_db get_ebpf_report(std::ostream& s, cfg_t& cfg, program_info info, try { // Get dictionaries of pre-invariants and post-invariants for each basic block. - ebpf_domain_t entry_dom = ebpf_domain_t::setup_entry(options->check_termination, true); - auto [pre_invariants, post_invariants] = - crab::run_forward_analyzer(cfg, std::move(entry_dom), options->check_termination); + ebpf_domain_t entry_dom = ebpf_domain_t::setup_entry(true); + auto [pre_invariants, post_invariants] = crab::run_forward_analyzer(cfg, std::move(entry_dom)); return get_analysis_report(s, cfg, pre_invariants, post_invariants); } catch (std::runtime_error& e) { // Convert verifier runtime_error exceptions to failure. @@ -206,15 +178,19 @@ bool run_ebpf_analysis(std::ostream& s, cfg_t& cfg, const program_info& info, co static string_invariant_map to_string_invariant_map(crab::invariant_table_t& inv_table) { string_invariant_map res; - for (auto& [label, inv]: inv_table) { + for (auto& [label, inv] : inv_table) { res.insert_or_assign(label, inv.to_set()); } return res; } -std::tuple -ebpf_analyze_program_for_test(std::ostream& os, const InstructionSeq& prog, const string_invariant& entry_invariant, - const program_info& info, const ebpf_verifier_options_t& options) { +std::tuple ebpf_analyze_program_for_test(std::ostream& os, const InstructionSeq& prog, + const string_invariant& entry_invariant, + const program_info& info, + const ebpf_verifier_options_t& options) { + crab::domains::clear_global_state(); + crab::variable_t::clear_thread_local_state(); + thread_local_options = options; global_program_info = info; assert(!entry_invariant.is_bottom()); @@ -222,16 +198,13 @@ ebpf_analyze_program_for_test(std::ostream& os, const InstructionSeq& prog, cons if (entry_inv.is_bottom()) throw std::runtime_error("Entry invariant is inconsistent"); cfg_t cfg = prepare_cfg(prog, info, !options.no_simplify, false); - auto [pre_invariants, post_invariants] = crab::run_forward_analyzer(cfg, entry_inv, options.check_termination); + auto [pre_invariants, post_invariants] = crab::run_forward_analyzer(cfg, std::move(entry_inv)); checks_db report = get_analysis_report(std::cerr, cfg, pre_invariants, post_invariants); print_report(os, report, prog, false); auto pre_invariant_map = to_string_invariant_map(pre_invariants); - return { - pre_invariant_map.at(label_t::exit), - (report.total_warnings == 0) - }; + return {pre_invariant_map.at(label_t::exit), (report.total_warnings == 0)}; } /// Returned value is true if the program passes verification. diff --git a/src/ebpf_yaml.cpp b/src/ebpf_yaml.cpp index 28d8a2fa9..25b27a006 100644 --- a/src/ebpf_yaml.cpp +++ b/src/ebpf_yaml.cpp @@ -29,11 +29,11 @@ static EbpfMapType ebpf_get_map_type(uint32_t platform_specific_type) { static EbpfHelperPrototype ebpf_get_helper_prototype(int32_t n) { return {}; -}; +} static bool ebpf_is_helper_usable(int32_t n){ return false; -}; +} static void ebpf_parse_maps_section(vector& map_descriptors, const char* data, size_t map_record_size, int map_count, const struct ebpf_platform_t* platform, ebpf_verifier_options_t options) { @@ -225,15 +225,14 @@ static Diff make_diff(const T& actual, const T& expected) { }; } -std::optional run_yaml_test_case(const TestCase& _test_case, bool debug) { - TestCase test_case = _test_case; +std::optional run_yaml_test_case(TestCase test_case, bool debug) { if (debug) { test_case.options.print_failures = true; test_case.options.print_invariants = true; test_case.options.no_simplify = true; } - ebpf_context_descriptor_t context_descriptor{64, 0, 8, -1}; + ebpf_context_descriptor_t context_descriptor{64, 0, 4, -1}; EbpfProgramType program_type = make_program_type(test_case.name, &context_descriptor); program_info info{&g_platform_test, {}, program_type}; diff --git a/src/ebpf_yaml.hpp b/src/ebpf_yaml.hpp index af45ce642..183ea3daa 100644 --- a/src/ebpf_yaml.hpp +++ b/src/ebpf_yaml.hpp @@ -32,7 +32,7 @@ struct Failure { void print_failure(const Failure& failure, std::ostream& out); -std::optional run_yaml_test_case(const TestCase& test_case, bool debug = false); +std::optional run_yaml_test_case(TestCase test_case, bool debug = false); struct ConformanceTestResult { bool success{}; diff --git a/src/test/test_loop.cpp b/src/test/test_loop.cpp deleted file mode 100644 index e265baa4a..000000000 --- a/src/test/test_loop.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Prevail Verifier contributors. -// SPDX-License-Identifier: MIT -#include - -#include "ebpf_verifier.hpp" - -using namespace crab; - -TEST_CASE("Trivial loop: middle", "[sanity][loop]") { - cfg_t cfg; - - basic_block_t& entry = cfg.insert(label_t(0)); - basic_block_t& middle = cfg.insert(label_t(1)); - basic_block_t& exit = cfg.get_node(cfg.exit_label()); - - entry.insert(Bin{.op = Bin::Op::MOV, .dst = Reg{0}, .v = Imm{0}, .is64 = true}); - middle.insert(Bin{.op = Bin::Op::ADD, .dst = Reg{0}, .v = Imm{1}, .is64 = true}); - - cfg.get_node(cfg.entry_label()) >> entry; - entry >> middle; - middle >> middle; - middle >> exit; - - ebpf_verifier_options_t options{ - .check_termination=false - }; - program_info info{ - .platform = &g_ebpf_platform_linux, - .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec") - }; - bool pass = run_ebpf_analysis(std::cout, cfg, info, &options, nullptr); - REQUIRE(pass); -} diff --git a/src/test/test_termination.cpp b/src/test/test_termination.cpp deleted file mode 100644 index 6188b08a7..000000000 --- a/src/test/test_termination.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Prevail Verifier contributors. -// SPDX-License-Identifier: MIT -#include - -#include "crab/cfg.hpp" -#include "crab_verifier.hpp" -#include "config.hpp" -#include "platform.hpp" - -using namespace crab; - -TEST_CASE("Trivial infinite loop", "[loop][termination]") { - cfg_t cfg; - - basic_block_t& entry = cfg.get_node(cfg.entry_label()); - basic_block_t& middle = cfg.insert(label_t(0)); - basic_block_t& exit = cfg.get_node(cfg.exit_label()); - - entry >> middle; - middle >> middle; - middle >> exit; - - ebpf_verifier_options_t options{ - .check_termination = true, - }; - program_info info{ - .platform = &g_ebpf_platform_linux, - .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec") - }; - ebpf_verifier_stats_t stats; - bool pass = run_ebpf_analysis(std::cout, cfg, info, &options, &stats); - REQUIRE_FALSE(pass); - REQUIRE(stats.max_instruction_count == INT_MAX); - REQUIRE(stats.total_unreachable == 0); - REQUIRE(stats.total_warnings == 1); -} - -TEST_CASE("Trivial finite loop", "[loop][termination]") { - cfg_t cfg; - - basic_block_t& entry = cfg.get_node(cfg.entry_label()); - basic_block_t& start = cfg.insert(label_t(0)); - basic_block_t& middle = cfg.insert(label_t(1)); - basic_block_t& exit = cfg.get_node(cfg.exit_label()); - - Reg r{0}; - start.insert(Bin{.op = Bin::Op::MOV, .dst = r, .v = Imm{0}, .is64 = true}); - middle.insert(Assume{{.op=Condition::Op::GT, .left=r, .right=Imm{10}, .is64=true}}); - middle.insert(Bin{.op = Bin::Op::ADD, .dst = r, .v = Imm{1}, .is64 = true}); - - entry >> start; - start >> middle; - middle >> middle; - middle >> exit; - - ebpf_verifier_options_t options{ - .check_termination = true, - }; - program_info info{ - .platform = &g_ebpf_platform_linux, - .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec") - }; - ebpf_verifier_stats_t stats; - bool pass = run_ebpf_analysis(std::cout, cfg, info, &options, &stats); - REQUIRE(pass); - REQUIRE(stats.max_instruction_count == 3); - REQUIRE(stats.total_unreachable == 1); - REQUIRE(stats.total_warnings == 0); -} diff --git a/src/test/test_yaml.cpp b/src/test/test_yaml.cpp index 86f572c37..e77f0ed05 100644 --- a/src/test/test_yaml.cpp +++ b/src/test/test_yaml.cpp @@ -26,6 +26,7 @@ YAML_CASE("test-data/call.yaml") YAML_CASE("test-data/divmod.yaml") YAML_CASE("test-data/full64.yaml") YAML_CASE("test-data/jump.yaml") +YAML_CASE("test-data/loop.yaml") YAML_CASE("test-data/packet.yaml") YAML_CASE("test-data/parse.yaml") YAML_CASE("test-data/shift.yaml") diff --git a/test-data/assign.yaml b/test-data/assign.yaml index 918102ae0..4385c0ceb 100644 --- a/test-data/assign.yaml +++ b/test-data/assign.yaml @@ -219,7 +219,7 @@ pre: ["r1.ctx_offset=0", "r1.type=ctx", "r1.svalue=[1, 2147418112]", "r1.uvalue= code: : | - r2 = *(u32 *)(r1 + 8) + r2 = *(u32 *)(r1 + 4) post: - packet_size=r2.packet_offset diff --git a/test-data/loop.yaml b/test-data/loop.yaml new file mode 100644 index 000000000..643cb4b16 --- /dev/null +++ b/test-data/loop.yaml @@ -0,0 +1,268 @@ +# Copyright (c) Prevail Verifier contributors. +# SPDX-License-Identifier: MIT +--- +test-case: while loop, unsigned gte +options: ["termination"] +pre: [] + +code: + : | + r0 = 0 + : | + if r0 >= 4 goto + r0 += 1 + goto + : | + exit + +post: + - "r0.type=number" + - "r0.svalue=4" + - "r0.uvalue=4" + - "r0.svalue=r0.uvalue" + - "pc[1]=5" + - "pc[1]=r0.svalue+1" + - "pc[1]=r0.uvalue+1" +--- +test-case: until loop, unsigned leq +options: ["termination"] +pre: [] + +code: + : | + r0 = 0 + : | + r0 += 1 + if r0 <= 3 goto + : | + exit + +post: + - "r0.type=number" + - "r0.svalue=4" + - "r0.uvalue=4" + - "r0.svalue=r0.uvalue" + - "pc[1]=4" + - "pc[1]=r0.svalue" + - "pc[1]=r0.uvalue" +--- +test-case: while loop, signed gte +options: ["termination"] +pre: [] + +code: + : | + r0 = 0 + : | + if r0 s>= 4 goto + r0 += 1 + goto + : | + exit + +post: + - "r0.type=number" + - "r0.svalue=4" + - "r0.uvalue=4" + - "r0.svalue=r0.uvalue" + - "pc[1]=5" + - "pc[1]=r0.svalue+1" + - "pc[1]=r0.uvalue+1" +--- +test-case: until loop, signed leq +options: ["termination"] + +pre: [] + +code: + : | + r0 = 0 + : | + r0 += 1 + if r0 s<= 3 goto + : | + exit + +post: + - "r0.type=number" + - "r0.svalue=4" + - "r0.uvalue=4" + - "r0.svalue=r0.uvalue" + - "pc[1]=4" + - "pc[1]=r0.svalue" + - "pc[1]=r0.uvalue" +--- +test-case: loop with data dependence, unsigned leq +options: ["termination"] + +pre: ["r1.type=number", "r2.type=number", "r3.type=number"] + +code: + : | + r0 = 0 + : | + r0 += 1 + r1 += r2 + if r1 > r3 goto + if r0 <= 3 goto + : | + exit + +post: + - "r0.type=number" + - "r0.svalue=[1, 4]" + - "r0.uvalue=[1, 4]" + - "r0.svalue=r0.uvalue" + - "r1.type=number" + - "r2.type=number" + - "r3.type=number" + - "pc[1]=[1, 4]" + - "pc[1]=r0.svalue" + - "pc[1]=r0.uvalue" +--- +test-case: while loop, eq +#options: ["termination"] +pre: [] + +code: + : | + r0 = 0 + : | + if r0 == 4 goto + r0 += 1 + goto + : | + exit + +post: + - "r0.type=number" + - "r0.svalue=4" + - "r0.uvalue=4" +# - "r0.svalue=r0.uvalue" +# - "pc[1]=4" +# - "pc[1]=r0.value" +--- +test-case: until loop, neq +#options: ["termination"] + +pre: [] + +code: + : | + r0 = 0 + : | + r0 += 1 + if r0 != 9 goto + : | + exit + +post: + - "r0.type=number" + - "r0.svalue=9" + - "r0.uvalue=9" +# - "r0.svalue=r0.uvalue" +# - "pc[1]=9" +# - "pc[1]=r0.value" +--- +test-case: simple infinite loop, neq +options: ["termination"] + +pre: [] + +code: + : | + r0 = 0 + : | + r0 += 2 + if r0 != 9 goto + : | + exit + +post: + - "r0.type=number" + - "r0.svalue=9" + - "r0.uvalue=9" +# - "r0.svalue=r0.uvalue" + - "pc[1]=[1, +oo]" + +messages: + - "Could not prove termination." + +--- +test-case: realistic forward loop +options: ["termination"] + +pre: [ + "meta_offset=[-4098, 0]", + "packet_size=[0, 65534]", + "r1.ctx_offset=0", "r1.svalue=[1, 2147418112]", "r1.type=ctx", +] + +code: + : | + r0 = 0 + r2 = *(u32 *)(r1 + 4) + r1 = *(u32 *)(r1 + 0) + assume r2 != r1 + r2 -= r1 + r3 = 0 + r0 = 0 +# r2 <<= 32; this fails with "11: Upper bound must be at most packet_size (valid_access(r4.offset, width=1) for read)" +# r2 >>= 32 + : | + r4 = r1 + r4 += r3 + r4 = *(u8 *)(r4 + 0) + r0 += r4 + r3 += 1 + if r4 == r3 goto + assume r2 > r3 + goto + : | + exit + +post: + - meta_offset=[-oo, 0] + - packet_size=[1, 65534] + - packet_size=r2.svalue + - packet_size=r2.uvalue + - pc[7]-packet_size<=0 + - pc[7]-r2.svalue<=0 + - pc[7]-r2.uvalue<=0 + - pc[7]=[1, 255] + - pc[7]=r3.svalue + - pc[7]=r3.uvalue + - pc[7]=r4.svalue + - pc[7]=r4.uvalue + - r0.type=number + - r1.packet_offset=0 + - r1.svalue=[4098, 2147418112] + - r1.type=packet + - r2.svalue=[1, 65534] + - r2.svalue=r2.uvalue + - r2.type=number + - r2.uvalue=[1, 65534] + - r3.svalue-packet_size<=0 + - r3.svalue-r2.svalue<=0 + - r3.svalue-r2.uvalue<=0 + - r3.svalue=[1, 255] + - r3.svalue=r3.uvalue + - r3.svalue=r4.svalue + - r3.svalue=r4.uvalue + - r3.type=number + - r3.uvalue-packet_size<=0 + - r3.uvalue-r2.svalue<=0 + - r3.uvalue-r2.uvalue<=0 + - r3.uvalue=[1, 255] + - r3.uvalue=r4.svalue + - r3.uvalue=r4.uvalue + - r4.svalue-packet_size<=0 + - r4.svalue-r2.svalue<=0 + - r4.svalue-r2.uvalue<=0 + - r4.svalue=[1, 255] + - r4.svalue=r4.uvalue + - r4.type=number + - r4.uvalue-packet_size<=0 + - r4.uvalue-r2.svalue<=0 + - r4.uvalue-r2.uvalue<=0 + - r4.uvalue=[1, 255] From 59244394ae38e1b3241e945c28d0a122ec0714ef Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Mon, 20 Nov 2023 10:36:44 -0800 Subject: [PATCH 008/373] Add support for signed division and modulo instructions Fixes #523 Signed-off-by: Dave Thaler --- src/asm_ostream.cpp | 2 + src/asm_parse.cpp | 1 + src/asm_syntax.hpp | 5 +- src/asm_unmarshal.cpp | 14 +- src/assertions.cpp | 5 +- src/crab/ebpf_domain.cpp | 20 +- src/crab/interval.cpp | 51 ++- src/crab/interval.hpp | 10 +- src/crab/split_dbm.cpp | 4 +- src/test/test_yaml.cpp | 3 +- test-data/sdivmod.yaml | 571 ++++++++++++++++++++++++ test-data/{divmod.yaml => udivmod.yaml} | 0 12 files changed, 668 insertions(+), 18 deletions(-) create mode 100644 test-data/sdivmod.yaml rename test-data/{divmod.yaml => udivmod.yaml} (100%) diff --git a/src/asm_ostream.cpp b/src/asm_ostream.cpp index 2ecdda978..43ff6491e 100644 --- a/src/asm_ostream.cpp +++ b/src/asm_ostream.cpp @@ -60,7 +60,9 @@ std::ostream& operator<<(std::ostream& os, Bin::Op op) { case Op::SUB: return os << "-"; case Op::MUL: return os << "*"; case Op::UDIV: return os << "/"; + case Op::SDIV: return os << "s/"; case Op::UMOD: return os << "%"; + case Op::SMOD: return os << "s%"; case Op::OR: return os << "|"; case Op::AND: return os << "&"; case Op::LSH: return os << "<<"; diff --git a/src/asm_parse.cpp b/src/asm_parse.cpp index 50d98f802..7ce6428ed 100644 --- a/src/asm_parse.cpp +++ b/src/asm_parse.cpp @@ -58,6 +58,7 @@ static const std::map str_to_binop = { {"", Bin::Op::MOV}, {"+", Bin::Op::ADD}, {"-", Bin::Op::SUB}, {"*", Bin::Op::MUL}, {"/", Bin::Op::UDIV}, {"%", Bin::Op::UMOD}, {"|", Bin::Op::OR}, {"&", Bin::Op::AND}, {"<<", Bin::Op::LSH}, {">>", Bin::Op::RSH}, {"s>>", Bin::Op::ARSH}, {"^", Bin::Op::XOR}, + {"s/", Bin::Op::SDIV}, {"s%", Bin::Op::SMOD}, }; static const std::map str_to_unop = { diff --git a/src/asm_syntax.hpp b/src/asm_syntax.hpp index 09da5b6a4..4121bc9e0 100644 --- a/src/asm_syntax.hpp +++ b/src/asm_syntax.hpp @@ -85,6 +85,8 @@ struct Bin { RSH, ARSH, XOR, + SDIV, + SMOD, }; Op op; @@ -262,6 +264,7 @@ struct Addable { // Condition check whether a register contains a non-zero number. struct ValidDivisor { Reg reg; + bool is_signed{}; }; enum class AccessType { @@ -380,7 +383,7 @@ DECLARE_EQ2(TypeConstraint, reg, types) DECLARE_EQ2(ValidSize, reg, can_be_zero) DECLARE_EQ2(Comparable, r1, r2) DECLARE_EQ2(Addable, ptr, num) -DECLARE_EQ1(ValidDivisor, reg) +DECLARE_EQ2(ValidDivisor, reg, is_signed) DECLARE_EQ2(ValidStore, mem, val) DECLARE_EQ5(ValidAccess, reg, offset, width, or_null, access_type) DECLARE_EQ3(ValidMapKeyValue, access_reg, map_fd_reg, key) diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index 4a9609d46..5cd3fdc99 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -91,13 +91,23 @@ struct Unmarshaller { case INST_ALU_OP_ADD: return Bin::Op::ADD; case INST_ALU_OP_SUB: return Bin::Op::SUB; case INST_ALU_OP_MUL: return Bin::Op::MUL; - case INST_ALU_OP_DIV: return Bin::Op::UDIV; + case INST_ALU_OP_DIV: + switch (inst.offset) { + case 0: return Bin::Op::UDIV; + case 1: return Bin::Op::SDIV; + default: throw InvalidInstruction{pc, "invalid ALU op 0x30"}; + } case INST_ALU_OP_OR: return Bin::Op::OR; case INST_ALU_OP_AND: return Bin::Op::AND; case INST_ALU_OP_LSH: return Bin::Op::LSH; case INST_ALU_OP_RSH: return Bin::Op::RSH; case INST_ALU_OP_NEG: return Un::Op::NEG; - case INST_ALU_OP_MOD: return Bin::Op::UMOD; + case INST_ALU_OP_MOD: + switch (inst.offset) { + case 0: return Bin::Op::UMOD; + case 1: return Bin::Op::SMOD; + default: throw InvalidInstruction{pc, "invalid ALU op 0x90"}; + } case INST_ALU_OP_XOR: return Bin::Op::XOR; case INST_ALU_OP_MOV: return Bin::Op::MOV; case INST_ALU_OP_ARSH: diff --git a/src/assertions.cpp b/src/assertions.cpp index a76d5906d..4dd791470 100644 --- a/src/assertions.cpp +++ b/src/assertions.cpp @@ -209,9 +209,12 @@ class AssertExtractor { } case Bin::Op::UDIV: case Bin::Op::UMOD: + case Bin::Op::SDIV: + case Bin::Op::SMOD: if (std::holds_alternative(ins.v)) { auto src = reg(ins.v); - return {Assert{TypeConstraint{ins.dst, TypeGroup::number}}, Assert{ValidDivisor{src}}}; + bool is_signed = (ins.op == Bin::Op::SDIV || ins.op == Bin::Op::SMOD); + return {Assert{TypeConstraint{ins.dst, TypeGroup::number}}, Assert{ValidDivisor{src, is_signed}}}; } else { return {Assert{TypeConstraint{ins.dst, TypeGroup::number}}}; } diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index 072ffad67..62667c88e 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -1527,8 +1527,8 @@ void ebpf_domain_t::operator()(const ValidDivisor& s) { if (!type_inv.implies_type(m_inv, type_is_pointer(reg), type_is_number(s.reg))) require(m_inv, linear_constraint_t::FALSE(), "Only numbers can be used as divisors"); if (!thread_local_options.allow_division_by_zero) { - // Division is an unsigned operation. There are no eBPF instructions for signed division. - require(m_inv, reg.uvalue != 0, "Possible division by zero"); + auto v = s.is_signed ? reg.svalue : reg.uvalue; + require(m_inv, v != 0, "Possible division by zero"); } } @@ -2435,6 +2435,14 @@ void ebpf_domain_t::operator()(const Bin& bin) { urem(dst.svalue, dst.uvalue, imm, finite_width); havoc_offsets(bin.dst); break; + case Bin::Op::SDIV: + sdiv(dst.svalue, dst.uvalue, imm, finite_width); + havoc_offsets(bin.dst); + break; + case Bin::Op::SMOD: + srem(dst.svalue, dst.uvalue, imm, finite_width); + havoc_offsets(bin.dst); + break; case Bin::Op::OR: bitwise_or(dst.svalue, dst.uvalue, imm); havoc_offsets(bin.dst); @@ -2581,6 +2589,14 @@ void ebpf_domain_t::operator()(const Bin& bin) { urem(dst.svalue, dst.uvalue, src.uvalue, finite_width); havoc_offsets(bin.dst); break; + case Bin::Op::SDIV: + sdiv(dst.svalue, dst.uvalue, src.svalue, finite_width); + havoc_offsets(bin.dst); + break; + case Bin::Op::SMOD: + srem(dst.svalue, dst.uvalue, src.svalue, finite_width); + havoc_offsets(bin.dst); + break; case Bin::Op::OR: bitwise_or(dst.svalue, dst.uvalue, src.uvalue, finite_width); havoc_offsets(bin.dst); diff --git a/src/crab/interval.cpp b/src/crab/interval.cpp index dd197e9ba..5aa333004 100644 --- a/src/crab/interval.cpp +++ b/src/crab/interval.cpp @@ -51,7 +51,54 @@ interval_t interval_t::operator/(const interval_t& x) const { } } -// Unsigned division +// Signed division. +interval_t interval_t::SDiv(const interval_t& x) const { + if (is_bottom() || x.is_bottom()) { + return bottom(); + } else { + // Divisor is a singleton: + // the linear interval solver can perform many divisions where + // the divisor is a singleton interval. We optimize for this case. + std::optional n = x.singleton(); + if (n && n->fits_cast_to_int64()) { + number_t c{n->cast_to_sint64()}; + if (c == 1) { + return *this; + } else if (c != 0) { + return interval_t(_lb / bound_t{c}, _ub / bound_t{c}); + } else { + // The eBPF ISA defines division by 0 as resulting in 0. + return interval_t(number_t(0)); + } + } + // Divisor is not a singleton + using z_interval = interval_t; + if (x[0]) { + // The divisor contains 0. + z_interval l(x._lb, z_bound(-1)); + z_interval u(z_bound(1), x._ub); + return (SDiv(l) | SDiv(u) | z_interval(number_t(0))); + } else if (operator[](0)) { + // The dividend contains 0. + z_interval l(_lb, z_bound(-1)); + z_interval u(z_bound(1), _ub); + return (l.SDiv(x) | u.SDiv(x) | z_interval(number_t(0))); + } else { + // Neither the dividend nor the divisor contains 0 + z_interval a = + (_ub < number_t{0}) + ? (*this + ((x._ub < number_t{0}) ? (x + z_interval(number_t(1))) : (z_interval(number_t(1)) - x))) + : *this; + bound_t ll = a._lb / x._lb; + bound_t lu = a._lb / x._ub; + bound_t ul = a._ub / x._lb; + bound_t uu = a._ub / x._ub; + return interval_t(bound_t::min(ll, lu, ul, uu), bound_t::max(ll, lu, ul, uu)); + } + } +} + +// Unsigned division. interval_t interval_t::UDiv(const interval_t& x) const { if (is_bottom() || x.is_bottom()) { return bottom(); @@ -97,7 +144,7 @@ interval_t interval_t::UDiv(const interval_t& x) const { } } -// Signed remainder (modulo). eBPF has no instruction for this. +// Signed remainder (modulo). interval_t interval_t::SRem(const interval_t& x) const { // note that the sign of the divisor does not matter diff --git a/src/crab/interval.hpp b/src/crab/interval.hpp index 5596d8d17..bf28cd2f5 100644 --- a/src/crab/interval.hpp +++ b/src/crab/interval.hpp @@ -121,13 +121,7 @@ class bound_t final { } else if (is_finite() && x.is_finite()) { return bound_t(false, _n / x._n); } else if (is_finite() && x.is_infinite()) { - if (_n > 0) { - return x; - } else if (_n == 0) { - return *this; - } else { - return x.operator-(); - } + return number_t{0}; } else if (is_infinite() && x.is_finite()) { if (x._n > 0) { return *this; @@ -423,6 +417,8 @@ class interval_t final { // division and remainder operations + [[nodiscard]] interval_t SDiv(const interval_t& x) const; + [[nodiscard]] interval_t UDiv(const interval_t& x) const; [[nodiscard]] interval_t SRem(const interval_t& x) const; diff --git a/src/crab/split_dbm.cpp b/src/crab/split_dbm.cpp index e6cce3021..8336ba40e 100644 --- a/src/crab/split_dbm.cpp +++ b/src/crab/split_dbm.cpp @@ -951,7 +951,7 @@ void SplitDBM::apply(arith_binop_t op, variable_t x, variable_t y, variable_t z, case arith_binop_t::SUB: assign(x, linear_expression_t(y).subtract(z)); break; // For the rest of operations, we fall back on intervals. case arith_binop_t::MUL: set(x, get_interval(y, finite_width) * get_interval(z, finite_width)); break; - case arith_binop_t::SDIV: set(x, get_interval(y, finite_width) / get_interval(z, finite_width)); break; + case arith_binop_t::SDIV: set(x, get_interval(y, finite_width).SDiv(get_interval(z, finite_width))); break; case arith_binop_t::UDIV: set(x, get_interval(y, finite_width).UDiv(get_interval(z, finite_width))); break; case arith_binop_t::SREM: set(x, get_interval(y, finite_width).SRem(get_interval(z, finite_width))); break; case arith_binop_t::UREM: set(x, get_interval(y, finite_width).URem(get_interval(z, finite_width))); break; @@ -969,7 +969,7 @@ void SplitDBM::apply(arith_binop_t op, variable_t x, variable_t y, const number_ case arith_binop_t::SUB: assign(x, linear_expression_t(y).subtract(k)); break; case arith_binop_t::MUL: assign(x, linear_expression_t(k, y)); break; // For the rest of operations, we fall back on intervals. - case arith_binop_t::SDIV: set(x, get_interval(y, finite_width) / interval_t(k)); break; + case arith_binop_t::SDIV: set(x, get_interval(y, finite_width).SDiv(interval_t(k))); break; case arith_binop_t::UDIV: set(x, get_interval(y, finite_width).UDiv(interval_t(k))); break; case arith_binop_t::SREM: set(x, get_interval(y, finite_width).SRem(interval_t(k))); break; case arith_binop_t::UREM: set(x, get_interval(y, finite_width).URem(interval_t(k))); break; diff --git a/src/test/test_yaml.cpp b/src/test/test_yaml.cpp index e77f0ed05..44c89156f 100644 --- a/src/test/test_yaml.cpp +++ b/src/test/test_yaml.cpp @@ -23,7 +23,8 @@ YAML_CASE("test-data/add.yaml") YAML_CASE("test-data/assign.yaml") YAML_CASE("test-data/bitop.yaml") YAML_CASE("test-data/call.yaml") -YAML_CASE("test-data/divmod.yaml") +YAML_CASE("test-data/udivmod.yaml") +YAML_CASE("test-data/sdivmod.yaml") YAML_CASE("test-data/full64.yaml") YAML_CASE("test-data/jump.yaml") YAML_CASE("test-data/loop.yaml") diff --git a/test-data/sdivmod.yaml b/test-data/sdivmod.yaml new file mode 100644 index 000000000..8da36fb5b --- /dev/null +++ b/test-data/sdivmod.yaml @@ -0,0 +1,571 @@ +# Copyright (c) Prevail Verifier contributors. +# SPDX-License-Identifier: MIT +--- +test-case: integer divided by non-integer + +pre: ["r1.type=number", "r1.svalue=6", "r1.uvalue=6", + "r2.svalue=1", "r2.uvalue=1"] + +code: + : | + r1 s/= r2 + +post: + - r1.type=number + - r1.svalue=6 + - r1.uvalue=6 + - r2.svalue=1 + - r2.uvalue=1 + +messages: + - "0: Only numbers can be used as divisors (r2 != 0)" +--- +test-case: non-zero divided by zero immediate + +pre: ["r1.type=number", "r1.svalue=6", "r1.uvalue=6"] + +code: + : | + r1 s/= 0 + +post: + - r1.type=number + - r1.svalue=0 + - r1.uvalue=0 +--- +test-case: zero divided by zero immediate + +pre: ["r1.type=number", "r1.svalue=0", "r1.uvalue=0"] + +code: + : | + r1 s/= 0 + +post: + - r1.type=number + - r1.svalue=0 + - r1.uvalue=0 +--- +test-case: non-zero divided by zero register + +pre: ["r1.type=number", "r1.svalue=6", "r1.uvalue=6", + "r2.type=number", "r2.svalue=0", "r2.uvalue=0"] + +options: ["!allow_division_by_zero"] + +code: + : | + r1 s/= r2 + +post: + - r1.type=number + - r1.svalue=0 + - r1.uvalue=0 + - r2.type=number + - r2.svalue=0 + - r2.uvalue=0 + +messages: + - "0: Possible division by zero (r2 != 0)" +--- +test-case: non-zero divided by zero register without warning + +pre: ["r1.type=number", "r1.svalue=6", "r1.uvalue=6", + "r2.type=number", "r2.svalue=0", "r2.uvalue=0"] + +code: + : | + r1 s/= r2 + +post: + - r1.type=number + - r1.svalue=0 + - r1.uvalue=0 + - r2.type=number + - r2.svalue=0 + - r2.uvalue=0 +--- +test-case: zero divided by zero register + +pre: ["r1.type=number", "r1.svalue=0", "r1.uvalue=0", + "r2.type=number", "r2.svalue=0", "r2.uvalue=0"] + +options: ["!allow_division_by_zero"] + +code: + : | + r1 s/= r2 + +post: + - r1.type=number + - r1.svalue=0 + - r1.uvalue=0 + - r2.type=number + - r2.svalue=0 + - r2.uvalue=0 + +messages: + - "0: Possible division by zero (r2 != 0)" +--- +test-case: non-zero divided by possibly zero register + +pre: ["r1.type=number", "r1.svalue=6", "r1.uvalue=6", + "r2.type=number", "r2.svalue=[-5, 5]"] + +options: ["!allow_division_by_zero"] + +code: + : | + r1 s/= r2 ; this could divide by 0 + +post: + - r1.type=number + - r1.svalue=[-6, 6] + - r2.type=number + - r2.svalue=[-5, 5] + +messages: + - "0: Possible division by zero (r2 != 0)" +--- +test-case: zero divided by possibly zero register + +pre: ["r1.type=number", "r1.svalue=0", "r1.uvalue=0", + "r2.type=number", "r2.svalue=[-5, 5]"] + +options: ["!allow_division_by_zero"] + +code: + : | + r1 s/= r2 ; this could divide by 0 but ok to set to 0 + +post: + - r1.type=number + - r1.svalue=0 + - r1.uvalue=0 + - r2.type=number + - r2.svalue=[-5, 5] + +messages: + - "0: Possible division by zero (r2 != 0)" +--- +test-case: non-zero divided by possibly zero register 2 + +pre: ["r1.type=number", "r1.svalue=6", "r1.uvalue=6", + "r2.type=number", "r2.svalue=[0, 5]", "r2.uvalue=[0, 5]", "r2.svalue=r2.uvalue"] + +options: ["!allow_division_by_zero"] + +code: + : | + r1 s/= r2 ; this could divide by 0 + +post: + - r1.type=number + - r1.svalue=[0, 6] + - r1.uvalue=[0, 6] + - r1.svalue=r1.uvalue + - r2.type=number + - r2.svalue=[0, 5] + - r2.uvalue=[0, 5] + - r2.svalue=r2.uvalue + +messages: + - "0: Possible division by zero (r2 != 0)" +--- +test-case: zero divided by possibly zero register 2 + +options: ["!allow_division_by_zero"] + +pre: ["r1.type=number", "r1.svalue=0", "r1.uvalue=0", + "r2.type=number", "r2.svalue=[-5, 0]"] + +code: + : | + r1 s/= r2 ; this could divide by 0 but ok to set to 0 + +post: + - r1.type=number + - r1.svalue=0 + - r1.uvalue=0 + - r2.type=number + - r2.svalue=[-5, 0] + +messages: + - "0: Possible division by zero (r2 != 0)" +--- +test-case: non-zero divided by undefined value register + +options: ["!allow_division_by_zero"] + +pre: ["r1.type=number", "r1.svalue=6", "r1.uvalue=6", + "r2.type=number"] + +code: + : | + r1 s/= r2 ; this could divide by 0 + +post: + - r1.type=number + - r1.svalue=[-6, 6] + - r2.type=number + +messages: + - "0: Possible division by zero (r2 != 0)" +--- +test-case: zero divided by undefined value register + +options: ["!allow_division_by_zero"] + +pre: ["r1.type=number", "r1.svalue=0", "r1.uvalue=0", + "r2.type=number"] + +code: + : | + r1 s/= r2 ; this could divide by 0 + +post: + - r1.type=number + - r1.svalue=0 + - r1.uvalue=0 + - r2.type=number + +messages: + - "0: Possible division by zero (r2 != 0)" +--- +test-case: undefined value register divided by signed non-zero range + +pre: ["r1.type=number", "r1.svalue=[-9223372036854775808, -1]", + "r2.type=number"] + +code: + : | + r2 s/= r1 + +post: + - r1.type=number + - r1.svalue=[-9223372036854775808, -1] + - r2.type=number +--- +test-case: non-zero modulo zero immediate + +pre: ["r1.type=number", "r1.svalue=-6", "r1.uvalue=18446744073709551610"] + +code: + : | + r1 s%= 0 + +post: + - r1.type=number + - r1.svalue=-6 + - r1.uvalue=18446744073709551610 +--- +test-case: zero modulo zero immediate + +pre: ["r1.type=number", "r1.svalue=0", "r1.uvalue=0"] + +code: + : | + r1 s%= 0 + +post: + - r1.type=number + - r1.svalue=0 + - r1.uvalue=0 +--- +test-case: non-zero modulo zero register + +options: ["!allow_division_by_zero"] + +pre: ["r1.type=number", "r1.svalue=-6", "r1.uvalue=18446744073709551610", + "r2.type=number", "r2.svalue=0", "r2.uvalue=0"] + +code: + : | + r1 s%= r2 + +post: + - r1.type=number + - r1.svalue=-6 + - r1.uvalue=18446744073709551610 + - r2.type=number + - r2.svalue=0 + - r2.uvalue=0 + +messages: + - "0: Possible division by zero (r2 != 0)" +--- +test-case: non-zero modulo zero register without warning + +pre: ["r1.type=number", "r1.svalue=-6", "r1.uvalue=18446744073709551610", + "r2.type=number", "r2.svalue=0", "r2.uvalue=0"] + +code: + : | + r1 s%= r2 + +post: + - r1.type=number + - r1.svalue=-6 + - r1.uvalue=18446744073709551610 + - r2.type=number + - r2.svalue=0 + - r2.uvalue=0 +--- +test-case: zero modulo zero register + +options: ["!allow_division_by_zero"] + +pre: ["r1.type=number", "r1.svalue=0", "r1.uvalue=0", + "r2.type=number", "r2.svalue=0", "r2.uvalue=0"] + +code: + : | + r1 s%= r2 + +post: + - r1.type=number + - r1.svalue=0 + - r1.uvalue=0 + - r2.type=number + - r2.svalue=0 + - r2.uvalue=0 + +messages: + - "0: Possible division by zero (r2 != 0)" +--- +test-case: non-zero modulo possibly zero register + +options: ["!allow_division_by_zero"] + +pre: ["r1.type=number", "r1.svalue=6", "r1.uvalue=6", + "r2.type=number", "r2.svalue=[-5, 5]"] + +code: + : | + r1 s%= r2 ; this could do modulo 0 so could set r1 = r2 + +post: + - r1.type=number + - r1.svalue=[0, 6] + - r1.uvalue=[0, 6] + - r1.svalue=r1.uvalue + - r2.type=number + - r2.svalue=[-5, 5] + +messages: + - "0: Possible division by zero (r2 != 0)" +--- +test-case: zero modulo possibly zero register + +options: ["!allow_division_by_zero"] + +pre: ["r1.type=number", "r1.svalue=0", "r1.uvalue=0", + "r2.type=number", "r2.svalue=[-5, 5]"] + +code: + : | + r1 s%= r2 ; this could do modulo 0 so could set r1 = r2 + +post: + - r1.type=number + - r1.svalue=0 + - r1.uvalue=0 + - r2.type=number + - r2.svalue=[-5, 5] + +messages: + - "0: Possible division by zero (r2 != 0)" +--- +test-case: non-zero modulo possibly zero register 2 + +options: ["!allow_division_by_zero"] + +pre: ["r1.type=number", "r1.svalue=6", "r1.uvalue=6", + "r2.type=number", "r2.svalue=[0, 5]", "r2.uvalue=[0, 5]", "r2.svalue=r2.uvalue"] + +code: + : | + r1 s%= r2 ; this could do modulo 0 so could set r1 = r2 + +post: + - r1.type=number + - r1.svalue=[0, 6] + - r1.uvalue=[0, 6] + - r1.svalue=r1.uvalue + - r2.type=number + - r2.svalue=[0, 5] + - r2.uvalue=[0, 5] + - r2.svalue=r2.uvalue + +messages: + - "0: Possible division by zero (r2 != 0)" +--- +test-case: zero modulo possibly zero register 2 + +options: ["!allow_division_by_zero"] + +pre: ["r1.type=number", "r1.svalue=0", "r1.uvalue=0", + "r2.type=number", "r2.svalue=[-5, 0]"] + +code: + : | + r1 s%= r2 ; this could do modulo 0 so could set r1 = r2 + +post: + - r1.type=number + - r1.svalue=0 + - r1.uvalue=0 + - r2.type=number + - r2.svalue=[-5, 0] + +messages: + - "0: Possible division by zero (r2 != 0)" +--- +test-case: non-zero modulo undefined value register + +options: ["!allow_division_by_zero"] + +pre: ["r1.type=number", "r1.svalue=6", "r1.uvalue=6", + "r2.type=number"] + +code: + : | + r1 s%= r2 ; this could be modulo 0 + +post: + - r1.type=number + - r1.svalue=[0, 6] + - r1.uvalue=[0, 6] + - r1.svalue=r1.uvalue + - r2.type=number + +messages: + - "0: Possible division by zero (r2 != 0)" +--- +test-case: zero modulo undefined value register + +options: ["!allow_division_by_zero"] + +pre: ["r1.type=number", "r1.svalue=0", "r1.uvalue=0", + "r2.type=number"] + +code: + : | + r1 s%= r2 ; this could be modulo 0 + +post: + - r1.type=number + - r1.svalue=0 + - r1.uvalue=0 + - r2.type=number + +messages: + - "0: Possible division by zero (r2 != 0)" +--- +test-case: positive modulo positive range + +pre: ["r1.type=number", "r1.svalue=6", "r1.uvalue=6", + "r2.type=number", "r2.svalue=[1, 5]", "r2.uvalue=[1, 5]"] + +code: + : | + r1 s%= r2 + +post: + - r1.type=number + - r1.svalue=[0, 4] + - r1.uvalue=[0, 4] + - r1.svalue=r1.uvalue + - r2.type=number + - r2.svalue=[1, 5] + - r2.uvalue=[1, 5] +--- +test-case: negative modulo positive + +pre: ["r1.type=number", "r1.svalue=-13", "r1.uvalue=18446744073709551603"] + +code: + : | + r1 s%= 4 + +post: + - r1.type=number + - r1.svalue=-1 + - r1.uvalue=18446744073709551615 +--- +test-case: positive modulo negative + +pre: ["r1.type=number", "r1.svalue=13", "r1.uvalue=13"] + +code: + : | + r1 s%= -3 + +post: + - r1.type=number + - r1.svalue=1 + - r1.uvalue=1 +--- +test-case: positive modulo negative range + +pre: ["r1.type=number", "r1.svalue=13", "r1.uvalue=13", + "r2.type=number", "r2.svalue=[-3, -2]", "r2.uvalue=[18446744073709551613, 18446744073709551614]"] + +code: + : | + r1 s%= r2 + +post: + - r1.type=number + - r1.svalue=[0, 2] + - r1.uvalue=[0, 2] + - r1.svalue=r1.uvalue + - r2.type=number + - r2.svalue=[-3, -2] + - r2.uvalue=[18446744073709551613, 18446744073709551614] +--- +test-case: negative modulo negative + +pre: ["r1.type=number", "r1.svalue=-13", "r1.uvalue=18446744073709551603"] + +code: + : | + r1 s%= -3 + +post: + - r1.type=number + - r1.svalue=-1 + - r1.uvalue=18446744073709551615 +--- +test-case: negative modulo negative range + +pre: ["r1.type=number", "r1.svalue=-13", "r1.uvalue=18446744073709551603", + "r2.type=number", "r2.svalue=[-3, -2]", "r2.uvalue=[18446744073709551613, 18446744073709551614]"] + +code: + : | + r1 s%= r2 + +post: + - r1.type=number + - r1.svalue=[-2, 0] + - r2.type=number + - r2.svalue=[-3, -2] + - r2.uvalue=[18446744073709551613, 18446744073709551614] +--- +test-case: smaller modulo larger + +pre: ["r1.type=number", "r1.svalue=6", "r1.uvalue=6", + "r2.type=number", "r2.svalue=[7, 10]", "r2.uvalue=[7, 10]"] + +code: + : | + r1 s%= r2 + +post: + - r1.type=number + - r1.svalue=6 + - r1.uvalue=6 + - r2.type=number + - r2.svalue=[7, 10] + - r2.uvalue=[7, 10] diff --git a/test-data/divmod.yaml b/test-data/udivmod.yaml similarity index 100% rename from test-data/divmod.yaml rename to test-data/udivmod.yaml From fbe8d16e7c00552212fbff5001c8b4c950b99d14 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 28 Oct 2023 16:22:25 +0000 Subject: [PATCH 009/373] Bump external/ELFIO from `73a2410` to `eb68829` Bumps [external/ELFIO](https://github.com/serge1/ELFIO) from `73a2410` to `eb68829`. - [Release notes](https://github.com/serge1/ELFIO/releases) - [Commits](https://github.com/serge1/ELFIO/compare/73a241079a040710481e9b840248ec0425e46c7c...eb6882952c86e28e9fc0ee5a668d2180ddcefbb4) --- updated-dependencies: - dependency-name: external/ELFIO dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/ELFIO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/ELFIO b/external/ELFIO index 73a241079..eb6882952 160000 --- a/external/ELFIO +++ b/external/ELFIO @@ -1 +1 @@ -Subproject commit 73a241079a040710481e9b840248ec0425e46c7c +Subproject commit eb6882952c86e28e9fc0ee5a668d2180ddcefbb4 From e89dc2ecc9a3fff4df7e606c699e801dcfce6f2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 25 Nov 2023 16:11:08 +0000 Subject: [PATCH 010/373] Bump external/ELFIO from `eb68829` to `4bc17b6` Bumps [external/ELFIO](https://github.com/serge1/ELFIO) from `eb68829` to `4bc17b6`. - [Release notes](https://github.com/serge1/ELFIO/releases) - [Commits](https://github.com/serge1/ELFIO/compare/eb6882952c86e28e9fc0ee5a668d2180ddcefbb4...4bc17b6b81e4348a9428beac64a7bb5931c88a88) --- updated-dependencies: - dependency-name: external/ELFIO dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/ELFIO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/ELFIO b/external/ELFIO index eb6882952..4bc17b6b8 160000 --- a/external/ELFIO +++ b/external/ELFIO @@ -1 +1 @@ -Subproject commit eb6882952c86e28e9fc0ee5a668d2180ddcefbb4 +Subproject commit 4bc17b6b81e4348a9428beac64a7bb5931c88a88 From 40f20b679f759c6acb108993c4f7367c4587ea16 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Dec 2023 16:10:59 +0000 Subject: [PATCH 011/373] Bump github/codeql-action from 2 to 3 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v2...v3) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f33915f9b..0522fc057 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -41,7 +41,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -70,4 +70,4 @@ jobs: cmake --build build -j $(nproc) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 From 86e82a7ff57708ada439dbbc8438006144100fba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Dec 2023 16:58:04 +0000 Subject: [PATCH 012/373] Bump external/ELFIO from `4bc17b6` to `c5e56b8` Bumps [external/ELFIO](https://github.com/serge1/ELFIO) from `4bc17b6` to `c5e56b8`. - [Release notes](https://github.com/serge1/ELFIO/releases) - [Commits](https://github.com/serge1/ELFIO/compare/4bc17b6b81e4348a9428beac64a7bb5931c88a88...c5e56b8c505b084153971f64dfd2640a41f60b49) --- updated-dependencies: - dependency-name: external/ELFIO dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/ELFIO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/ELFIO b/external/ELFIO index 4bc17b6b8..c5e56b8c5 160000 --- a/external/ELFIO +++ b/external/ELFIO @@ -1 +1 @@ -Subproject commit 4bc17b6b81e4348a9428beac64a7bb5931c88a88 +Subproject commit c5e56b8c505b084153971f64dfd2640a41f60b49 From 0169e06066bec10ac372ee9e8514424b2a02de4b Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Sat, 23 Dec 2023 14:15:06 -0800 Subject: [PATCH 013/373] Make ALU operations check for invalid register values makeMemOp() already had a check but makeAluOp() didn't. Fixes #505 Signed-off-by: Dave Thaler --- src/asm_unmarshal.cpp | 2 ++ src/test/test_marshal.cpp | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index 5cd3fdc99..e85885857 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -249,6 +249,8 @@ struct Unmarshaller { auto makeAluOp(size_t pc, ebpf_inst inst) -> Instruction { if (inst.dst == R10_STACK_POINTER) throw InvalidInstruction(pc, "Invalid target r10"); + if (inst.dst > R10_STACK_POINTER || inst.src > R10_STACK_POINTER) + throw InvalidInstruction(pc, "Bad register"); return std::visit(overloaded{[&](Un::Op op) -> Instruction { return Un{.op = op, .dst = Reg{inst.dst}, .is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64}; }, [&](Bin::Op op) -> Instruction { Bin res{ diff --git a/src/test/test_marshal.cpp b/src/test/test_marshal.cpp index abf58f2bd..8be56961e 100644 --- a/src/test/test_marshal.cpp +++ b/src/test/test_marshal.cpp @@ -208,6 +208,12 @@ TEST_CASE("disasm_marshal_Mem", "[disasm][marshal]") { TEST_CASE("fail unmarshal", "[disasm][marshal]") { check_unmarshal_fail(ebpf_inst{.opcode = ((INST_MEM << 5) | INST_SIZE_B | INST_CLS_LDX), .dst = 11, .imm = 8}, "0: Bad register\n"); + check_unmarshal_fail(ebpf_inst{.opcode = ((INST_MEM << 5) | INST_SIZE_B | INST_CLS_LDX), .dst = 1, .src = 11}, + "0: Bad register\n"); + check_unmarshal_fail(ebpf_inst{.opcode = (INST_ALU_OP_MOV | INST_SRC_IMM | INST_CLS_ALU), .dst = 11, .imm = 8}, + "0: Bad register\n"); + check_unmarshal_fail(ebpf_inst{.opcode = (INST_ALU_OP_MOV | INST_SRC_REG | INST_CLS_ALU), .dst = 1, .src = 11}, + "0: Bad register\n"); check_unmarshal_fail(ebpf_inst{.opcode = ((INST_MEM << 5) | INST_SIZE_W | INST_CLS_LD)}, "0: plain LD\n"); check_unmarshal_fail(ebpf_inst{.opcode = INST_ALU_OP_END | INST_END_LE | INST_CLS_ALU, .dst = 1, .imm = 8}, "0: invalid endian immediate\n"); From 28f6f6984c5e23caca868f738f9ca3348a889ae6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 23 Dec 2023 16:45:58 +0000 Subject: [PATCH 014/373] Bump external/ELFIO from `c5e56b8` to `2b457dd` Bumps [external/ELFIO](https://github.com/serge1/ELFIO) from `c5e56b8` to `2b457dd`. - [Release notes](https://github.com/serge1/ELFIO/releases) - [Commits](https://github.com/serge1/ELFIO/compare/c5e56b8c505b084153971f64dfd2640a41f60b49...2b457dd5d90018172a4de12a67cc6869c51a2c0a) --- updated-dependencies: - dependency-name: external/ELFIO dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/ELFIO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/ELFIO b/external/ELFIO index c5e56b8c5..2b457dd5d 160000 --- a/external/ELFIO +++ b/external/ELFIO @@ -1 +1 @@ -Subproject commit c5e56b8c505b084153971f64dfd2640a41f60b49 +Subproject commit 2b457dd5d90018172a4de12a67cc6869c51a2c0a From e1d7afc859f1582c10b056ac1140683ec15cc2f8 Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Fri, 22 Dec 2023 08:36:57 -0800 Subject: [PATCH 015/373] Add support for MOVSX* instructions Per https://www.ietf.org/archive/id/draft-ietf-bpf-isa-00.html#section-3.1 Signed-off-by: Dave Thaler --- src/asm_cfg.cpp | 10 +- src/asm_marshal.cpp | 21 ++- src/asm_ostream.cpp | 3 + src/asm_parse.cpp | 3 +- src/asm_syntax.hpp | 3 + src/asm_unmarshal.cpp | 9 +- src/assertions.cpp | 8 + src/crab/ebpf_domain.cpp | 56 +++++++ src/crab/ebpf_domain.hpp | 1 + src/test/test_yaml.cpp | 1 + test-data/movsx.yaml | 330 +++++++++++++++++++++++++++++++++++++++ 11 files changed, 438 insertions(+), 7 deletions(-) create mode 100644 test-data/movsx.yaml diff --git a/src/asm_cfg.cpp b/src/asm_cfg.cpp index 0d4724435..3787507fc 100644 --- a/src/asm_cfg.cpp +++ b/src/asm_cfg.cpp @@ -181,9 +181,13 @@ static std::string instype(Instruction ins) { } else if (std::holds_alternative(ins)) { return "packet_access"; } else if (std::holds_alternative(ins)) { - if (std::get(ins).op == Bin::Op::MOV) - return "assign"; - return "arith"; + switch (std::get(ins).op) { + case Bin::Op::MOV: + case Bin::Op::MOVSX8: + case Bin::Op::MOVSX16: + case Bin::Op::MOVSX32: return "assign"; + default: return "arith"; + } } else if (std::holds_alternative(ins)) { return "arith"; } else if (std::holds_alternative(ins)) { diff --git a/src/asm_marshal.cpp b/src/asm_marshal.cpp index 36bbe783e..fd7a74902 100644 --- a/src/asm_marshal.cpp +++ b/src/asm_marshal.cpp @@ -36,20 +36,37 @@ static uint8_t op(Bin::Op op) { case Op::ADD: return 0x0; case Op::SUB: return 0x1; case Op::MUL: return 0x2; + case Op::SDIV: case Op::UDIV: return 0x3; case Op::OR: return 0x4; case Op::AND: return 0x5; case Op::LSH: return 0x6; case Op::RSH: return 0x7; + case Op::SMOD: case Op::UMOD: return 0x9; case Op::XOR: return 0xa; - case Op::MOV: return 0xb; + case Op::MOV: + case Op::MOVSX8: + case Op::MOVSX16: + case Op::MOVSX32: return 0xb; case Op::ARSH: return 0xc; } assert(false); return {}; } +static int16_t offset(Bin::Op op) { + using Op = Bin::Op; + switch (op) { + case Op::SDIV: + case Op::SMOD: return 1; + case Op::MOVSX8: return 8; + case Op::MOVSX16: return 16; + case Op::MOVSX32: return 32; + } + return 0; +} + static uint8_t imm(Un::Op op) { using Op = Un::Op; switch (op) { @@ -96,7 +113,7 @@ struct MarshalVisitor { ebpf_inst res{.opcode = static_cast((b.is64 ? INST_CLS_ALU64 : INST_CLS_ALU) | (op(b.op) << 4)), .dst = b.dst.v, .src = 0, - .offset = 0, + .offset = offset(b.op), .imm = 0}; std::visit(overloaded{[&](Reg right) { res.opcode |= INST_SRC_REG; diff --git a/src/asm_ostream.cpp b/src/asm_ostream.cpp index 43ff6491e..9f8c12254 100644 --- a/src/asm_ostream.cpp +++ b/src/asm_ostream.cpp @@ -56,6 +56,9 @@ std::ostream& operator<<(std::ostream& os, Bin::Op op) { using Op = Bin::Op; switch (op) { case Op::MOV: return os; + case Op::MOVSX8: return os << "s8"; + case Op::MOVSX16: return os << "s16"; + case Op::MOVSX32: return os << "s32"; case Op::ADD: return os << "+"; case Op::SUB: return os << "-"; case Op::MUL: return os << "*"; diff --git a/src/asm_parse.cpp b/src/asm_parse.cpp index 7ce6428ed..9f4e42c1c 100644 --- a/src/asm_parse.cpp +++ b/src/asm_parse.cpp @@ -58,7 +58,8 @@ static const std::map str_to_binop = { {"", Bin::Op::MOV}, {"+", Bin::Op::ADD}, {"-", Bin::Op::SUB}, {"*", Bin::Op::MUL}, {"/", Bin::Op::UDIV}, {"%", Bin::Op::UMOD}, {"|", Bin::Op::OR}, {"&", Bin::Op::AND}, {"<<", Bin::Op::LSH}, {">>", Bin::Op::RSH}, {"s>>", Bin::Op::ARSH}, {"^", Bin::Op::XOR}, - {"s/", Bin::Op::SDIV}, {"s%", Bin::Op::SMOD}, + {"s/", Bin::Op::SDIV}, {"s%", Bin::Op::SMOD}, {"s8", Bin::Op::MOVSX8}, {"s16", Bin::Op::MOVSX16}, + {"s32", Bin::Op::MOVSX32}, }; static const std::map str_to_unop = { diff --git a/src/asm_syntax.hpp b/src/asm_syntax.hpp index 4121bc9e0..5490bb933 100644 --- a/src/asm_syntax.hpp +++ b/src/asm_syntax.hpp @@ -87,6 +87,9 @@ struct Bin { XOR, SDIV, SMOD, + MOVSX8, + MOVSX16, + MOVSX32, }; Op op; diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index e85885857..d2e100e14 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -109,7 +109,14 @@ struct Unmarshaller { default: throw InvalidInstruction{pc, "invalid ALU op 0x90"}; } case INST_ALU_OP_XOR: return Bin::Op::XOR; - case INST_ALU_OP_MOV: return Bin::Op::MOV; + case INST_ALU_OP_MOV: + switch (inst.offset) { + case 0: return Bin::Op::MOV; + case 8: return Bin::Op::MOVSX8; + case 16: return Bin::Op::MOVSX16; + case 32: return Bin::Op::MOVSX32; + default: throw InvalidInstruction{pc, "invalid ALU op 0xb0"}; + } case INST_ALU_OP_ARSH: if ((inst.opcode & INST_CLS_MASK) == INST_CLS_ALU) note("arsh32 is not allowed"); diff --git a/src/assertions.cpp b/src/assertions.cpp index 4dd791470..f99a67d6f 100644 --- a/src/assertions.cpp +++ b/src/assertions.cpp @@ -180,6 +180,14 @@ class AssertExtractor { vector operator()(Bin ins) const { switch (ins.op) { case Bin::Op::MOV: return {}; + case Bin::Op::MOVSX8: + case Bin::Op::MOVSX16: + case Bin::Op::MOVSX32: + if (std::holds_alternative(ins.v)) { + auto src = reg(ins.v); + return {Assert{TypeConstraint{src, TypeGroup::number}}}; + } + return {}; case Bin::Op::ADD: if (std::holds_alternative(ins.v)) { auto src = reg(ins.v); diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index 62667c88e..b511de0a7 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -2342,6 +2342,46 @@ void ebpf_domain_t::lshr(const Reg& dst_reg, int imm, int finite_width) { havoc_offsets(dst_reg); } +void ebpf_domain_t::sign_extend(const Reg& dst_reg, const linear_expression_t& right_svalue, int finite_width, + Bin::Op op) { + using namespace crab; + + int bits; + switch (op) { + case Bin::Op::MOVSX8: bits = 8; break; + case Bin::Op::MOVSX16: bits = 16; break; + case Bin::Op::MOVSX32: bits = 32; break; + default: throw std::exception(); + } + + reg_pack_t dst = reg_pack(dst_reg); + interval_t right_interval = m_inv.eval_interval(right_svalue); + type_inv.assign_type(m_inv, dst_reg, T_NUM); + havoc_offsets(dst_reg); + int64_t span = 1ULL << bits; + if (right_interval.ub() - right_interval.lb() >= number_t{span}) { + // Interval covers the full space. + havoc(dst.svalue); + return; + } + int64_t mask = 1ULL << (bits - 1); + + // Sign extend each bound. + int64_t lb = right_interval.lb().number().value().cast_to_sint64(); + lb &= (span - 1); + lb = (lb ^ mask) - mask; + int64_t ub = right_interval.ub().number().value().cast_to_sint64(); + ub &= (span - 1); + ub = (ub ^ mask) - mask; + m_inv.set(dst.svalue, crab::interval_t{number_t{lb}, number_t{ub}}); + + if (finite_width) { + m_inv.assign(dst.uvalue, dst.svalue); + overflow_signed(m_inv, dst.svalue, finite_width); + overflow_unsigned(m_inv, dst.uvalue, finite_width); + } +} + void ebpf_domain_t::ashr(const Reg& dst_reg, const linear_expression_t& right_svalue, int finite_width) { using namespace crab; @@ -2413,6 +2453,11 @@ void ebpf_domain_t::operator()(const Bin& bin) { type_inv.assign_type(m_inv, bin.dst, T_NUM); havoc_offsets(bin.dst); break; + case Bin::Op::MOVSX8: + case Bin::Op::MOVSX16: + case Bin::Op::MOVSX32: + sign_extend(bin.dst, number_t{(int32_t)imm}, finite_width, bin.op); + break; case Bin::Op::ADD: if (imm == 0) return; @@ -2655,6 +2700,17 @@ void ebpf_domain_t::operator()(const Bin& bin) { bitwise_xor(dst.svalue, dst.uvalue, src.uvalue, finite_width); havoc_offsets(bin.dst); break; + case Bin::Op::MOVSX8: + case Bin::Op::MOVSX16: + case Bin::Op::MOVSX32: + if (m_inv.entail(type_is_number(src_reg))) { + sign_extend(bin.dst, src.svalue, finite_width, bin.op); + break; + } + havoc(dst.svalue); + havoc(dst.uvalue); + havoc_offsets(bin.dst); + break; case Bin::Op::MOV: assign(dst.svalue, src.svalue); assign(dst.uvalue, src.uvalue); diff --git a/src/crab/ebpf_domain.hpp b/src/crab/ebpf_domain.hpp index c8bba9197..0fbb1992a 100644 --- a/src/crab/ebpf_domain.hpp +++ b/src/crab/ebpf_domain.hpp @@ -139,6 +139,7 @@ class ebpf_domain_t final { void shl_overflow(variable_t lhss, variable_t lhsu, const number_t& op2); void lshr(const Reg& reg, int imm, int finite_width); void ashr(const Reg& reg, const linear_expression_t& right_svalue, int finite_width); + void sign_extend(const Reg& dst_reg, const linear_expression_t& right_svalue, int finite_width, Bin::Op op); void assume(const linear_constraint_t& cst); diff --git a/src/test/test_yaml.cpp b/src/test/test_yaml.cpp index 44c89156f..2456bfe3f 100644 --- a/src/test/test_yaml.cpp +++ b/src/test/test_yaml.cpp @@ -28,6 +28,7 @@ YAML_CASE("test-data/sdivmod.yaml") YAML_CASE("test-data/full64.yaml") YAML_CASE("test-data/jump.yaml") YAML_CASE("test-data/loop.yaml") +YAML_CASE("test-data/movsx.yaml") YAML_CASE("test-data/packet.yaml") YAML_CASE("test-data/parse.yaml") YAML_CASE("test-data/shift.yaml") diff --git a/test-data/movsx.yaml b/test-data/movsx.yaml new file mode 100644 index 000000000..7aa3d0f54 --- /dev/null +++ b/test-data/movsx.yaml @@ -0,0 +1,330 @@ +# Copyright (c) Prevail Verifier contributors. +# SPDX-License-Identifier: MIT +--- +test-case: movsx8 immediate to 32 bits + +pre: [] + +code: + : | + w1 s8= 384 ; 0x180 -> 0xFFFFFF80 + +post: + - r1.type=number + - r1.svalue=4294967168 + - r1.uvalue=4294967168 +--- +test-case: movsx16 immediate to 32 bits + +pre: [] + +code: + : | + w1 s16= 98304 ; 0x18000 -> 0xFFFF8000 + +post: + - r1.type=number + - r1.svalue=4294934528 + - r1.uvalue=4294934528 +--- +test-case: movsx8 immediate to 64 bits + +pre: [] + +code: + : | + r1 s8= 384 ; 0x180 -> 0xFFFFFFFFFFFFFF80 + +post: + - r1.type=number + - r1.svalue=-128 + - r1.uvalue=18446744073709551488 +--- +test-case: movsx16 immediate to 64 bits + +pre: [] + +code: + : | + r1 s16= 98304 ; 0x18000 -> 0xFFFFFFFFFFFF8000 + +post: + - r1.type=number + - r1.svalue=-32768 + - r1.uvalue=18446744073709518848 +--- +test-case: movsx32 immediate to 64 bits + +pre: [] + +code: + : | + r1 s32= 2147483648 ; 0x80000000 -> 0xFFFFFFFF80000000 + +post: + - r1.type=number + - r1.svalue=-2147483648 + - r1.uvalue=18446744071562067968 +--- +test-case: movsx8 register to 32 bits + +pre: ["r1.svalue=384", "r1.uvalue=384", "r1.type=number", "r1.svalue=r1.uvalue"] + +code: + : | + w2 s8= r1 + +post: + - r1.type=number + - r1.svalue=384 + - r1.uvalue=384 + - r1.svalue=r1.uvalue + - r2.type=number + - r2.svalue=4294967168 + - r2.uvalue=4294967168 +--- +test-case: movsx16 register to 32 bits + +pre: ["r1.svalue=98304", "r1.uvalue=98304", "r1.type=number", "r1.svalue=r1.uvalue"] + +code: + : | + w2 s16= r1 + +post: + - r1.type=number + - r1.svalue=98304 + - r1.uvalue=98304 + - r1.svalue=r1.uvalue + - r2.type=number + - r2.svalue=4294934528 + - r2.uvalue=4294934528 +--- +test-case: movsx8 register to 64 bits + +pre: ["r1.svalue=384", "r1.uvalue=384", "r1.type=number", "r1.svalue=r1.uvalue"] + +code: + : | + r2 s8= r1 + +post: + - r1.type=number + - r1.svalue=384 + - r1.uvalue=384 + - r1.svalue=r1.uvalue + - r2.type=number + - r2.svalue=-128 + - r2.uvalue=18446744073709551488 +--- +test-case: movsx16 register to 64 bits + +pre: ["r1.svalue=98304", "r1.uvalue=98304", "r1.type=number", "r1.svalue=r1.uvalue"] + +code: + : | + r2 s16= r1 + +post: + - r1.type=number + - r1.svalue=98304 + - r1.uvalue=98304 + - r1.svalue=r1.uvalue + - r2.type=number + - r2.svalue=-32768 + - r2.uvalue=18446744073709518848 +--- +test-case: movsx32 register to 64 bits + +pre: ["r1.svalue=2147483648", "r1.uvalue=2147483648", "r1.type=number", "r1.svalue=r1.uvalue"] + +code: + : | + r2 s32= r1 + +post: + - r1.type=number + - r1.svalue=2147483648 + - r1.uvalue=2147483648 + - r1.svalue=r1.uvalue + - r2.type=number + - r2.svalue=-2147483648 + - r2.uvalue=18446744071562067968 +--- +test-case: movsx32 register with a non-number + +pre: [] + +code: + : | + r2 s32= r1 + +post: [] + +messages: + - "0: (r1.type == number)" +--- +test-case: movsx8 register range to 32 bits without wrap + +pre: ["r1.svalue=[128, 130]", "r1.uvalue=[128, 130]", "r1.type=number", "r1.svalue=r1.uvalue"] + +code: + : | + w2 s8= r1 + +post: + - r1.type=number + - r1.svalue=[128, 130] + - r1.uvalue=[128, 130] + - r1.svalue=r1.uvalue + - r2.type=number + - r2.svalue=[4294967168, 4294967170] + - r2.uvalue=[4294967168, 4294967170] + - r2.svalue=r2.uvalue +--- +test-case: movsx8 register range to 32 bits with wrap + +pre: ["r1.svalue=[255, 257]", "r1.uvalue=[255, 257]", "r1.type=number", "r1.svalue=r1.uvalue"] + +code: + : | + w2 s8= r1 + +post: + - r1.type=number + - r1.svalue=[255, 257] + - r1.uvalue=[255, 257] + - r1.svalue=r1.uvalue + - r2.type=number + - r2.svalue=[0, 4294967295] + - r2.uvalue=[0, 4294967295] + - r2.svalue=r2.uvalue +--- +test-case: movsx16 register range to 32 bits without wrap + +pre: ["r1.svalue=[32768, 32770]", "r1.uvalue=[32768, 32770]", "r1.type=number", "r1.svalue=r1.uvalue"] + +code: + : | + w2 s16= r1 + +post: + - r1.type=number + - r1.svalue=[32768, 32770] + - r1.uvalue=[32768, 32770] + - r1.svalue=r1.uvalue + - r2.type=number + - r2.svalue=[4294934528, 4294934530] + - r2.uvalue=[4294934528, 4294934530] + - r2.svalue=r2.uvalue +--- +test-case: movsx16 register range to 32 bits with wrap + +pre: ["r1.svalue=[65535, 65537]", "r1.uvalue=[65535, 65537]", "r1.type=number", "r1.svalue=r1.uvalue"] + +code: + : | + w2 s16= r1 + +post: + - r1.type=number + - r1.svalue=[65535, 65537] + - r1.uvalue=[65535, 65537] + - r1.svalue=r1.uvalue + - r2.type=number + - r2.svalue=[0, 4294967295] + - r2.uvalue=[0, 4294967295] + - r2.svalue=r2.uvalue +--- +test-case: movsx8 register range to 64 bits + +pre: ["r1.svalue=[255, 257]", "r1.uvalue=[255, 257]", "r1.type=number", "r1.svalue=r1.uvalue"] + +code: + : | + r2 s8= r1 + +post: + - r1.type=number + - r1.svalue=[255, 257] + - r1.uvalue=[255, 257] + - r1.svalue=r1.uvalue + - r2.type=number + - r2.svalue=[-1, 1] +--- +test-case: movsx8 register full range to 64 bits + +pre: ["r1.svalue=[255, 511]", "r1.uvalue=[255, 511]", "r1.type=number", "r1.svalue=r1.uvalue"] + +code: + : | + r2 s8= r1 + +post: + - r1.type=number + - r1.svalue=[255, 511] + - r1.uvalue=[255, 511] + - r1.svalue=r1.uvalue + - r2.type=number +--- +test-case: movsx16 register range to 64 bits + +pre: ["r1.svalue=[65535, 65537]", "r1.uvalue=[65535, 65537]", "r1.type=number", "r1.svalue=r1.uvalue"] + +code: + : | + r2 s16= r1 + +post: + - r1.type=number + - r1.svalue=[65535, 65537] + - r1.uvalue=[65535, 65537] + - r1.svalue=r1.uvalue + - r2.type=number + - r2.svalue=[-1, 1] +--- +test-case: movsx16 register full range to 64 bits + +pre: ["r1.svalue=[65535, 131071]", "r1.uvalue=[65535, 131071]", "r1.type=number", "r1.svalue=r1.uvalue"] + +code: + : | + r2 s16= r1 + +post: + - r1.type=number + - r1.svalue=[65535, 131071] + - r1.uvalue=[65535, 131071] + - r1.svalue=r1.uvalue + - r2.type=number +--- +test-case: movsx32 register range to 64 bits + +pre: ["r1.svalue=[4294967295, 4294967297]", "r1.uvalue=[4294967295, 4294967297]", "r1.type=number", "r1.svalue=r1.uvalue"] + +code: + : | + r2 s32= r1 + +post: + - r1.type=number + - r1.svalue=[4294967295, 4294967297] + - r1.uvalue=[4294967295, 4294967297] + - r1.svalue=r1.uvalue + - r2.type=number + - r2.svalue=[-1, 1] +--- +test-case: movsx32 register full range to 64 bits + +pre: ["r1.svalue=[4294967295, 8589934591]", "r1.uvalue=[4294967295, 8589934591]", "r1.type=number", "r1.svalue=r1.uvalue"] + +code: + : | + r2 s32= r1 + +post: + - r1.type=number + - r1.svalue=[4294967295, 8589934591] + - r1.uvalue=[4294967295, 8589934591] + - r1.svalue=r1.uvalue + - r2.type=number From 38ebf365a17128ab8773a8c780307f63770332a9 Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Mon, 25 Dec 2023 10:06:44 -0800 Subject: [PATCH 016/373] Update bpf_conformance to latest This PR combines #543 and #544 Also removes a redundant inclusion of the elfio library Signed-off-by: Dave Thaler --- .gitmodules | 3 --- CMakeLists.txt | 5 +++-- external/ELFIO | 1 - external/bpf_conformance | 2 +- 4 files changed, 4 insertions(+), 7 deletions(-) delete mode 160000 external/ELFIO diff --git a/.gitmodules b/.gitmodules index 808fbd701..f12802369 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,9 +4,6 @@ [submodule "external/radix_tree"] path = external/radix_tree url = https://github.com/ytakano/radix_tree -[submodule "external/ELFIO"] - path = external/ELFIO - url = https://github.com/serge1/ELFIO.git [submodule "external/bpf_conformance"] path = external/bpf_conformance url = https://github.com/Alan-Jowett/bpf_conformance.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 26d9ae3bf..d4c795491 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") if (NOT NUGET) message("ERROR: You must first install nuget.exe from https://www.nuget.org/downloads") else () - execute_process(COMMAND ${NUGET} ARGS install "Boost" -Version 1.81.0 -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages) + execute_process(COMMAND ${NUGET} install "Boost" -Version 1.81.0 -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages) set(BOOST_VERSION 1.81.0) endif() set(Boost_INCLUDE_DIRS ${CMAKE_BINARY_DIR}/packages/boost/lib/native/include) @@ -62,7 +62,7 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") endif () include_directories(./external) -include_directories(./external/ELFIO) +include_directories(./external/bpf_conformance/external/elfio) include_directories(./src) include_directories(./external/libbtf) include_directories(${Boost_INCLUDE_DIRS}) @@ -130,6 +130,7 @@ target_compile_options(ebpfverifier PUBLIC "$<$:${DEBUG_FLAGS}>") target_compile_options(ebpfverifier PUBLIC "$<$:${RELEASE_FLAGS}>") target_compile_options(ebpfverifier PUBLIC "$<$:${SANITIZE_FLAGS}>") +add_subdirectory("external/bpf_conformance/external/elfio") add_subdirectory("external/bpf_conformance/src") add_subdirectory("external/libbtf") diff --git a/external/ELFIO b/external/ELFIO deleted file mode 160000 index 2b457dd5d..000000000 --- a/external/ELFIO +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2b457dd5d90018172a4de12a67cc6869c51a2c0a diff --git a/external/bpf_conformance b/external/bpf_conformance index 78df87a6e..ab3409499 160000 --- a/external/bpf_conformance +++ b/external/bpf_conformance @@ -1 +1 @@ -Subproject commit 78df87a6e85d4051d6bc1c92b1b6c05d7195cf05 +Subproject commit ab3409499613e005cf8098bb8249efd71fa4c43e From 81a834d64edeb59b5045b1ecd955f25d1116a157 Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Wed, 27 Dec 2023 12:37:01 -0800 Subject: [PATCH 017/373] Add 32-bit unconditional jump instruction This instruction is part of the cpu=v4 set of instructions recently added, and specified in https://www.ietf.org/archive/id/draft-ietf-bpf-isa-00.html#jump-instructions Fixes #532 Signed-off-by: Dave Thaler --- src/asm_marshal.cpp | 15 +++++++++++---- src/asm_ostream.hpp | 17 +++++++++++++++-- src/asm_syntax.hpp | 3 ++- src/asm_unmarshal.cpp | 11 +++++++---- src/ebpf_vm_isa.hpp | 3 ++- src/test/test_marshal.cpp | 28 +++++++++++++++++++++++++++- 6 files changed, 64 insertions(+), 13 deletions(-) diff --git a/src/asm_marshal.cpp b/src/asm_marshal.cpp index fd7a74902..764077299 100644 --- a/src/asm_marshal.cpp +++ b/src/asm_marshal.cpp @@ -94,7 +94,8 @@ struct MarshalVisitor { } public: - std::functionint16_t> label_to_offset; + std::functionint16_t> label_to_offset16; + std::functionint32_t> label_to_offset32; vector operator()(Undefined const& a) { assert(false); @@ -179,7 +180,7 @@ struct MarshalVisitor { .opcode = static_cast(INST_CLS_JMP | (op(b.cond->op) << 4)), .dst = b.cond->left.v, .src = 0, - .offset = label_to_offset(b.target), + .offset = label_to_offset16(b.target), }; visit(overloaded{[&](Reg right) { res.opcode |= INST_SRC_REG; @@ -189,7 +190,11 @@ struct MarshalVisitor { b.cond->right); return {res}; } else { - return {ebpf_inst{.opcode = INST_OP_JA, .dst = 0, .src = 0, .offset = label_to_offset(b.target), .imm = 0}}; + int32_t imm = label_to_offset32(b.target); + if (imm != 0) + return {ebpf_inst{.opcode = INST_OP_JA32, .imm = imm}}; + else + return {ebpf_inst{.opcode = INST_OP_JA16, .offset = label_to_offset16(b.target)}}; } } @@ -252,7 +257,9 @@ struct MarshalVisitor { } }; -vector marshal(const Instruction& ins, pc_t pc) { return std::visit(MarshalVisitor{label_to_offset(pc)}, ins); } +vector marshal(const Instruction& ins, pc_t pc) { + return std::visit(MarshalVisitor{label_to_offset16(pc), label_to_offset32(pc)}, ins); +} vector marshal(const vector& insts) { vector res; diff --git a/src/asm_ostream.hpp b/src/asm_ostream.hpp index 7ab2c059e..e36f25fa6 100644 --- a/src/asm_ostream.hpp +++ b/src/asm_ostream.hpp @@ -11,8 +11,21 @@ #include "asm_syntax.hpp" -inline std::function label_to_offset(pc_t pc) { - return [=](const label_t& label) { return label.from - pc - 1; }; +// We use a 16-bit offset whenever it fits in 16 bits. +inline std::function label_to_offset16(pc_t pc) { + return [=](const label_t& label) { + int64_t offset = label.from - (int64_t)pc - 1; + return (offset >= INT16_MIN && offset <= INT16_MAX) ? (int16_t)offset : 0; + }; +} + +// We use the JA32 opcode with the offset in 'imm' when the offset +// of an unconditional jump doesn't fit in a int16_t. +inline std::function label_to_offset32(pc_t pc) { + return [=](const label_t& label) { + int64_t offset = label.from - (int64_t)pc - 1; + return (offset >= INT16_MIN && offset <= INT16_MAX) ? 0 : (int32_t)offset; + }; } std::ostream& operator<<(std::ostream& os, const btf_line_info_t& line_info); diff --git a/src/asm_syntax.hpp b/src/asm_syntax.hpp index 5490bb933..55c0b082b 100644 --- a/src/asm_syntax.hpp +++ b/src/asm_syntax.hpp @@ -336,7 +336,8 @@ using InstructionSeq = std::vector; #define DECLARE_EQ1(T, f1) \ inline bool operator==(T const& a, T const& b) { return a.f1 == b.f1; } -using pc_t = uint16_t; +// cpu=v4 supports 32-bit PC offsets so we need a large enough type. +using pc_t = size_t; // Helpers: diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index d2e100e14..8b63f5a97 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -381,16 +381,19 @@ struct Unmarshaller { throw InvalidInstruction(pc, "Bad instruction"); return Exit{}; case 0x0: - if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP) + if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP && + (inst.opcode & INST_CLS_MASK) != INST_CLS_JMP32) throw InvalidInstruction(pc, "Bad instruction"); default: { - pc_t new_pc = pc + 1 + inst.offset; + int32_t offset = (inst.opcode == INST_OP_JA32) ? inst.imm : inst.offset; + pc_t new_pc = pc + 1 + offset; if (new_pc >= insts.size()) throw InvalidInstruction(pc, "jump out of bounds"); else if (insts[new_pc].opcode == 0) throw InvalidInstruction(pc, "jump to middle of lddw"); - auto cond = inst.opcode == INST_OP_JA ? std::optional{} + auto cond = (inst.opcode == INST_OP_JA16 || inst.opcode == INST_OP_JA32) + ? std::optional{} : Condition{ .op = getJmpOp(pc, inst.opcode), .left = Reg{inst.dst}, @@ -400,7 +403,7 @@ struct Unmarshaller { }; return Jmp{ .cond = cond, - .target = label_t{new_pc}, + .target = label_t{(int)new_pc}, }; } } diff --git a/src/ebpf_vm_isa.hpp b/src/ebpf_vm_isa.hpp index 0f1f0974c..654e34525 100644 --- a/src/ebpf_vm_isa.hpp +++ b/src/ebpf_vm_isa.hpp @@ -54,7 +54,8 @@ enum { INST_OP_LDDW_IMM = (INST_CLS_LD | INST_SRC_IMM | INST_SIZE_DW), // Special - INST_OP_JA = (INST_CLS_JMP | 0x00), + INST_OP_JA32 = (INST_CLS_JMP32 | 0x00), + INST_OP_JA16 = (INST_CLS_JMP | 0x00), INST_OP_CALL = (INST_CLS_JMP | 0x80), INST_OP_EXIT = (INST_CLS_JMP | 0x90), diff --git a/src/test/test_marshal.cpp b/src/test/test_marshal.cpp index 8be56961e..083d5e3fa 100644 --- a/src/test/test_marshal.cpp +++ b/src/test/test_marshal.cpp @@ -6,6 +6,25 @@ #include "asm_marshal.hpp" #include "asm_unmarshal.hpp" +// Verify that if we unmarshal an instruction and then re-marshal it, +// we get what we expect. +static void compare_unmarshal_marshal(const ebpf_inst& ins, const ebpf_inst& expected_result) { + program_info info{.platform = &g_ebpf_platform_linux, + .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")}; + const ebpf_inst exit{.opcode = INST_OP_EXIT}; + InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", {ins, exit, exit}, info})); + REQUIRE(parsed.size() == 3); + auto [_, single, _2] = parsed.front(); + (void)_; // unused + (void)_2; // unused + std::vector marshaled = marshal(single, 0); + REQUIRE(marshaled.size() == 1); + ebpf_inst result = marshaled.back(); + REQUIRE(memcmp(&expected_result, &result, sizeof(result)) == 0); +} + +// Verify that if we marshal an instruction and then unmarshal it, +// we get the original. static void compare_marshal_unmarshal(const Instruction& ins, bool double_cmd = false) { program_info info{.platform = &g_ebpf_platform_linux, .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")}; @@ -81,6 +100,13 @@ TEST_CASE("disasm_marshal", "[disasm][marshal]") { // Condition::Op::NSET, does not exist in ebpf Condition::Op::NE, Condition::Op::SGT, Condition::Op::SGE, Condition::Op::LT, Condition::Op::LE, Condition::Op::SLT, Condition::Op::SLE}; + SECTION("goto offset") { + ebpf_inst jmp_offset{.opcode = INST_OP_JA16, .offset = 1}; + compare_unmarshal_marshal(jmp_offset, jmp_offset); + + // JA32 +1 is equivalent to JA16 +1 since the offset fits in 16 bits. + compare_unmarshal_marshal(ebpf_inst{.opcode = INST_OP_JA32, .imm = 1}, jmp_offset); + } SECTION("Reg right") { for (auto op : ops) { Condition cond{.op = op, .left = Reg{1}, .right = Reg{2}}; @@ -241,7 +267,7 @@ TEST_CASE("fail unmarshal", "[disasm][marshal]") { "0: Bad instruction\n"); check_unmarshal_fail(ebpf_inst{.opcode = (INST_MEM_UNUSED << 5) | INST_SIZE_W | INST_CLS_LDX, .imm = 8}, "0: Bad instruction\n"); - check_unmarshal_fail(ebpf_inst{.opcode = INST_CLS_JMP32}, "0: Bad instruction\n"); + check_unmarshal_fail(ebpf_inst{.opcode = INST_CLS_JMP32}, "0: jump out of bounds\n"); check_unmarshal_fail(ebpf_inst{.opcode = 0x90 | INST_CLS_JMP32}, "0: Bad instruction\n"); check_unmarshal_fail(ebpf_inst{.opcode = 0x10 | INST_CLS_JMP32}, "0: jump out of bounds\n"); } From 10d47ff0b4fef8e3d1b6f94ee58a4310e978ef38 Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Thu, 28 Dec 2023 14:26:35 -0800 Subject: [PATCH 018/373] Byteswap fixes * Add byteswap test cases * Fix asm_parse.cpp to allow le* operations Signed-off-by: Dave Thaler --- src/asm_parse.cpp | 2 +- src/asm_unmarshal.cpp | 13 ++++--- src/test/test_marshal.cpp | 6 ++- test-data/unop.yaml | 80 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 8 deletions(-) diff --git a/src/asm_parse.cpp b/src/asm_parse.cpp index 9f4e42c1c..d6c95cfdd 100644 --- a/src/asm_parse.cpp +++ b/src/asm_parse.cpp @@ -33,7 +33,7 @@ using crab::linear_expression_t; #define OPASSIGN R"_(\s*(\S*)=\s*)_" #define ASSIGN R"_(\s*=\s*)_" #define LONGLONG R"_(\s*(ll|)\s*)_" -#define UNOP R"_((-|be16|be32|be64))_" +#define UNOP R"_((-|be16|be32|be64|le16|le32|le64))_" #define PLUSMINUS R"_((\s*[+-])\s*)_" #define LPAREN R"_(\s*\(\s*)_" diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index 8b63f5a97..cacd76a43 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -122,14 +122,15 @@ struct Unmarshaller { note("arsh32 is not allowed"); return Bin::Op::ARSH; case INST_ALU_OP_END: + if ((inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64) { + std::string error_message = + "invalid endian immediate " + std::to_string(inst.imm) + " for 64 bit instruction"; + throw InvalidInstruction(pc, error_message.c_str()); + } switch (inst.imm) { case 16: return (inst.opcode & INST_END_BE) ? Un::Op::BE16 : Un::Op::LE16; - case 32: - if ((inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64) - throw InvalidInstruction(pc, "invalid endian immediate 32 for 64 bit instruction"); - return (inst.opcode & INST_END_BE) ? Un::Op::BE32 : Un::Op::LE32; - case 64: - return (inst.opcode & INST_END_BE) ? Un::Op::BE64 : Un::Op::LE64; + case 32: return (inst.opcode & INST_END_BE) ? Un::Op::BE32 : Un::Op::LE32; + case 64: return (inst.opcode & INST_END_BE) ? Un::Op::BE64 : Un::Op::LE64; default: throw InvalidInstruction(pc, "invalid endian immediate"); } diff --git a/src/test/test_marshal.cpp b/src/test/test_marshal.cpp index 083d5e3fa..05984a7ab 100644 --- a/src/test/test_marshal.cpp +++ b/src/test/test_marshal.cpp @@ -47,7 +47,9 @@ static void check_unmarshal_fail(ebpf_inst inst, std::string expected_error_mess program_info info{.platform = &g_ebpf_platform_linux, .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")}; std::vector insns = {inst}; - std::string error_message = std::get(unmarshal(raw_program{"", "", insns, info})); + auto result = unmarshal(raw_program{"", "", insns, info}); + REQUIRE(std::holds_alternative(result)); + std::string error_message = std::get(result); REQUIRE(error_message == expected_error_message); } @@ -270,4 +272,6 @@ TEST_CASE("fail unmarshal", "[disasm][marshal]") { check_unmarshal_fail(ebpf_inst{.opcode = INST_CLS_JMP32}, "0: jump out of bounds\n"); check_unmarshal_fail(ebpf_inst{.opcode = 0x90 | INST_CLS_JMP32}, "0: Bad instruction\n"); check_unmarshal_fail(ebpf_inst{.opcode = 0x10 | INST_CLS_JMP32}, "0: jump out of bounds\n"); + check_unmarshal_fail(ebpf_inst{.opcode = INST_ALU_OP_END | INST_CLS_ALU64, .imm = 0}, "0: invalid endian immediate 0 for 64 bit instruction\n"); + check_unmarshal_fail(ebpf_inst{.opcode = INST_ALU_OP_END | INST_CLS_ALU64, .imm = 16}, "0: invalid endian immediate 16 for 64 bit instruction\n"); } diff --git a/test-data/unop.yaml b/test-data/unop.yaml index 2c23f914f..6c3f3033d 100644 --- a/test-data/unop.yaml +++ b/test-data/unop.yaml @@ -40,3 +40,83 @@ code: r1 = -r1 post: ["r1.type=number", "r1.svalue=[-5, 5]"] +--- +test-case: be16 singleton + +pre: ["r1.type=number", "r1.svalue=6636321", "r1.uvalue=6636321"] + +code: + : | + r1 = be16 r1 ; 0x654321 -> 0x2143 + +post: ["r1.type=number", "r1.svalue=8515", "r1.uvalue=8515"] +--- +test-case: be32 singleton + +pre: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] + +code: + : | + r1 = be32 r1 ; 0x0987654321 -> 0x21436587 + +post: ["r1.type=number", "r1.svalue=558065031", "r1.uvalue=558065031"] +--- +test-case: be64 singleton + +pre: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] + +code: + : | + r1 = be64 r1 ; 0x0987654321 -> 0x2143658709000000 + +post: ["r1.type=number", "r1.svalue=2396871057337221120", "r1.uvalue=2396871057337221120"] +--- +test-case: le16 singleton + +pre: ["r1.type=number", "r1.svalue=6636321", "r1.uvalue=6636321"] + +code: + : | + r1 = le16 r1 ; 0x654321 -> 0x4321 + +post: ["r1.type=number", "r1.svalue=17185", "r1.uvalue=17185"] +--- +test-case: le32 singleton + +pre: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] + +code: + : | + r1 = le32 r1 ; 0x0987654321 -> 0x87654321 + +post: ["r1.type=number", "r1.svalue=2271560481", "r1.uvalue=2271560481"] +--- +test-case: le64 singleton + +pre: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] + +code: + : | + r1 = le64 r1 ; 0x0987654321 -> 0x2143658709000000 + +post: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] +--- +test-case: le16 range + +pre: ["r1.type=number", "r1.svalue=[0, 2]", "r1.uvalue=[0, 2]", "r1.svalue=r1.uvalue"] + +code: + : | + r1 = le16 r1 ; this could preserve the range but we don't support that yet + +post: ["r1.type=number"] +--- +test-case: be16 range + +pre: ["r1.type=number", "r1.svalue=[0, 2]", "r1.uvalue=[0, 2]", "r1.svalue=r1.uvalue"] + +code: + : | + r1 = be16 r1 ; [0x0000, 0x0002] -> [0x0000, 0x2000] but currently we just lose the range + +post: ["r1.type=number"] From 3321c602fc6c61f6238f5230fe386edc870be4a1 Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Thu, 28 Dec 2023 14:55:37 -0800 Subject: [PATCH 019/373] Add bswap instructions Fixes #531 Signed-off-by: Dave Thaler --- src/asm_marshal.cpp | 19 ++++++++++++++--- src/asm_ostream.cpp | 3 +++ src/asm_parse.cpp | 5 ++++- src/asm_syntax.hpp | 3 +++ src/asm_unmarshal.cpp | 11 +++++++--- src/crab/ebpf_domain.cpp | 12 +++++++++++ src/test/test_marshal.cpp | 4 ++-- test-data/unop.yaml | 44 +++++++++++++++++++++++++++++++++++++-- 8 files changed, 90 insertions(+), 11 deletions(-) diff --git a/src/asm_marshal.cpp b/src/asm_marshal.cpp index 764077299..1a308bc93 100644 --- a/src/asm_marshal.cpp +++ b/src/asm_marshal.cpp @@ -72,11 +72,14 @@ static uint8_t imm(Un::Op op) { switch (op) { case Op::NEG: return 0; case Op::BE16: - case Op::LE16: return 16; + case Op::LE16: + case Op::SWAP16: return 16; case Op::BE32: - case Op::LE32: return 32; + case Op::LE32: + case Op::SWAP32: return 32; case Op::BE64: - case Op::LE64: return 64; + case Op::LE64: + case Op::SWAP64: return 64; } assert(false); return {}; @@ -156,6 +159,16 @@ struct MarshalVisitor { .offset = 0, .imm = imm(b.op), }}; + case Un::Op::SWAP16: + case Un::Op::SWAP32: + case Un::Op::SWAP64: + return {ebpf_inst{ + .opcode = static_cast(INST_CLS_ALU64 | (0xd << 4)), + .dst = b.dst.v, + .src = 0, + .offset = 0, + .imm = imm(b.op), + }}; } assert(false); return {}; diff --git a/src/asm_ostream.cpp b/src/asm_ostream.cpp index 9f8c12254..3c8693954 100644 --- a/src/asm_ostream.cpp +++ b/src/asm_ostream.cpp @@ -224,6 +224,9 @@ struct InstructionPrinterVisitor { case Un::Op::LE16: os_ << "le16 "; break; case Un::Op::LE32: os_ << "le32 "; break; case Un::Op::LE64: os_ << "le64 "; break; + case Un::Op::SWAP16: os_ << "swap16 "; break; + case Un::Op::SWAP32: os_ << "swap32 "; break; + case Un::Op::SWAP64: os_ << "swap64 "; break; case Un::Op::NEG: os_ << "-"; break; } os_ << b.dst; diff --git a/src/asm_parse.cpp b/src/asm_parse.cpp index d6c95cfdd..a60fe6395 100644 --- a/src/asm_parse.cpp +++ b/src/asm_parse.cpp @@ -33,7 +33,7 @@ using crab::linear_expression_t; #define OPASSIGN R"_(\s*(\S*)=\s*)_" #define ASSIGN R"_(\s*=\s*)_" #define LONGLONG R"_(\s*(ll|)\s*)_" -#define UNOP R"_((-|be16|be32|be64|le16|le32|le64))_" +#define UNOP R"_((-|be16|be32|be64|le16|le32|le64|swap16|swap32|swap64))_" #define PLUSMINUS R"_((\s*[+-])\s*)_" #define LPAREN R"_(\s*\(\s*)_" @@ -69,6 +69,9 @@ static const std::map str_to_unop = { {"le16", Un::Op::LE16}, {"le32", Un::Op::LE32}, {"le64", Un::Op::LE64}, + {"swap16", Un::Op::SWAP16}, + {"swap32", Un::Op::SWAP32}, + {"swap64", Un::Op::SWAP64}, {"-", Un::Op::NEG}, }; diff --git a/src/asm_syntax.hpp b/src/asm_syntax.hpp index 55c0b082b..c8e621161 100644 --- a/src/asm_syntax.hpp +++ b/src/asm_syntax.hpp @@ -108,6 +108,9 @@ struct Un { LE16, // dst = htole16(dst) LE32, // dst = htole32(dst) LE64, // dst = htole64(dst) + SWAP16, // dst = bswap16(dst) + SWAP32, // dst = bswap32(dst) + SWAP64, // dst = bswap64(dst) NEG, // dst = -dst }; diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index cacd76a43..102f1fa80 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -123,9 +123,14 @@ struct Unmarshaller { return Bin::Op::ARSH; case INST_ALU_OP_END: if ((inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64) { - std::string error_message = - "invalid endian immediate " + std::to_string(inst.imm) + " for 64 bit instruction"; - throw InvalidInstruction(pc, error_message.c_str()); + if (inst.opcode & INST_END_BE) + throw InvalidInstruction(pc, "invalid endian immediate"); + switch (inst.imm) { + case 16: return Un::Op::SWAP16; + case 32: return Un::Op::SWAP32; + case 64: return Un::Op::SWAP64; + default: throw InvalidInstruction(pc, "invalid endian immediate"); + } } switch (inst.imm) { case 16: return (inst.opcode & INST_END_BE) ? Un::Op::BE16 : Un::Op::LE16; diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index b511de0a7..bce79438e 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -1484,6 +1484,18 @@ void ebpf_domain_t::operator()(const Un& stmt) { swap_endianness(dst.svalue, int64_t(0), boost::endian::native_to_little); swap_endianness(dst.svalue, uint64_t(0), boost::endian::native_to_little); break; + case Un::Op::SWAP16: + swap_endianness(dst.svalue, uint16_t(0), boost::endian::endian_reverse); + swap_endianness(dst.uvalue, uint16_t(0), boost::endian::endian_reverse); + break; + case Un::Op::SWAP32: + swap_endianness(dst.svalue, uint32_t(0), boost::endian::endian_reverse); + swap_endianness(dst.uvalue, uint32_t(0), boost::endian::endian_reverse); + break; + case Un::Op::SWAP64: + swap_endianness(dst.svalue, int64_t(0), boost::endian::endian_reverse); + swap_endianness(dst.uvalue, uint64_t(0), boost::endian::endian_reverse); + break; case Un::Op::NEG: neg(dst.svalue, dst.uvalue, stmt.is64 ? 64 : 32); havoc_offsets(stmt.dst); diff --git a/src/test/test_marshal.cpp b/src/test/test_marshal.cpp index 05984a7ab..33773791f 100644 --- a/src/test/test_marshal.cpp +++ b/src/test/test_marshal.cpp @@ -272,6 +272,6 @@ TEST_CASE("fail unmarshal", "[disasm][marshal]") { check_unmarshal_fail(ebpf_inst{.opcode = INST_CLS_JMP32}, "0: jump out of bounds\n"); check_unmarshal_fail(ebpf_inst{.opcode = 0x90 | INST_CLS_JMP32}, "0: Bad instruction\n"); check_unmarshal_fail(ebpf_inst{.opcode = 0x10 | INST_CLS_JMP32}, "0: jump out of bounds\n"); - check_unmarshal_fail(ebpf_inst{.opcode = INST_ALU_OP_END | INST_CLS_ALU64, .imm = 0}, "0: invalid endian immediate 0 for 64 bit instruction\n"); - check_unmarshal_fail(ebpf_inst{.opcode = INST_ALU_OP_END | INST_CLS_ALU64, .imm = 16}, "0: invalid endian immediate 16 for 64 bit instruction\n"); + check_unmarshal_fail(ebpf_inst{.opcode = INST_ALU_OP_END | INST_CLS_ALU, .imm = 0}, "0: invalid endian immediate\n"); + check_unmarshal_fail(ebpf_inst{.opcode = INST_ALU_OP_END | INST_CLS_ALU64, .imm = 0}, "0: invalid endian immediate\n"); } diff --git a/test-data/unop.yaml b/test-data/unop.yaml index 6c3f3033d..180ff41ca 100644 --- a/test-data/unop.yaml +++ b/test-data/unop.yaml @@ -71,6 +71,16 @@ code: post: ["r1.type=number", "r1.svalue=2396871057337221120", "r1.uvalue=2396871057337221120"] --- +test-case: be16 range + +pre: ["r1.type=number", "r1.svalue=[0, 2]", "r1.uvalue=[0, 2]", "r1.svalue=r1.uvalue"] + +code: + : | + r1 = be16 r1 ; [0x0000, 0x0002] -> [0x0000, 0x2000] but currently we just lose the range + +post: ["r1.type=number"] +--- test-case: le16 singleton pre: ["r1.type=number", "r1.svalue=6636321", "r1.uvalue=6636321"] @@ -111,12 +121,42 @@ code: post: ["r1.type=number"] --- -test-case: be16 range +test-case: swap16 singleton + +pre: ["r1.type=number", "r1.svalue=6636321", "r1.uvalue=6636321"] + +code: + : | + r1 = swap16 r1 ; 0x654321 -> 0x2143 + +post: ["r1.type=number", "r1.svalue=8515", "r1.uvalue=8515"] +--- +test-case: swap32 singleton + +pre: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] + +code: + : | + r1 = swap32 r1 ; 0x0987654321 -> 0x21436587 + +post: ["r1.type=number", "r1.svalue=558065031", "r1.uvalue=558065031"] +--- +test-case: swap64 singleton + +pre: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] + +code: + : | + r1 = swap64 r1 ; 0x0987654321 -> 0x2143658709000000 + +post: ["r1.type=number", "r1.svalue=2396871057337221120", "r1.uvalue=2396871057337221120"] +--- +test-case: swap16 range pre: ["r1.type=number", "r1.svalue=[0, 2]", "r1.uvalue=[0, 2]", "r1.svalue=r1.uvalue"] code: : | - r1 = be16 r1 ; [0x0000, 0x0002] -> [0x0000, 0x2000] but currently we just lose the range + r1 = swap16 r1 ; [0x0000, 0x0002] -> [0x0000, 0x2000] but currently we just lose the range post: ["r1.type=number"] From d239aa395daf8d1ab86bbd9b55ef45b01109cd13 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 30 Dec 2023 16:41:56 +0000 Subject: [PATCH 020/373] Bump external/bpf_conformance from `ab34094` to `9b6abdf` Bumps [external/bpf_conformance](https://github.com/Alan-Jowett/bpf_conformance) from `ab34094` to `9b6abdf`. - [Release notes](https://github.com/Alan-Jowett/bpf_conformance/releases) - [Commits](https://github.com/Alan-Jowett/bpf_conformance/compare/ab3409499613e005cf8098bb8249efd71fa4c43e...9b6abdf38db28dcaf8da33e504c89039b931b6ab) --- updated-dependencies: - dependency-name: external/bpf_conformance dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/bpf_conformance | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/bpf_conformance b/external/bpf_conformance index ab3409499..9b6abdf38 160000 --- a/external/bpf_conformance +++ b/external/bpf_conformance @@ -1 +1 @@ -Subproject commit ab3409499613e005cf8098bb8249efd71fa4c43e +Subproject commit 9b6abdf38db28dcaf8da33e504c89039b931b6ab From 68951ab23493196f8a1c0d184cae59bdd5943c28 Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Sun, 31 Dec 2023 12:01:50 -0800 Subject: [PATCH 021/373] Fix misleading instruction_count message Since PR #479, PREVAIL now computes a loop iteration count, not an instruction_count, so update messages and symbols names accordingly. Fixes #542 Signed-off-by: Dave Thaler --- src/asm_ostream.cpp | 2 +- src/config.hpp | 2 +- src/crab/ebpf_domain.cpp | 14 +++++++------- src/crab/ebpf_domain.hpp | 4 ++-- src/crab/fwd_analyzer.cpp | 2 +- src/crab/var_factory.cpp | 4 ++-- src/crab/variable.hpp | 4 ++-- src/crab_verifier.cpp | 16 ++++++++-------- src/main/check.cpp | 4 ++-- 9 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/asm_ostream.cpp b/src/asm_ostream.cpp index 3c8693954..0285d6a27 100644 --- a/src/asm_ostream.cpp +++ b/src/asm_ostream.cpp @@ -338,7 +338,7 @@ struct InstructionPrinterVisitor { } void operator()(IncrementLoopCounter const& a) { - os_ << crab::variable_t::instruction_count(to_string(a.name)) << "++"; + os_ << crab::variable_t::loop_counter(to_string(a.name)) << "++"; } }; diff --git a/src/config.hpp b/src/config.hpp index 00fcd4ea1..cf3065fd9 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -25,7 +25,7 @@ struct ebpf_verifier_options_t { struct ebpf_verifier_stats_t { int total_unreachable; int total_warnings; - int max_instruction_count; + int max_loop_count; }; extern const ebpf_verifier_options_t ebpf_verifier_default_options; diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index bce79438e..90e920a17 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -925,7 +925,7 @@ ebpf_domain_t ebpf_domain_t::calculate_constant_limits() { inv += r.packet_offset <= variable_t::packet_size(); inv += r.packet_offset >= 0; if (thread_local_options.check_termination) { - for (variable_t counter : variable_t::get_instruction_counters()) { + for (variable_t counter : variable_t::get_loop_counters()) { inv += counter <= std::numeric_limits::max(); inv += counter >= 0; inv += counter <= r.svalue; @@ -2828,18 +2828,18 @@ ebpf_domain_t ebpf_domain_t::setup_entry(bool init_r1) { return inv; } -void ebpf_domain_t::initialize_instruction_count(const label_t label) { - m_inv.assign(variable_t::instruction_count(to_string(label)), 0); +void ebpf_domain_t::initialize_loop_counter(const label_t label) { + m_inv.assign(variable_t::loop_counter(to_string(label)), 0); } -bound_t ebpf_domain_t::get_instruction_count_upper_bound() { +bound_t ebpf_domain_t::get_loop_count_upper_bound() { crab::bound_t ub{number_t{0}}; - for (variable_t counter : variable_t::get_instruction_counters()) - ub += std::max(ub, m_inv[counter].ub()); + for (variable_t counter : variable_t::get_loop_counters()) + ub = std::max(ub, m_inv[counter].ub()); return ub; } void ebpf_domain_t::operator()(const IncrementLoopCounter& ins) { - this->add(variable_t::instruction_count(to_string(ins.name)), 1); + this->add(variable_t::loop_counter(to_string(ins.name)), 1); } } // namespace crab diff --git a/src/crab/ebpf_domain.hpp b/src/crab/ebpf_domain.hpp index 0fbb1992a..fb3ef276a 100644 --- a/src/crab/ebpf_domain.hpp +++ b/src/crab/ebpf_domain.hpp @@ -47,7 +47,7 @@ class ebpf_domain_t final { typedef bool check_require_func_t(NumAbsDomain&, const linear_constraint_t&, std::string); void set_require_check(std::function f); - bound_t get_instruction_count_upper_bound(); + bound_t get_loop_count_upper_bound(); static ebpf_domain_t setup_entry(bool init_r1); static ebpf_domain_t from_constraints(const std::set& constraints, bool setup_constraints); @@ -79,7 +79,7 @@ class ebpf_domain_t final { void operator()(const ZeroCtxOffset&); void operator()(const IncrementLoopCounter&); - void initialize_instruction_count(label_t label); + void initialize_loop_counter(label_t label); static ebpf_domain_t calculate_constant_limits(); private: // private generic domain functions diff --git a/src/crab/fwd_analyzer.cpp b/src/crab/fwd_analyzer.cpp index 5a760bae4..d08f515d3 100644 --- a/src/crab/fwd_analyzer.cpp +++ b/src/crab/fwd_analyzer.cpp @@ -124,7 +124,7 @@ std::pair run_forward_analyzer(cfg_t& cfg, } } for (const label_t& label : cycle_heads) { - entry_inv.initialize_instruction_count(label); + entry_inv.initialize_loop_counter(label); cfg.get_node(label).insert(IncrementLoopCounter{label}); } } diff --git a/src/crab/var_factory.cpp b/src/crab/var_factory.cpp index 2418a0b96..e03371e32 100644 --- a/src/crab/var_factory.cpp +++ b/src/crab/var_factory.cpp @@ -88,7 +88,7 @@ variable_t variable_t::kind_var(data_kind_t kind, variable_t type_variable) { variable_t variable_t::meta_offset() { return make("meta_offset"); } variable_t variable_t::packet_size() { return make("packet_size"); } -variable_t variable_t::instruction_count(const std::string& label) { return make("pc[" + label + "]"); } +variable_t variable_t::loop_counter(const std::string& label) { return make("pc[" + label + "]"); } static bool ends_with(const std::string& str, const std::string& suffix) { @@ -108,7 +108,7 @@ bool variable_t::is_in_stack() const { return this->name()[0] == 's'; } -std::vector variable_t::get_instruction_counters() { +std::vector variable_t::get_loop_counters() { std::vector res; for (const std::string& name: *names) { if (name.find("pc") == 0) diff --git a/src/crab/variable.hpp b/src/crab/variable.hpp index f51055c42..4b83effbe 100644 --- a/src/crab/variable.hpp +++ b/src/crab/variable.hpp @@ -75,8 +75,8 @@ class variable_t final { static variable_t kind_var(data_kind_t kind, variable_t type_variable); static variable_t meta_offset(); static variable_t packet_size(); - static std::vector get_instruction_counters(); - static variable_t instruction_count(const std::string& label); + static std::vector get_loop_counters(); + static variable_t loop_counter(const std::string& label); [[nodiscard]] bool is_in_stack() const; struct Hasher { diff --git a/src/crab_verifier.cpp b/src/crab_verifier.cpp index 7904415cd..3d5d33f31 100644 --- a/src/crab_verifier.cpp +++ b/src/crab_verifier.cpp @@ -31,7 +31,7 @@ struct checks_db final { std::map> m_db{}; int total_warnings{}; int total_unreachable{}; - crab::bound_t max_instruction_count{crab::number_t{0}}; + crab::bound_t max_loop_count{crab::number_t{0}}; void add(const label_t& label, const std::string& msg) { m_db[label].emplace_back(msg); } @@ -45,8 +45,8 @@ struct checks_db final { total_unreachable++; } - [[nodiscard]] int get_max_instruction_count() const { - auto m = this->max_instruction_count.number(); + [[nodiscard]] int get_max_loop_count() const { + auto m = this->max_loop_count.number(); if (m && m->fits_sint32()) return m->cast_to_sint32(); else @@ -94,7 +94,7 @@ static checks_db generate_report(cfg_t& cfg, crab::invariant_table_t& pre_invari if (thread_local_options.check_termination) { auto last_inv = post_invariants.at(cfg.exit_label()); - m_db.max_instruction_count = last_inv.get_instruction_count_upper_bound(); + m_db.max_loop_count = last_inv.get_loop_count_upper_bound(); } return m_db; } @@ -122,8 +122,8 @@ static void print_report(std::ostream& os, const checks_db& db, const Instructio } } os << "\n"; - crab::number_t max_instructions{100000}; - if (db.max_instruction_count > max_instructions) { + crab::number_t max_loop_count{100000}; + if (db.max_loop_count > max_loop_count) { os << "Could not prove termination.\n"; } } @@ -171,7 +171,7 @@ bool run_ebpf_analysis(std::ostream& s, cfg_t& cfg, const program_info& info, co if (stats) { stats->total_unreachable = report.total_unreachable; stats->total_warnings = report.total_warnings; - stats->max_instruction_count = report.get_max_instruction_count(); + stats->max_loop_count = report.get_max_loop_count(); } return (report.total_warnings == 0); } @@ -224,7 +224,7 @@ bool ebpf_verify_program(std::ostream& os, const InstructionSeq& prog, const pro if (stats) { stats->total_unreachable = report.total_unreachable; stats->total_warnings = report.total_warnings; - stats->max_instruction_count = report.get_max_instruction_count(); + stats->max_loop_count = report.get_max_loop_count(); } return (report.total_warnings == 0); } diff --git a/src/main/check.cpp b/src/main/check.cpp index c039807f6..8b91d6d03 100644 --- a/src/main/check.cpp +++ b/src/main/check.cpp @@ -161,8 +161,8 @@ int main(int argc, char** argv) { const auto [res, seconds] = timed_execution([&] { return ebpf_verify_program(std::cout, prog, raw_prog.info, &ebpf_verifier_options, &verifier_stats); }); - if (ebpf_verifier_options.check_termination && (ebpf_verifier_options.print_failures || ebpf_verifier_options.print_invariants)) { - std::cout << "Program terminates within " << verifier_stats.max_instruction_count << " instructions\n"; + if (res && ebpf_verifier_options.check_termination && (ebpf_verifier_options.print_failures || ebpf_verifier_options.print_invariants)) { + std::cout << "Program terminates within " << verifier_stats.max_loop_count << " loop iterations\n"; } std::cout << res << "," << seconds << "," << resident_set_size_kb() << "\n"; return !res; From 90ce44a666f21e7caa5c7adb9abbcc87a14aee7b Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Mon, 1 Jan 2024 18:47:00 -0800 Subject: [PATCH 022/373] Fix checking of bad call parameter Prior to this change, if the caller didn't pass enough registers, the verifier would throw an exception instead of gracefully failing verification. Signed-off-by: Dave Thaler --- src/crab/ebpf_domain.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index 90e920a17..d1ac9678d 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -2144,7 +2144,12 @@ void ebpf_domain_t::operator()(const Call& call) { case ArgPair::Kind::PTR_TO_WRITABLE_MEM: { bool store_numbers = true; - variable_t addr = get_type_offset_variable(param.mem).value(); + auto variable = get_type_offset_variable(param.mem); + if (!variable.has_value()) { + require(m_inv, linear_constraint_t::FALSE(), "Argument must be a pointer to writable memory"); + return; + } + variable_t addr = variable.value(); variable_t width = reg_pack(param.size).svalue; m_inv = type_inv.join_over_types(m_inv, param.mem, [&](NumAbsDomain& inv, type_encoding_t type) { From c5f2b92cbfc5de435c8da34360ae99a2e3818df6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 6 Jan 2024 16:07:24 +0000 Subject: [PATCH 023/373] Bump external/bpf_conformance from `9b6abdf` to `fe414a7` Bumps [external/bpf_conformance](https://github.com/Alan-Jowett/bpf_conformance) from `9b6abdf` to `fe414a7`. - [Release notes](https://github.com/Alan-Jowett/bpf_conformance/releases) - [Commits](https://github.com/Alan-Jowett/bpf_conformance/compare/9b6abdf38db28dcaf8da33e504c89039b931b6ab...fe414a7f063dc68d506c6368e4e0c31ffb1f2584) --- updated-dependencies: - dependency-name: external/bpf_conformance dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/bpf_conformance | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/bpf_conformance b/external/bpf_conformance index 9b6abdf38..fe414a7f0 160000 --- a/external/bpf_conformance +++ b/external/bpf_conformance @@ -1 +1 @@ -Subproject commit 9b6abdf38db28dcaf8da33e504c89039b931b6ab +Subproject commit fe414a7f063dc68d506c6368e4e0c31ffb1f2584 From 757e3fab10d165b85f75d9c3982e0c1695cc3466 Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Thu, 11 Jan 2024 10:37:28 -0800 Subject: [PATCH 024/373] Update to latest bpf_conformance suite Signed-off-by: Dave Thaler --- external/bpf_conformance | 2 +- src/asm_unmarshal.cpp | 28 +++++++++++++++++----------- src/test/test_conformance.cpp | 25 ++++++++++++++++++++----- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/external/bpf_conformance b/external/bpf_conformance index fe414a7f0..13de786ef 160000 --- a/external/bpf_conformance +++ b/external/bpf_conformance @@ -1 +1 @@ -Subproject commit fe414a7f063dc68d506c6368e4e0c31ffb1f2584 +Subproject commit 13de786efe55fb9c456561cf3ca1de4f51f590de diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index 102f1fa80..f11697bef 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -87,28 +87,20 @@ struct Unmarshaller { explicit Unmarshaller(vector>& notes, const program_info& info) : notes{notes}, info{info} { note_next_pc(); } auto getAluOp(size_t pc, ebpf_inst inst) -> std::variant { + // First handle instructions that support a non-zero offset. switch (inst.opcode & INST_ALU_OP_MASK) { - case INST_ALU_OP_ADD: return Bin::Op::ADD; - case INST_ALU_OP_SUB: return Bin::Op::SUB; - case INST_ALU_OP_MUL: return Bin::Op::MUL; case INST_ALU_OP_DIV: switch (inst.offset) { case 0: return Bin::Op::UDIV; case 1: return Bin::Op::SDIV; default: throw InvalidInstruction{pc, "invalid ALU op 0x30"}; } - case INST_ALU_OP_OR: return Bin::Op::OR; - case INST_ALU_OP_AND: return Bin::Op::AND; - case INST_ALU_OP_LSH: return Bin::Op::LSH; - case INST_ALU_OP_RSH: return Bin::Op::RSH; - case INST_ALU_OP_NEG: return Un::Op::NEG; case INST_ALU_OP_MOD: switch (inst.offset) { case 0: return Bin::Op::UMOD; case 1: return Bin::Op::SMOD; default: throw InvalidInstruction{pc, "invalid ALU op 0x90"}; } - case INST_ALU_OP_XOR: return Bin::Op::XOR; case INST_ALU_OP_MOV: switch (inst.offset) { case 0: return Bin::Op::MOV; @@ -117,6 +109,22 @@ struct Unmarshaller { case 32: return Bin::Op::MOVSX32; default: throw InvalidInstruction{pc, "invalid ALU op 0xb0"}; } + } + + // All the rest require a zero offset. + if (inst.offset != 0) + throw InvalidInstruction{pc, "nonzero offset for register alu op"}; + + switch (inst.opcode & INST_ALU_OP_MASK) { + case INST_ALU_OP_ADD: return Bin::Op::ADD; + case INST_ALU_OP_SUB: return Bin::Op::SUB; + case INST_ALU_OP_MUL: return Bin::Op::MUL; + case INST_ALU_OP_OR: return Bin::Op::OR; + case INST_ALU_OP_AND: return Bin::Op::AND; + case INST_ALU_OP_LSH: return Bin::Op::LSH; + case INST_ALU_OP_RSH: return Bin::Op::RSH; + case INST_ALU_OP_NEG: return Un::Op::NEG; + case INST_ALU_OP_XOR: return Bin::Op::XOR; case INST_ALU_OP_ARSH: if ((inst.opcode & INST_CLS_MASK) == INST_CLS_ALU) note("arsh32 is not allowed"); @@ -150,8 +158,6 @@ struct Unmarshaller { uint64_t zero_extend(int32_t imm) { return (uint64_t)(uint32_t)imm; } auto getBinValue(pc_t pc, ebpf_inst inst) -> Value { - if (inst.offset != 0) - throw InvalidInstruction{pc, "nonzero offset for register alu op"}; if (inst.opcode & INST_SRC_REG) { if (inst.imm != 0) throw InvalidInstruction{pc, "nonzero imm for register alu op"}; diff --git a/src/test/test_conformance.cpp b/src/test/test_conformance.cpp index 73d3e6c5c..c7a681cfb 100644 --- a/src/test/test_conformance.cpp +++ b/src/test/test_conformance.cpp @@ -13,7 +13,7 @@ void test_conformance(std::string filename, bpf_conformance_test_result_t expect std::filesystem::path plugin_path = test_path.remove_filename().append("conformance_check" + extension.string()).string(); std::map> result = - bpf_conformance(test_files, plugin_path, {}, {}, {}, bpf_conformance_test_CPU_version_t::v3, + bpf_conformance(test_files, plugin_path, {}, {}, {}, bpf_conformance_test_CPU_version_t::v4, bpf_conformance_list_instructions_t::LIST_INSTRUCTIONS_NONE, true); for (auto file : test_files) { auto& [file_result, reason] = result[file]; @@ -34,21 +34,21 @@ void test_conformance(std::string filename, bpf_conformance_test_result_t expect // legitimate programs from being usable. #define TEST_CONFORMANCE_VERIFICATION_FAILED(filename) \ TEST_CASE("conformance_check " filename, "[conformance]") { \ - test_conformance(filename, bpf_conformance_test_result_t::TEST_RESULT_FAIL, "Verification failed"); \ + test_conformance(filename, bpf_conformance_test_result_t::TEST_RESULT_ERROR, "Verification failed"); \ } // Any tests that return top are safe, but are not as precise as they // could be and so may prevent legitimate programs from being usable. #define TEST_CONFORMANCE_TOP(filename) \ TEST_CASE("conformance_check " filename, "[conformance]") { \ - test_conformance(filename, bpf_conformance_test_result_t::TEST_RESULT_FAIL, "Couldn't determine r0 value"); \ + test_conformance(filename, bpf_conformance_test_result_t::TEST_RESULT_ERROR, "Couldn't determine r0 value"); \ } // Any tests that return a range are safe, but are not as precise as they // could be and so may prevent legitimate programs from being usable. #define TEST_CONFORMANCE_RANGE(filename, range) \ TEST_CASE("conformance_check " filename, "[conformance]") { \ - test_conformance(filename, bpf_conformance_test_result_t::TEST_RESULT_FAIL, "r0 value is range " range); \ + test_conformance(filename, bpf_conformance_test_result_t::TEST_RESULT_ERROR, "r0 value is range " range); \ } TEST_CONFORMANCE("add.data") @@ -74,8 +74,9 @@ TEST_CONFORMANCE("be16.data") TEST_CONFORMANCE("be32-high.data") TEST_CONFORMANCE("be32.data") TEST_CONFORMANCE("be64.data") +TEST_CONFORMANCE_VERIFICATION_FAILED("call_local.data") TEST_CONFORMANCE("call_unwind_fail.data") -TEST_CONFORMANCE("div-by-zero-reg.data") +TEST_CONFORMANCE("div32-by-zero-reg.data") TEST_CONFORMANCE("div32-high-divisor.data") TEST_CONFORMANCE("div32-imm.data") TEST_CONFORMANCE("div32-reg.data") @@ -149,6 +150,14 @@ TEST_CONFORMANCE_VERIFICATION_FAILED("lock_and.data") TEST_CONFORMANCE_VERIFICATION_FAILED("lock_and32.data") TEST_CONFORMANCE_VERIFICATION_FAILED("lock_cmpxchg.data") TEST_CONFORMANCE_VERIFICATION_FAILED("lock_cmpxchg32.data") +TEST_CONFORMANCE_VERIFICATION_FAILED("lock_fetch_add.data") +TEST_CONFORMANCE_VERIFICATION_FAILED("lock_fetch_add32.data") +TEST_CONFORMANCE_VERIFICATION_FAILED("lock_fetch_and.data") +TEST_CONFORMANCE_VERIFICATION_FAILED("lock_fetch_and32.data") +TEST_CONFORMANCE_VERIFICATION_FAILED("lock_fetch_or.data") +TEST_CONFORMANCE_VERIFICATION_FAILED("lock_fetch_or32.data") +TEST_CONFORMANCE_VERIFICATION_FAILED("lock_fetch_xor.data") +TEST_CONFORMANCE_VERIFICATION_FAILED("lock_fetch_xor32.data") TEST_CONFORMANCE_VERIFICATION_FAILED("lock_or.data") TEST_CONFORMANCE_VERIFICATION_FAILED("lock_or32.data") TEST_CONFORMANCE_VERIFICATION_FAILED("lock_xchg.data") @@ -195,6 +204,12 @@ TEST_CONFORMANCE("rsh64-imm-neg.data") TEST_CONFORMANCE("rsh64-reg.data") TEST_CONFORMANCE("rsh64-reg-high.data") TEST_CONFORMANCE("rsh64-reg-neg.data") +TEST_CONFORMANCE("sdiv32-by-zero-reg.data") +TEST_CONFORMANCE("sdiv32-imm.data") +TEST_CONFORMANCE("sdiv32-reg.data") +TEST_CONFORMANCE("sdiv64-by-zero-reg.data") +TEST_CONFORMANCE("sdiv64-imm.data") +TEST_CONFORMANCE("sdiv64-reg.data") TEST_CONFORMANCE("stack.data") TEST_CONFORMANCE("stb.data") TEST_CONFORMANCE("stdw.data") From 75fd8c9eae91cf03014d0b32ed0acf045df045a1 Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Sat, 13 Jan 2024 09:35:42 -0800 Subject: [PATCH 025/373] Try apt-get update in CI/CD To see if it fixes the build issue Signed-off-by: Dave Thaler --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b4b3f8e6c..85fb55f56 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,6 +26,7 @@ jobs: steps: - name: Install dependencies run: | + sudo apt-get update sudo apt install libboost-dev libboost-filesystem-dev libboost-program-options-dev libyaml-cpp-dev valgrind - uses: actions/checkout@v4 From 112eb88ffa4b3fe6e7554c2b5db9c4096b308e20 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 13 Jan 2024 22:58:18 +0000 Subject: [PATCH 026/373] Bump ebpf-samples from `e052151` to `29574d4` Bumps [ebpf-samples](https://github.com/vbpf/ebpf-samples) from `e052151` to `29574d4`. - [Release notes](https://github.com/vbpf/ebpf-samples/releases) - [Commits](https://github.com/vbpf/ebpf-samples/compare/e052151f8cf3b3d65bfaade6255318a566457fbb...29574d49653bc07543379d66a46752cbd09debcf) --- updated-dependencies: - dependency-name: ebpf-samples dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- ebpf-samples | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebpf-samples b/ebpf-samples index e052151f8..29574d496 160000 --- a/ebpf-samples +++ b/ebpf-samples @@ -1 +1 @@ -Subproject commit e052151f8cf3b3d65bfaade6255318a566457fbb +Subproject commit 29574d49653bc07543379d66a46752cbd09debcf From 7311c35212c67bf328d8516505ffad5070351431 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 13 Jan 2024 22:59:06 +0000 Subject: [PATCH 027/373] Bump external/libbtf from `f5fab5a` to `fdb1bc9` Bumps [external/libbtf](https://github.com/Alan-Jowett/libbtf) from `f5fab5a` to `fdb1bc9`. - [Release notes](https://github.com/Alan-Jowett/libbtf/releases) - [Commits](https://github.com/Alan-Jowett/libbtf/compare/f5fab5a0b36869c104fa45a6ffb6c87295a22888...fdb1bc97410cd784f230257aa1d07c63ed926d2b) --- updated-dependencies: - dependency-name: external/libbtf dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/libbtf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/libbtf b/external/libbtf index f5fab5a0b..fdb1bc974 160000 --- a/external/libbtf +++ b/external/libbtf @@ -1 +1 @@ -Subproject commit f5fab5a0b36869c104fa45a6ffb6c87295a22888 +Subproject commit fdb1bc97410cd784f230257aa1d07c63ed926d2b From cfc0fe8c25669b2268de525c83a6796ea0ffc2e8 Mon Sep 17 00:00:00 2001 From: Alan Jowett Date: Fri, 12 Jan 2024 10:31:07 -0800 Subject: [PATCH 028/373] Pickup update libbtf that supports anonymous inner maps Signed-off-by: Alan Jowett --- src/test/test_verify.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/test_verify.cpp b/src/test/test_verify.cpp index 450a2acbb..f1c546fb0 100644 --- a/src/test/test_verify.cpp +++ b/src/test/test_verify.cpp @@ -465,6 +465,7 @@ TEST_SECTION("build", "packet_start_ok.o", "xdp") TEST_SECTION("build", "packet_access.o", "xdp") TEST_SECTION("build", "tail_call.o", "xdp_prog") TEST_SECTION("build", "map_in_map.o", ".text") +TEST_SECTION("build", "map_in_map_anonymous.o", ".text") TEST_SECTION("build", "map_in_map_legacy.o", ".text") TEST_SECTION("build", "twomaps.o", ".text"); TEST_SECTION("build", "twostackvars.o", ".text"); From 7cfa6a56c8eb7ab24bcc63cb6543c002a1db87be Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Tue, 16 Jan 2024 09:50:17 -0800 Subject: [PATCH 029/373] Update to latest bpf_conformance And add new ja32.data test Signed-off-by: Dave Thaler --- external/bpf_conformance | 2 +- src/test/test_conformance.cpp | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/external/bpf_conformance b/external/bpf_conformance index 13de786ef..a309ef6d3 160000 --- a/external/bpf_conformance +++ b/external/bpf_conformance @@ -1 +1 @@ -Subproject commit 13de786efe55fb9c456561cf3ca1de4f51f590de +Subproject commit a309ef6d384e4395e5315e19c17b47b94fa96672 diff --git a/src/test/test_conformance.cpp b/src/test/test_conformance.cpp index c7a681cfb..b453685c9 100644 --- a/src/test/test_conformance.cpp +++ b/src/test/test_conformance.cpp @@ -87,6 +87,7 @@ TEST_CONFORMANCE("div64-negative-reg.data") TEST_CONFORMANCE("div64-reg.data") TEST_CONFORMANCE("exit-not-last.data") TEST_CONFORMANCE("exit.data") +TEST_CONFORMANCE("ja32.data") TEST_CONFORMANCE("jeq-imm.data") TEST_CONFORMANCE("jeq-reg.data") TEST_CONFORMANCE("jeq32-imm.data") @@ -184,6 +185,16 @@ TEST_CONFORMANCE("mod64-by-zero-reg.data") TEST_CONFORMANCE("mod64.data") TEST_CONFORMANCE("mov.data") TEST_CONFORMANCE("mov64-sign-extend.data") +TEST_CONFORMANCE("movsx1632-imm.data") +TEST_CONFORMANCE("movsx1632-reg.data") +TEST_CONFORMANCE("movsx1664-imm.data") +TEST_CONFORMANCE("movsx1664-reg.data") +TEST_CONFORMANCE("movsx3264-imm.data") +TEST_CONFORMANCE("movsx3264-reg.data") +TEST_CONFORMANCE("movsx832-imm.data") +TEST_CONFORMANCE("movsx832-reg.data") +TEST_CONFORMANCE("movsx864-imm.data") +TEST_CONFORMANCE("movsx864-reg.data") TEST_CONFORMANCE("mul32-imm.data") TEST_CONFORMANCE("mul32-reg-overflow.data") TEST_CONFORMANCE("mul32-reg.data") @@ -222,4 +233,7 @@ TEST_CONFORMANCE("stxb.data") TEST_CONFORMANCE("stxdw.data") TEST_CONFORMANCE("stxh.data") TEST_CONFORMANCE("stxw.data") +TEST_CONFORMANCE("swap16.data") +TEST_CONFORMANCE("swap32.data") +TEST_CONFORMANCE("swap64.data") TEST_CONFORMANCE("subnet.data") From 28bcbbb36fd099f3468351d57bf4eb3c6c68a65d Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Tue, 16 Jan 2024 15:32:40 -0800 Subject: [PATCH 030/373] Add smod conformance tests Signed-off-by: Dave Thaler --- src/test/test_conformance.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/test_conformance.cpp b/src/test/test_conformance.cpp index b453685c9..ac7a308c3 100644 --- a/src/test/test_conformance.cpp +++ b/src/test/test_conformance.cpp @@ -215,12 +215,30 @@ TEST_CONFORMANCE("rsh64-imm-neg.data") TEST_CONFORMANCE("rsh64-reg.data") TEST_CONFORMANCE("rsh64-reg-high.data") TEST_CONFORMANCE("rsh64-reg-neg.data") +TEST_CONFORMANCE("sdiv32-by-zero-imm.data") TEST_CONFORMANCE("sdiv32-by-zero-reg.data") TEST_CONFORMANCE("sdiv32-imm.data") TEST_CONFORMANCE("sdiv32-reg.data") +TEST_CONFORMANCE("sdiv64-by-zero-imm.data") TEST_CONFORMANCE("sdiv64-by-zero-reg.data") TEST_CONFORMANCE("sdiv64-imm.data") TEST_CONFORMANCE("sdiv64-reg.data") +TEST_CONFORMANCE("smod32-neg-by-neg-imm.data") +TEST_CONFORMANCE("smod32-neg-by-neg-reg.data") +TEST_CONFORMANCE("smod32-neg-by-pos-imm.data") +TEST_CONFORMANCE("smod32-neg-by-pos-reg.data") +TEST_CONFORMANCE("smod32-neg-by-zero-imm.data") +TEST_CONFORMANCE("smod32-neg-by-zero-reg.data") +TEST_CONFORMANCE("smod32-pos-by-neg-imm.data") +TEST_CONFORMANCE("smod32-pos-by-neg-reg.data") +TEST_CONFORMANCE("smod64-neg-by-neg-imm.data") +TEST_CONFORMANCE("smod64-neg-by-neg-reg.data") +TEST_CONFORMANCE("smod64-neg-by-pos-imm.data") +TEST_CONFORMANCE("smod64-neg-by-pos-reg.data") +TEST_CONFORMANCE("smod64-neg-by-zero-imm.data") +TEST_CONFORMANCE("smod64-neg-by-zero-reg.data") +TEST_CONFORMANCE("smod64-pos-by-neg-imm.data") +TEST_CONFORMANCE("smod64-pos-by-neg-reg.data") TEST_CONFORMANCE("stack.data") TEST_CONFORMANCE("stb.data") TEST_CONFORMANCE("stdw.data") From 93ac334ae36cb78c75fc056dd3fe34c9d50b9337 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Jan 2024 16:42:20 +0000 Subject: [PATCH 031/373] Bump external/libbtf from `fdb1bc9` to `d0938e0` Bumps [external/libbtf](https://github.com/Alan-Jowett/libbtf) from `fdb1bc9` to `d0938e0`. - [Release notes](https://github.com/Alan-Jowett/libbtf/releases) - [Commits](https://github.com/Alan-Jowett/libbtf/compare/fdb1bc97410cd784f230257aa1d07c63ed926d2b...d0938e01fe143b632a13bce712605eba33e2169d) --- updated-dependencies: - dependency-name: external/libbtf dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/libbtf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/libbtf b/external/libbtf index fdb1bc974..d0938e01f 160000 --- a/external/libbtf +++ b/external/libbtf @@ -1 +1 @@ -Subproject commit fdb1bc97410cd784f230257aa1d07c63ed926d2b +Subproject commit d0938e01fe143b632a13bce712605eba33e2169d From 8b0c660ee4201067787b2177e5d727c16632220d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Jan 2024 16:42:23 +0000 Subject: [PATCH 032/373] Bump ebpf-samples from `29574d4` to `ce6d479` Bumps [ebpf-samples](https://github.com/vbpf/ebpf-samples) from `29574d4` to `ce6d479`. - [Release notes](https://github.com/vbpf/ebpf-samples/releases) - [Commits](https://github.com/vbpf/ebpf-samples/compare/29574d49653bc07543379d66a46752cbd09debcf...ce6d4793d8b851683a86c6af675a02e39f91f0db) --- updated-dependencies: - dependency-name: ebpf-samples dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- ebpf-samples | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebpf-samples b/ebpf-samples index 29574d496..ce6d4793d 160000 --- a/ebpf-samples +++ b/ebpf-samples @@ -1 +1 @@ -Subproject commit 29574d49653bc07543379d66a46752cbd09debcf +Subproject commit ce6d4793d8b851683a86c6af675a02e39f91f0db From 47d78f4d26ce6d0d9ddb64184986cddb83b6a03d Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Thu, 25 Jan 2024 02:01:34 -0800 Subject: [PATCH 033/373] Add more negative unmarshaling tests (#576) * Add more negative unmarshaling test cases * Bug fixes * Test invalid opcodes and src0 opcodes * Test invalid imm0 opcodes * Test invalid off0 opcodes Signed-off-by: Dave Thaler --- src/asm_marshal.cpp | 8 +-- src/asm_unmarshal.cpp | 88 ++++++++++++++++++++++-------- src/ebpf_vm_isa.hpp | 12 +++-- src/test/test_marshal.cpp | 111 +++++++++++++++++++++++++------------- 4 files changed, 152 insertions(+), 67 deletions(-) diff --git a/src/asm_marshal.cpp b/src/asm_marshal.cpp index 1a308bc93..5b42c5914 100644 --- a/src/asm_marshal.cpp +++ b/src/asm_marshal.cpp @@ -133,7 +133,7 @@ struct MarshalVisitor { case Un::Op::NEG: return {ebpf_inst{ // FIX: should be INST_CLS_ALU / INST_CLS_ALU64 - .opcode = static_cast(INST_CLS_ALU | 0x3 | (0x8 << 4)), + .opcode = static_cast(INST_CLS_ALU | 0x3 | INST_ALU_OP_NEG), .dst = b.dst.v, .src = 0, .offset = 0, @@ -143,7 +143,7 @@ struct MarshalVisitor { case Un::Op::LE32: case Un::Op::LE64: return {ebpf_inst{ - .opcode = static_cast(INST_CLS_ALU | (0xd << 4)), + .opcode = static_cast(INST_CLS_ALU | INST_ALU_OP_END), .dst = b.dst.v, .src = 0, .offset = 0, @@ -153,7 +153,7 @@ struct MarshalVisitor { case Un::Op::BE32: case Un::Op::BE64: return {ebpf_inst{ - .opcode = static_cast(INST_CLS_ALU | 0x8 | (0xd << 4)), + .opcode = static_cast(INST_CLS_ALU | INST_END_BE | INST_ALU_OP_END), .dst = b.dst.v, .src = 0, .offset = 0, @@ -163,7 +163,7 @@ struct MarshalVisitor { case Un::Op::SWAP32: case Un::Op::SWAP64: return {ebpf_inst{ - .opcode = static_cast(INST_CLS_ALU64 | (0xd << 4)), + .opcode = static_cast(INST_CLS_ALU64 | INST_ALU_OP_END), .dst = b.dst.v, .src = 0, .offset = 0, diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index f11697bef..9560d0320 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -40,9 +40,17 @@ void compare(const string& field, T actual, T expected) { std::cerr << field << ": (actual) " << std::hex << (int)actual << " != " << (int)expected << " (expected)\n"; } +static std::string make_opcode_message(const char* msg, uint8_t opcode) { + std::ostringstream oss; + oss << msg << " op 0x" << std::hex << (int)opcode; + return oss.str(); +} + struct InvalidInstruction : std::invalid_argument { size_t pc; explicit InvalidInstruction(size_t pc, const char* what) : std::invalid_argument{what}, pc{pc} {} + InvalidInstruction(size_t pc, std::string what) : std::invalid_argument{what}, pc{pc} {} + InvalidInstruction(size_t pc, uint8_t opcode) : std::invalid_argument{make_opcode_message("Bad instruction", opcode)}, pc{pc} {} }; struct UnsupportedMemoryMode : std::invalid_argument { @@ -113,7 +121,7 @@ struct Unmarshaller { // All the rest require a zero offset. if (inst.offset != 0) - throw InvalidInstruction{pc, "nonzero offset for register alu op"}; + throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode)); switch (inst.opcode & INST_ALU_OP_MASK) { case INST_ALU_OP_ADD: return Bin::Op::ADD; @@ -123,16 +131,26 @@ struct Unmarshaller { case INST_ALU_OP_AND: return Bin::Op::AND; case INST_ALU_OP_LSH: return Bin::Op::LSH; case INST_ALU_OP_RSH: return Bin::Op::RSH; - case INST_ALU_OP_NEG: return Un::Op::NEG; + case INST_ALU_OP_NEG: + // Negation is a unary operation. The SRC bit, src, and imm must be all 0. + if (inst.opcode & INST_SRC_REG) + throw InvalidInstruction{pc, inst.opcode}; + if (inst.src != 0) + throw InvalidInstruction{pc, make_opcode_message("nonzero src for register", inst.opcode)}; + if (inst.imm != 0) + throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode)); + return Un::Op::NEG; case INST_ALU_OP_XOR: return Bin::Op::XOR; case INST_ALU_OP_ARSH: if ((inst.opcode & INST_CLS_MASK) == INST_CLS_ALU) note("arsh32 is not allowed"); return Bin::Op::ARSH; case INST_ALU_OP_END: + if (inst.src != 0) + throw InvalidInstruction{pc, make_opcode_message("nonzero src for register", inst.opcode)}; if ((inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64) { if (inst.opcode & INST_END_BE) - throw InvalidInstruction(pc, "invalid endian immediate"); + throw InvalidInstruction(pc, inst.opcode); switch (inst.imm) { case 16: return Un::Op::SWAP16; case 32: return Un::Op::SWAP32; @@ -147,8 +165,8 @@ struct Unmarshaller { default: throw InvalidInstruction(pc, "invalid endian immediate"); } - case 0xe0: throw InvalidInstruction{pc, "invalid ALU op 0xe0"}; - case 0xf0: throw InvalidInstruction{pc, "invalid ALU op 0xf0"}; + case 0xe0: throw InvalidInstruction{pc, inst.opcode}; + case 0xf0: throw InvalidInstruction{pc, inst.opcode}; } return {}; } @@ -160,11 +178,11 @@ struct Unmarshaller { auto getBinValue(pc_t pc, ebpf_inst inst) -> Value { if (inst.opcode & INST_SRC_REG) { if (inst.imm != 0) - throw InvalidInstruction{pc, "nonzero imm for register alu op"}; + throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode)); return Reg{inst.src}; } else { if (inst.src != 0) - throw InvalidInstruction{pc, "nonzero src for register alu op"}; + throw InvalidInstruction{pc, make_opcode_message("nonzero src for register", inst.opcode)}; // Imm is a signed 32-bit number. Sign extend it to 64-bits for storage. return Imm{sign_extend(inst.imm)}; } @@ -187,7 +205,8 @@ struct Unmarshaller { case 0xb: return Op::LE; case 0xc: return Op::SLT; case 0xd: return Op::SLE; - case 0xe: throw InvalidInstruction(pc, "invalid JMP op 0xe"); + case 0xe: throw InvalidInstruction(pc, opcode); + case 0xf: throw InvalidInstruction(pc, opcode); } return {}; } @@ -199,8 +218,7 @@ struct Unmarshaller { int width = getMemWidth(inst.opcode); bool isLD = (inst.opcode & INST_CLS_MASK) == INST_CLS_LD; switch ((inst.opcode & INST_MODE_MASK) >> 5) { - case 0: - throw InvalidInstruction(pc, "Bad instruction"); + case 0: throw InvalidInstruction(pc, inst.opcode); case INST_ABS: if (!isLD) throw InvalidInstruction(pc, "ABS but not LD"); @@ -217,11 +235,15 @@ struct Unmarshaller { case INST_MEM: { if (isLD) - throw InvalidInstruction(pc, "plain LD"); + throw InvalidInstruction(pc, inst.opcode); bool isLoad = getMemIsLoad(inst.opcode); if (isLoad && inst.dst == R10_STACK_POINTER) throw InvalidInstruction(pc, "Cannot modify r10"); bool isImm = !(inst.opcode & 1); + if (isImm && inst.src != 0) + throw InvalidInstruction(pc, make_opcode_message("nonzero src for register", inst.opcode)); + if (!isImm && inst.imm != 0) + throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode)); assert(!(isLoad && isImm)); uint8_t basereg = isLoad ? inst.src : inst.dst; @@ -247,7 +269,7 @@ struct Unmarshaller { if (((inst.opcode & INST_CLS_MASK) != INST_CLS_STX) || ((inst.opcode & INST_SIZE_MASK) != INST_SIZE_W && (inst.opcode & INST_SIZE_MASK) != INST_SIZE_DW)) - throw InvalidInstruction(pc, "Bad instruction"); + throw InvalidInstruction(pc, inst.opcode); if (inst.imm != 0) throw InvalidInstruction(pc, "Unsupported atomic instruction"); return LockAdd{ @@ -259,8 +281,7 @@ struct Unmarshaller { }, .valreg = Reg{inst.src}, }; - default: - throw InvalidInstruction(pc, "Bad instruction"); + default: throw InvalidInstruction(pc, inst.opcode); } return {}; } @@ -382,21 +403,44 @@ struct Unmarshaller { auto makeJmp(ebpf_inst inst, const vector& insts, pc_t pc) -> Instruction { switch ((inst.opcode >> 4) & 0xF) { - case 0x8: + case INST_CALL: + if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP) + throw InvalidInstruction(pc, inst.opcode); if (inst.opcode & INST_SRC_REG) - throw InvalidInstruction(pc, "Bad instruction"); + throw InvalidInstruction(pc, inst.opcode); + if (inst.offset != 0) + throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode)); if (!info.platform->is_helper_usable(inst.imm)) throw InvalidInstruction(pc, "invalid helper function id"); return makeCall(inst.imm); - case 0x9: - if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP) - throw InvalidInstruction(pc, "Bad instruction"); + case INST_EXIT: + if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP || (inst.opcode & INST_SRC_REG)) + throw InvalidInstruction(pc, inst.opcode); + if (inst.src != 0) + throw InvalidInstruction(pc, make_opcode_message("nonzero src for register", inst.opcode)); + if (inst.imm != 0) + throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode)); + if (inst.offset != 0) + throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode)); return Exit{}; - case 0x0: + case INST_JA: if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP && (inst.opcode & INST_CLS_MASK) != INST_CLS_JMP32) - throw InvalidInstruction(pc, "Bad instruction"); + throw InvalidInstruction(pc, inst.opcode); + if (inst.opcode & INST_SRC_REG) + throw InvalidInstruction(pc, inst.opcode); + if ((inst.opcode & INST_CLS_MASK) == INST_CLS_JMP && (inst.imm != 0)) + throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode)); + if ((inst.opcode & INST_CLS_MASK) == INST_CLS_JMP32 && (inst.offset != 0)) + throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode)); default: { + // First validate the opcode, src, and imm. + auto op = getJmpOp(pc, inst.opcode); + if (!(inst.opcode & INST_SRC_REG) && (inst.src != 0)) + throw InvalidInstruction(pc, make_opcode_message("nonzero src for register", inst.opcode)); + if ((inst.opcode & INST_SRC_REG) && (inst.imm != 0)) + throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode)); + int32_t offset = (inst.opcode == INST_OP_JA32) ? inst.imm : inst.offset; pc_t new_pc = pc + 1 + offset; if (new_pc >= insts.size()) @@ -407,7 +451,7 @@ struct Unmarshaller { auto cond = (inst.opcode == INST_OP_JA16 || inst.opcode == INST_OP_JA32) ? std::optional{} : Condition{ - .op = getJmpOp(pc, inst.opcode), + .op = op, .left = Reg{inst.dst}, .right = (inst.opcode & INST_SRC_REG) ? (Value)Reg{inst.src} : Imm{sign_extend(inst.imm)}, diff --git a/src/ebpf_vm_isa.hpp b/src/ebpf_vm_isa.hpp index 654e34525..ad308b9af 100644 --- a/src/ebpf_vm_isa.hpp +++ b/src/ebpf_vm_isa.hpp @@ -54,10 +54,14 @@ enum { INST_OP_LDDW_IMM = (INST_CLS_LD | INST_SRC_IMM | INST_SIZE_DW), // Special - INST_OP_JA32 = (INST_CLS_JMP32 | 0x00), - INST_OP_JA16 = (INST_CLS_JMP | 0x00), - INST_OP_CALL = (INST_CLS_JMP | 0x80), - INST_OP_EXIT = (INST_CLS_JMP | 0x90), + INST_JA = 0x0, + INST_CALL = 0x8, + INST_EXIT = 0x9, + + INST_OP_JA32 = ((INST_JA << 4) | INST_CLS_JMP32), + INST_OP_JA16 = ((INST_JA << 4) | INST_CLS_JMP), + INST_OP_CALL = ((INST_CALL << 4) | INST_CLS_JMP), + INST_OP_EXIT = ((INST_EXIT << 4) | INST_CLS_JMP), INST_ALU_OP_ADD = 0x00, INST_ALU_OP_SUB = 0x10, diff --git a/src/test/test_marshal.cpp b/src/test/test_marshal.cpp index 33773791f..20700855f 100644 --- a/src/test/test_marshal.cpp +++ b/src/test/test_marshal.cpp @@ -233,45 +233,82 @@ TEST_CASE("disasm_marshal_Mem", "[disasm][marshal]") { } } -TEST_CASE("fail unmarshal", "[disasm][marshal]") { - check_unmarshal_fail(ebpf_inst{.opcode = ((INST_MEM << 5) | INST_SIZE_B | INST_CLS_LDX), .dst = 11, .imm = 8}, +TEST_CASE("fail unmarshal invalid opcodes", "[disasm][marshal]") { + // The following opcodes are undefined and should generate bad instruction errors. + uint8_t bad_opcodes[] = { + 0x00, 0x01, 0x02, 0x03, 0x08, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x10, 0x11, 0x12, 0x13, 0x19, 0x1a, 0x1b, 0x60, + 0x68, 0x70, 0x78, 0x80, 0x81, 0x82, 0x83, 0x86, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, + 0x92, 0x93, 0x96, 0x98, 0x99, 0x9a, 0x9b, 0x9d, 0x9e, 0xa0, 0xa1, 0xa2, 0xa3, 0xa8, 0xa9, 0xaa, 0xab, 0xb0, + 0xb1, 0xb2, 0xb3, 0xb8, 0xb9, 0xba, 0xbb, 0xc0, 0xc1, 0xc2, 0xc8, 0xc9, 0xca, 0xcb, 0xd0, 0xd1, 0xd2, 0xd3, + 0xd8, 0xd9, 0xda, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, + 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff}; + for (int i = 0; i < sizeof(bad_opcodes); i++) { + std::ostringstream oss; + oss << "0: Bad instruction op 0x" << std::hex << (int)bad_opcodes[i] << std::endl; + check_unmarshal_fail(ebpf_inst{.opcode = bad_opcodes[i]}, oss.str().c_str()); + } +} + +TEST_CASE("fail unmarshal src0 opcodes", "[disasm][marshal]") { + // The following opcodes are only defined for src = 0. + uint8_t src0_opcodes[] = {0x04, 0x05, 0x06, 0x07, 0x14, 0x15, 0x16, 0x17, 0x24, 0x25, 0x26, 0x27, 0x34, 0x35, 0x36, + 0x37, 0x44, 0x45, 0x46, 0x47, 0x54, 0x55, 0x56, 0x57, 0x62, 0x64, 0x65, 0x66, 0x67, 0x6a, + 0x72, 0x74, 0x75, 0x76, 0x77, 0x7a, 0x84, 0x87, 0x94, 0x95, 0x97, 0xa4, 0xa5, 0xa6, 0xa7, + 0xb4, 0xb5, 0xb6, 0xb7, 0xc4, 0xc5, 0xc6, 0xc7, 0xd4, 0xd5, 0xd6, 0xd7, 0xdc}; + for (int i = 0; i < sizeof(src0_opcodes); i++) { + std::ostringstream oss; + oss << "0: nonzero src for register op 0x" << std::hex << (int)src0_opcodes[i] << std::endl; + check_unmarshal_fail(ebpf_inst{.opcode = src0_opcodes[i], .src = 1}, oss.str().c_str()); + } +} + +TEST_CASE("fail unmarshal imm0 opcodes", "[disasm][marshal]") { + // The following opcodes are only defined for imm = 0. + uint8_t imm0_opcodes[] = {0x05, 0x0c, 0x0f, 0x1c, 0x1d, 0x1e, 0x1f, 0x2c, 0x2d, 0x2e, 0x2f, 0x3d, 0x3e, 0x3f, 0x4c, + 0x4d, 0x4e, 0x4f, 0x5c, 0x5d, 0x5e, 0x5f, 0x61, 0x63, 0x69, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x71, 0x73, 0x79, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x84, 0x87, 0x95, 0x9c, 0x9f, 0xac, 0xad, + 0xae, 0xaf, 0xbc, 0xbd, 0xbe, 0xbf, 0xcc, 0xcd, 0xce, 0xcf, 0xdd, 0xde}; + for (int i = 0; i < sizeof(imm0_opcodes); i++) { + std::ostringstream oss; + oss << "0: nonzero imm for op 0x" << std::hex << (int)imm0_opcodes[i] << std::endl; + check_unmarshal_fail(ebpf_inst{.opcode = imm0_opcodes[i], .imm = 1}, oss.str().c_str()); + } +} + +TEST_CASE("fail unmarshal off0 opcodes", "[disasm][marshal]") { + // The following opcodes are only defined for offset = 0. + uint8_t off0_opcodes[] = {0x04, 0x06, 0x07, 0x0c, 0x0f, 0x14, 0x17, 0x1c, 0x1f, 0x24, 0x27, 0x2c, 0x2f, 0x44, 0x47, + 0x4c, 0x4f, 0x54, 0x57, 0x5c, 0x5f, 0x64, 0x67, 0x6c, 0x6f, 0x74, 0x77, 0x7c, 0x7f, 0x84, + 0x85, 0x87, 0x95, 0xa4, 0xa7, 0xac, 0xaf, 0xc4, 0xc7, 0xcc, 0xcf, 0xd4, 0xd7, 0xdc}; + for (int i = 0; i < sizeof(off0_opcodes); i++) { + std::ostringstream oss; + oss << "0: nonzero offset for op 0x" << std::hex << (int)off0_opcodes[i] << std::endl; + check_unmarshal_fail(ebpf_inst{.opcode = off0_opcodes[i], .offset = 1}, oss.str().c_str()); + } +} + +TEST_CASE("fail unmarshal misc", "[disasm][marshal]") { + check_unmarshal_fail(ebpf_inst{.opcode = /* 0x06 */ INST_CLS_JMP32}, "0: jump out of bounds\n"); + check_unmarshal_fail(ebpf_inst{.opcode = /* 0x16 */ 0x10 | INST_CLS_JMP32}, "0: jump out of bounds\n"); + check_unmarshal_fail(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM}, "0: incomplete LDDW\n"); + check_unmarshal_fail(ebpf_inst{.opcode = /* 0x21 */ (INST_ABS << 5) | INST_SIZE_W | INST_CLS_LDX, .imm = 8}, + "0: ABS but not LD\n"); + check_unmarshal_fail(ebpf_inst{.opcode = /* 0x41 */ (INST_IND << 5) | INST_SIZE_W | INST_CLS_LDX, .imm = 8}, + "0: IND but not LD\n"); + check_unmarshal_fail(ebpf_inst{.opcode = /* 0x71 */ ((INST_MEM << 5) | INST_SIZE_B | INST_CLS_LDX), .dst = 11, .imm = 8}, "0: Bad register\n"); - check_unmarshal_fail(ebpf_inst{.opcode = ((INST_MEM << 5) | INST_SIZE_B | INST_CLS_LDX), .dst = 1, .src = 11}, + check_unmarshal_fail(ebpf_inst{.opcode = /* 0x71 */ ((INST_MEM << 5) | INST_SIZE_B | INST_CLS_LDX), .dst = 1, .src = 11}, "0: Bad register\n"); - check_unmarshal_fail(ebpf_inst{.opcode = (INST_ALU_OP_MOV | INST_SRC_IMM | INST_CLS_ALU), .dst = 11, .imm = 8}, + check_unmarshal_fail(ebpf_inst{.opcode = /* 0xb4 */ (INST_ALU_OP_MOV | INST_SRC_IMM | INST_CLS_ALU), .dst = 11, .imm = 8}, "0: Bad register\n"); - check_unmarshal_fail(ebpf_inst{.opcode = (INST_ALU_OP_MOV | INST_SRC_REG | INST_CLS_ALU), .dst = 1, .src = 11}, + check_unmarshal_fail(ebpf_inst{.opcode = /* 0xbc */ (INST_ALU_OP_MOV | INST_SRC_REG | INST_CLS_ALU), .dst = 1, .src = 11}, "0: Bad register\n"); - check_unmarshal_fail(ebpf_inst{.opcode = ((INST_MEM << 5) | INST_SIZE_W | INST_CLS_LD)}, - "0: plain LD\n"); - check_unmarshal_fail(ebpf_inst{.opcode = INST_ALU_OP_END | INST_END_LE | INST_CLS_ALU, .dst = 1, .imm = 8}, "0: invalid endian immediate\n"); - check_unmarshal_fail(ebpf_inst{.opcode = INST_ALU_OP_END | INST_END_BE | INST_CLS_ALU, .dst = 1, .imm = 8}, "0: invalid endian immediate\n"); - check_unmarshal_fail(ebpf_inst{}, "0: Bad instruction\n"); - check_unmarshal_fail(ebpf_inst{.opcode = INST_CLS_LDX}, "0: Bad instruction\n"); - check_unmarshal_fail(ebpf_inst{.opcode = ((INST_XADD << 5) | INST_CLS_ST)}, "0: Bad instruction\n"); - check_unmarshal_fail(ebpf_inst{.opcode = ((INST_XADD << 5) | INST_SIZE_B | INST_CLS_STX)}, "0: Bad instruction\n"); - check_unmarshal_fail(ebpf_inst{.opcode = ((INST_XADD << 5) | INST_SIZE_H | INST_CLS_STX)}, "0: Bad instruction\n"); - check_unmarshal_fail(ebpf_inst{.opcode = INST_OP_CALL | INST_SRC_REG}, "0: Bad instruction\n"); - check_unmarshal_fail(ebpf_inst{.opcode = INST_OP_LDDW_IMM}, "0: incomplete LDDW\n"); - check_unmarshal_fail(ebpf_inst{.opcode = INST_ALU_OP_ADD | INST_SRC_IMM | INST_CLS_ALU64, .offset = 8}, - "0: nonzero offset for register alu op\n"); - check_unmarshal_fail(ebpf_inst{.opcode = INST_ALU_OP_ADD | INST_SRC_IMM | INST_CLS_ALU64, .src = 8}, - "0: nonzero src for register alu op\n"); - check_unmarshal_fail(ebpf_inst{.opcode = INST_ALU_OP_ADD | INST_SRC_REG | INST_CLS_ALU64, .imm = 8}, - "0: nonzero imm for register alu op\n"); - check_unmarshal_fail(ebpf_inst{.opcode = (INST_ABS << 5) | INST_SIZE_W | INST_CLS_LDX, .imm = 8}, - "0: ABS but not LD\n"); - check_unmarshal_fail(ebpf_inst{.opcode = (INST_IND << 5) | INST_SIZE_W | INST_CLS_LDX, .imm = 8}, - "0: IND but not LD\n"); - check_unmarshal_fail(ebpf_inst{.opcode = (INST_LEN << 5) | INST_SIZE_W | INST_CLS_LDX, .imm = 8}, - "0: Bad instruction\n"); - check_unmarshal_fail(ebpf_inst{.opcode = (INST_MSH << 5) | INST_SIZE_W | INST_CLS_LDX, .imm = 8}, - "0: Bad instruction\n"); - check_unmarshal_fail(ebpf_inst{.opcode = (INST_MEM_UNUSED << 5) | INST_SIZE_W | INST_CLS_LDX, .imm = 8}, - "0: Bad instruction\n"); - check_unmarshal_fail(ebpf_inst{.opcode = INST_CLS_JMP32}, "0: jump out of bounds\n"); - check_unmarshal_fail(ebpf_inst{.opcode = 0x90 | INST_CLS_JMP32}, "0: Bad instruction\n"); - check_unmarshal_fail(ebpf_inst{.opcode = 0x10 | INST_CLS_JMP32}, "0: jump out of bounds\n"); - check_unmarshal_fail(ebpf_inst{.opcode = INST_ALU_OP_END | INST_CLS_ALU, .imm = 0}, "0: invalid endian immediate\n"); - check_unmarshal_fail(ebpf_inst{.opcode = INST_ALU_OP_END | INST_CLS_ALU64, .imm = 0}, "0: invalid endian immediate\n"); + check_unmarshal_fail(ebpf_inst{.opcode = /* 0xd4 */ INST_ALU_OP_END | INST_END_LE | INST_CLS_ALU, .dst = 1, .imm = 8}, + "0: invalid endian immediate\n"); + check_unmarshal_fail(ebpf_inst{.opcode = /* 0xd4 */ INST_ALU_OP_END | INST_END_LE | INST_CLS_ALU, .imm = 0}, + "0: invalid endian immediate\n"); + check_unmarshal_fail(ebpf_inst{.opcode = /* 0xd7 */ INST_ALU_OP_END | INST_END_LE | INST_CLS_ALU64, .imm = 0}, + "0: invalid endian immediate\n"); + check_unmarshal_fail(ebpf_inst{.opcode = /* 0xdc */ INST_ALU_OP_END | INST_END_BE | INST_CLS_ALU, .dst = 1, .imm = 8}, + "0: invalid endian immediate\n"); } From e8047ca06d16c844ef7f6863bc12a457b8884288 Mon Sep 17 00:00:00 2001 From: Alan Jowett Date: Mon, 22 Jan 2024 17:30:28 -0800 Subject: [PATCH 034/373] Add failing test for dependent read Signed-off-by: Alan Jowett --- ebpf-samples | 2 +- src/test/test_verify.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ebpf-samples b/ebpf-samples index ce6d4793d..9057f7de2 160000 --- a/ebpf-samples +++ b/ebpf-samples @@ -1 +1 @@ -Subproject commit ce6d4793d8b851683a86c6af675a02e39f91f0db +Subproject commit 9057f7de29fb9bd8eaf6144f7bbf0b2d6e545e0e diff --git a/src/test/test_verify.cpp b/src/test/test_verify.cpp index f1c546fb0..023d780cf 100644 --- a/src/test/test_verify.cpp +++ b/src/test/test_verify.cpp @@ -476,6 +476,7 @@ TEST_SECTION("build", "prog_array.o", ".text"); TEST_SECTION_REJECT("build", "badhelpercall.o", ".text") TEST_SECTION_REJECT("build", "ctxoffset.o", "sockops") TEST_SECTION_REJECT("build", "badmapptr.o", "test") +TEST_SECTION_FAIL("build", "dependent_read.o", "xdp") TEST_SECTION_REJECT("build", "exposeptr.o", ".text") TEST_SECTION_REJECT("build", "exposeptr2.o", ".text") TEST_SECTION_REJECT("build", "mapvalue-overrun.o", ".text") From f3964ef83f5cabf031f0647333a06551372af627 Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Thu, 25 Jan 2024 07:30:02 -0800 Subject: [PATCH 035/373] Update conformance suite to latest (#574) * Update bpf_conformance to latest * Remove movsx imm tests since movsx is only defined for register operands per latest mailing list discussion * Run new test cases added to conformance suite * Fix conformance bug --------- Signed-off-by: Dave Thaler --- external/bpf_conformance | 2 +- src/crab/ebpf_domain.cpp | 6 +++++- src/test/test_conformance.cpp | 6 +----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/external/bpf_conformance b/external/bpf_conformance index a309ef6d3..583758eee 160000 --- a/external/bpf_conformance +++ b/external/bpf_conformance @@ -1 +1 @@ -Subproject commit a309ef6d384e4395e5315e19c17b47b94fa96672 +Subproject commit 583758eee4907dd3a356a0f73939751415d46bf7 diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index d1ac9678d..dc90c2df6 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -2751,7 +2751,11 @@ void ebpf_domain_t::operator()(const Bin& bin) { default: break; } }); - havoc(dst.type); + if ((bin.dst.v != std::get(bin.v).v) || (type_inv.get_type(m_inv, dst.type) == T_UNINIT)) { + // Only forget the destination type if we're copying from a different register, + // or from the same uninitialized register. + havoc(dst.type); + } type_inv.assign_type(m_inv, bin.dst, std::get(bin.v)); break; } diff --git a/src/test/test_conformance.cpp b/src/test/test_conformance.cpp index ac7a308c3..ff9340c78 100644 --- a/src/test/test_conformance.cpp +++ b/src/test/test_conformance.cpp @@ -93,6 +93,7 @@ TEST_CONFORMANCE("jeq-reg.data") TEST_CONFORMANCE("jeq32-imm.data") TEST_CONFORMANCE("jeq32-reg.data") TEST_CONFORMANCE("jge-imm.data") +TEST_CONFORMANCE("jge-reg.data") TEST_CONFORMANCE("jge32-imm.data") TEST_CONFORMANCE("jge32-reg.data") TEST_CONFORMANCE("jgt-imm.data") @@ -185,15 +186,10 @@ TEST_CONFORMANCE("mod64-by-zero-reg.data") TEST_CONFORMANCE("mod64.data") TEST_CONFORMANCE("mov.data") TEST_CONFORMANCE("mov64-sign-extend.data") -TEST_CONFORMANCE("movsx1632-imm.data") TEST_CONFORMANCE("movsx1632-reg.data") -TEST_CONFORMANCE("movsx1664-imm.data") TEST_CONFORMANCE("movsx1664-reg.data") -TEST_CONFORMANCE("movsx3264-imm.data") TEST_CONFORMANCE("movsx3264-reg.data") -TEST_CONFORMANCE("movsx832-imm.data") TEST_CONFORMANCE("movsx832-reg.data") -TEST_CONFORMANCE("movsx864-imm.data") TEST_CONFORMANCE("movsx864-reg.data") TEST_CONFORMANCE("mul32-imm.data") TEST_CONFORMANCE("mul32-reg-overflow.data") From a90ab20eb90e952538b9984539af795332e231fc Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Sun, 28 Jan 2024 11:07:13 -0800 Subject: [PATCH 036/373] More marshaling tests * Add negative tests for bad offset for instructions that vary by offset value * Add marshaling tests for unconditional byteswap * Add marshaling tests for signed division and modulo * Add marshaling tests for sign extension Signed-off-by: Dave Thaler --- src/asm_unmarshal.cpp | 6 +++--- src/test/test_marshal.cpp | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index 9560d0320..4a6af8308 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -101,13 +101,13 @@ struct Unmarshaller { switch (inst.offset) { case 0: return Bin::Op::UDIV; case 1: return Bin::Op::SDIV; - default: throw InvalidInstruction{pc, "invalid ALU op 0x30"}; + default: throw InvalidInstruction(pc, make_opcode_message("invalid offset for", inst.opcode)); } case INST_ALU_OP_MOD: switch (inst.offset) { case 0: return Bin::Op::UMOD; case 1: return Bin::Op::SMOD; - default: throw InvalidInstruction{pc, "invalid ALU op 0x90"}; + default: throw InvalidInstruction(pc, make_opcode_message("invalid offset for", inst.opcode)); } case INST_ALU_OP_MOV: switch (inst.offset) { @@ -115,7 +115,7 @@ struct Unmarshaller { case 8: return Bin::Op::MOVSX8; case 16: return Bin::Op::MOVSX16; case 32: return Bin::Op::MOVSX32; - default: throw InvalidInstruction{pc, "invalid ALU op 0xb0"}; + default: throw InvalidInstruction(pc, make_opcode_message("invalid offset for", inst.opcode)); } } diff --git a/src/test/test_marshal.cpp b/src/test/test_marshal.cpp index 20700855f..ffe0c8d02 100644 --- a/src/test/test_marshal.cpp +++ b/src/test/test_marshal.cpp @@ -58,7 +58,8 @@ static const auto ws = {1, 2, 4, 8}; TEST_CASE("disasm_marshal", "[disasm][marshal]") { SECTION("Bin") { auto ops = {Bin::Op::MOV, Bin::Op::ADD, Bin::Op::SUB, Bin::Op::MUL, Bin::Op::UDIV, Bin::Op::UMOD, - Bin::Op::OR, Bin::Op::AND, Bin::Op::LSH, Bin::Op::RSH, Bin::Op::ARSH, Bin::Op::XOR}; + Bin::Op::OR, Bin::Op::AND, Bin::Op::LSH, Bin::Op::RSH, Bin::Op::ARSH, Bin::Op::XOR, + Bin::Op::SDIV, Bin::Op::SMOD, Bin::Op::MOVSX8, Bin::Op::MOVSX16, Bin::Op::MOVSX32}; SECTION("Reg src") { for (auto op : ops) { compare_marshal_unmarshal(Bin{.op = op, .dst = Reg{1}, .v = Reg{2}, .is64 = true}); @@ -90,6 +91,9 @@ TEST_CASE("disasm_marshal", "[disasm][marshal]") { Un::Op::LE32, Un::Op::LE64, Un::Op::NEG, + Un::Op::SWAP16, + Un::Op::SWAP32, + Un::Op::SWAP64 }; for (auto op : ops) compare_marshal_unmarshal(Un{.op = op, .dst = Reg{1}}); @@ -287,6 +291,16 @@ TEST_CASE("fail unmarshal off0 opcodes", "[disasm][marshal]") { } } +TEST_CASE("fail unmarshal offset opcodes", "[disasm][marshal]") { + // The following opcodes are defined for multiple other offset values, but not offset = 2 for example. + uint8_t off2_opcodes[] = {0x34, 0x37, 0x3c, 0x3f, 0x94, 0x97, 0x9c, 0x9f, 0xb4, 0xb7, 0xbc, 0xbf}; + for (int i = 0; i < sizeof(off2_opcodes); i++) { + std::ostringstream oss; + oss << "0: invalid offset for op 0x" << std::hex << (int)off2_opcodes[i] << std::endl; + check_unmarshal_fail(ebpf_inst{.opcode = off2_opcodes[i], .offset = 2}, oss.str().c_str()); + } +} + TEST_CASE("fail unmarshal misc", "[disasm][marshal]") { check_unmarshal_fail(ebpf_inst{.opcode = /* 0x06 */ INST_CLS_JMP32}, "0: jump out of bounds\n"); check_unmarshal_fail(ebpf_inst{.opcode = /* 0x16 */ 0x10 | INST_CLS_JMP32}, "0: jump out of bounds\n"); From 96f433309c694ea6c9918dc11d16217d8a1ec1f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 27 Jan 2024 16:42:27 +0000 Subject: [PATCH 037/373] Bump ebpf-samples from `9057f7d` to `ce6d479` Bumps [ebpf-samples](https://github.com/vbpf/ebpf-samples) from `9057f7d` to `ce6d479`. - [Release notes](https://github.com/vbpf/ebpf-samples/releases) - [Commits](https://github.com/vbpf/ebpf-samples/compare/9057f7de29fb9bd8eaf6144f7bbf0b2d6e545e0e...ce6d4793d8b851683a86c6af675a02e39f91f0db) --- updated-dependencies: - dependency-name: ebpf-samples dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- ebpf-samples | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebpf-samples b/ebpf-samples index 9057f7de2..ce6d4793d 160000 --- a/ebpf-samples +++ b/ebpf-samples @@ -1 +1 @@ -Subproject commit 9057f7de29fb9bd8eaf6144f7bbf0b2d6e545e0e +Subproject commit ce6d4793d8b851683a86c6af675a02e39f91f0db From 55478db33c7cbaef64bf1c69802b64f54b65cd84 Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Thu, 25 Jan 2024 17:13:36 -0800 Subject: [PATCH 038/373] Add unmarshaling tests for 64-bit immediate instructions Blocked on answer to https://github.com/ietf-wg-bpf/ebpf-docs/issues/75 Signed-off-by: Dave Thaler --- src/asm_unmarshal.cpp | 10 +++--- src/test/test_marshal.cpp | 64 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index 4a6af8308..c5041b434 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -310,19 +310,21 @@ struct Unmarshaller { auto makeLddw(ebpf_inst inst, int32_t next_imm, const vector& insts, pc_t pc) -> Instruction { if (pc >= insts.size() - 1) throw InvalidInstruction(pc, "incomplete LDDW"); + ebpf_inst next = insts[pc + 1]; + if (next.opcode != 0 || next.dst != 0 || next.src != 0 || next.offset != 0) + throw InvalidInstruction(pc, "invalid LDDW"); if (inst.src > 1 || inst.dst > R10_STACK_POINTER || inst.offset != 0) throw InvalidInstruction(pc, "LDDW uses reserved fields"); if (inst.src == 1) { // magic number, meaning we're a per-process file descriptor defining the map. // (for details, look for BPF_PSEUDO_MAP_FD in the kernel) - + if (next.imm != 0) { + throw InvalidInstruction(pc, "LDDW uses reserved fields"); + } return LoadMapFd{.dst = Reg{inst.dst}, .mapfd = inst.imm}; } - ebpf_inst next = insts[pc + 1]; - if (next.opcode != 0 || next.dst != 0 || next.src != 0 || next.offset != 0) - throw InvalidInstruction(pc, "invalid LDDW"); return Bin{ .op = Bin::Op::MOV, .dst = Reg{inst.dst}, diff --git a/src/test/test_marshal.cpp b/src/test/test_marshal.cpp index ffe0c8d02..45291304c 100644 --- a/src/test/test_marshal.cpp +++ b/src/test/test_marshal.cpp @@ -23,6 +23,26 @@ static void compare_unmarshal_marshal(const ebpf_inst& ins, const ebpf_inst& exp REQUIRE(memcmp(&expected_result, &result, sizeof(result)) == 0); } +// Verify that if we unmarshal a 64-bit immediate instruction and then re-marshal it, +// we get what we expect. +static void compare_unmarshal_marshal(const ebpf_inst& ins1, const ebpf_inst& ins2, const ebpf_inst& expected_result1, + const ebpf_inst& expected_result2) { + program_info info{.platform = &g_ebpf_platform_linux, + .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")}; + const ebpf_inst exit{.opcode = INST_OP_EXIT}; + InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", {ins1, ins2, exit, exit}, info})); + REQUIRE(parsed.size() == 3); + auto [_, single, _2] = parsed.front(); + (void)_; // unused + (void)_2; // unused + std::vector marshaled = marshal(single, 0); + REQUIRE(marshaled.size() == 2); + ebpf_inst result1 = marshaled.front(); + REQUIRE(memcmp(&expected_result1, &result1, sizeof(result1)) == 0); + ebpf_inst result2 = marshaled.back(); + REQUIRE(memcmp(&expected_result2, &result2, sizeof(result2)) == 0); +} + // Verify that if we marshal an instruction and then unmarshal it, // we get the original. static void compare_marshal_unmarshal(const Instruction& ins, bool double_cmd = false) { @@ -53,6 +73,17 @@ static void check_unmarshal_fail(ebpf_inst inst, std::string expected_error_mess REQUIRE(error_message == expected_error_message); } +// Check that unmarshaling a 64-bit immediate instruction fails. +static void check_unmarshal_fail(ebpf_inst inst1, ebpf_inst inst2, std::string expected_error_message) { + program_info info{.platform = &g_ebpf_platform_linux, + .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")}; + std::vector insns = {inst1, inst2}; + auto result = unmarshal(raw_program{"", "", insns, info}); + REQUIRE(std::holds_alternative(result)); + std::string error_message = std::get(result); + REQUIRE(error_message == expected_error_message); +} + static const auto ws = {1, 2, 4, 8}; TEST_CASE("disasm_marshal", "[disasm][marshal]") { @@ -301,10 +332,41 @@ TEST_CASE("fail unmarshal offset opcodes", "[disasm][marshal]") { } } +TEST_CASE("unmarshal 64bit immediate", "[disasm][marshal]") { + compare_unmarshal_marshal(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 0, .imm = 1}, ebpf_inst{.imm = 2}, + ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 0, .imm = 1}, ebpf_inst{.imm = 2}); + compare_unmarshal_marshal(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 0, .imm = 1}, ebpf_inst{}, + ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 0, .imm = 1}, ebpf_inst{}); + + for (uint8_t src = 0; src <= 7; src++) { + check_unmarshal_fail(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = src}, "0: incomplete LDDW\n"); + check_unmarshal_fail(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = src}, + ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM}, "0: invalid LDDW\n"); + } + + // No supported src values use the offset field. + for (uint8_t src = 0; src <= 1; src++) { + check_unmarshal_fail(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = src, .offset = 1}, ebpf_inst{}, + "0: LDDW uses reserved fields\n"); + } + + // Verify that unsupported src values fail. + // TODO: support src = 2 through 6. + for (uint8_t src = 2; src <= 7; src++) { + check_unmarshal_fail(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = src}, ebpf_inst{}, + "0: LDDW uses reserved fields\n"); + } + + // When src = {1, 3, 4, 5}, next_imm must be 0. + for (uint8_t src : {1, 3, 4, 5}) { + check_unmarshal_fail(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = src}, ebpf_inst{.imm = 1}, + "0: LDDW uses reserved fields\n"); + } +} + TEST_CASE("fail unmarshal misc", "[disasm][marshal]") { check_unmarshal_fail(ebpf_inst{.opcode = /* 0x06 */ INST_CLS_JMP32}, "0: jump out of bounds\n"); check_unmarshal_fail(ebpf_inst{.opcode = /* 0x16 */ 0x10 | INST_CLS_JMP32}, "0: jump out of bounds\n"); - check_unmarshal_fail(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM}, "0: incomplete LDDW\n"); check_unmarshal_fail(ebpf_inst{.opcode = /* 0x21 */ (INST_ABS << 5) | INST_SIZE_W | INST_CLS_LDX, .imm = 8}, "0: ABS but not LD\n"); check_unmarshal_fail(ebpf_inst{.opcode = /* 0x41 */ (INST_IND << 5) | INST_SIZE_W | INST_CLS_LDX, .imm = 8}, From 342996d27ab05d11eefdfc64f211eaaa1a6ccadc Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Sun, 21 Jan 2024 08:38:27 -0800 Subject: [PATCH 039/373] Remove MOVSX immediate support Signed-off-by: Dave Thaler --- src/asm_unmarshal.cpp | 2 ++ src/test/test_marshal.cpp | 12 ++++++-- test-data/movsx.yaml | 65 --------------------------------------- 3 files changed, 11 insertions(+), 68 deletions(-) diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index c5041b434..4c5eceb95 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -110,6 +110,8 @@ struct Unmarshaller { default: throw InvalidInstruction(pc, make_opcode_message("invalid offset for", inst.opcode)); } case INST_ALU_OP_MOV: + if (inst.offset > 0 && !(inst.opcode & INST_SRC_REG)) + throw InvalidInstruction(pc, make_opcode_message("invalid offset for", inst.opcode)); switch (inst.offset) { case 0: return Bin::Op::MOV; case 8: return Bin::Op::MOVSX8; diff --git a/src/test/test_marshal.cpp b/src/test/test_marshal.cpp index 45291304c..1c5f66184 100644 --- a/src/test/test_marshal.cpp +++ b/src/test/test_marshal.cpp @@ -88,16 +88,20 @@ static const auto ws = {1, 2, 4, 8}; TEST_CASE("disasm_marshal", "[disasm][marshal]") { SECTION("Bin") { - auto ops = {Bin::Op::MOV, Bin::Op::ADD, Bin::Op::SUB, Bin::Op::MUL, Bin::Op::UDIV, Bin::Op::UMOD, - Bin::Op::OR, Bin::Op::AND, Bin::Op::LSH, Bin::Op::RSH, Bin::Op::ARSH, Bin::Op::XOR, - Bin::Op::SDIV, Bin::Op::SMOD, Bin::Op::MOVSX8, Bin::Op::MOVSX16, Bin::Op::MOVSX32}; SECTION("Reg src") { + auto ops = {Bin::Op::MOV, Bin::Op::ADD, Bin::Op::SUB, Bin::Op::MUL, Bin::Op::UDIV, Bin::Op::UMOD, + Bin::Op::OR, Bin::Op::AND, Bin::Op::LSH, Bin::Op::RSH, Bin::Op::ARSH, Bin::Op::XOR, + Bin::Op::SDIV, Bin::Op::SMOD, Bin::Op::MOVSX8, Bin::Op::MOVSX16, Bin::Op::MOVSX32}; for (auto op : ops) { compare_marshal_unmarshal(Bin{.op = op, .dst = Reg{1}, .v = Reg{2}, .is64 = true}); compare_marshal_unmarshal(Bin{.op = op, .dst = Reg{1}, .v = Reg{2}, .is64 = false}); } } SECTION("Imm src") { + // MOVSX* instructions are not defined for Imm, only Reg. + auto ops = {Bin::Op::MOV, Bin::Op::ADD, Bin::Op::SUB, Bin::Op::MUL, Bin::Op::UDIV, + Bin::Op::UMOD, Bin::Op::OR, Bin::Op::AND, Bin::Op::LSH, Bin::Op::RSH, + Bin::Op::ARSH, Bin::Op::XOR, Bin::Op::SDIV, Bin::Op::SMOD}; for (auto op : ops) { compare_marshal_unmarshal(Bin{.op = op, .dst = Reg{1}, .v = Imm{2}, .is64 = false}); compare_marshal_unmarshal(Bin{.op = op, .dst = Reg{1}, .v = Imm{2}, .is64 = true}); @@ -377,6 +381,8 @@ TEST_CASE("fail unmarshal misc", "[disasm][marshal]") { "0: Bad register\n"); check_unmarshal_fail(ebpf_inst{.opcode = /* 0xb4 */ (INST_ALU_OP_MOV | INST_SRC_IMM | INST_CLS_ALU), .dst = 11, .imm = 8}, "0: Bad register\n"); + check_unmarshal_fail(ebpf_inst{.opcode = /* 0xb4 */ INST_ALU_OP_MOV | INST_SRC_IMM | INST_CLS_ALU, .offset = 8}, + "0: invalid offset for op 0xb4\n"); check_unmarshal_fail(ebpf_inst{.opcode = /* 0xbc */ (INST_ALU_OP_MOV | INST_SRC_REG | INST_CLS_ALU), .dst = 1, .src = 11}, "0: Bad register\n"); check_unmarshal_fail(ebpf_inst{.opcode = /* 0xd4 */ INST_ALU_OP_END | INST_END_LE | INST_CLS_ALU, .dst = 1, .imm = 8}, diff --git a/test-data/movsx.yaml b/test-data/movsx.yaml index 7aa3d0f54..ef1fe399c 100644 --- a/test-data/movsx.yaml +++ b/test-data/movsx.yaml @@ -1,71 +1,6 @@ # Copyright (c) Prevail Verifier contributors. # SPDX-License-Identifier: MIT --- -test-case: movsx8 immediate to 32 bits - -pre: [] - -code: - : | - w1 s8= 384 ; 0x180 -> 0xFFFFFF80 - -post: - - r1.type=number - - r1.svalue=4294967168 - - r1.uvalue=4294967168 ---- -test-case: movsx16 immediate to 32 bits - -pre: [] - -code: - : | - w1 s16= 98304 ; 0x18000 -> 0xFFFF8000 - -post: - - r1.type=number - - r1.svalue=4294934528 - - r1.uvalue=4294934528 ---- -test-case: movsx8 immediate to 64 bits - -pre: [] - -code: - : | - r1 s8= 384 ; 0x180 -> 0xFFFFFFFFFFFFFF80 - -post: - - r1.type=number - - r1.svalue=-128 - - r1.uvalue=18446744073709551488 ---- -test-case: movsx16 immediate to 64 bits - -pre: [] - -code: - : | - r1 s16= 98304 ; 0x18000 -> 0xFFFFFFFFFFFF8000 - -post: - - r1.type=number - - r1.svalue=-32768 - - r1.uvalue=18446744073709518848 ---- -test-case: movsx32 immediate to 64 bits - -pre: [] - -code: - : | - r1 s32= 2147483648 ; 0x80000000 -> 0xFFFFFFFF80000000 - -post: - - r1.type=number - - r1.svalue=-2147483648 - - r1.uvalue=18446744071562067968 ---- test-case: movsx8 register to 32 bits pre: ["r1.svalue=384", "r1.uvalue=384", "r1.type=number", "r1.svalue=r1.uvalue"] From b62f8f10d543560b9e4a0952a9acc91e48584621 Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Sun, 11 Feb 2024 12:52:58 -0800 Subject: [PATCH 040/373] Only set type to pointer if the width is correct (#581) * Only set type to packet if the width is correct * Fix and test move operations * Correct variable name to be offset_width Signed-off-by: Dave Thaler --- src/asm_parse.cpp | 2 +- src/crab/ebpf_domain.cpp | 81 +++++++++++----- src/crab/split_dbm.cpp | 2 +- test-data/assign.yaml | 193 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 255 insertions(+), 23 deletions(-) diff --git a/src/asm_parse.cpp b/src/asm_parse.cpp index a60fe6395..56069ee0c 100644 --- a/src/asm_parse.cpp +++ b/src/asm_parse.cpp @@ -52,7 +52,7 @@ using crab::linear_expression_t; #define ARRAY_RANGE R"_(\s*\[([-+]?\d+)\.\.\.\s*([-+]?\d+)\]?\s*)_" #define DOT "[.]" -#define TYPE R"_(\s*(shared|number|packet|stack|ctx|map_fd|map_fd_program)\s*)_" +#define TYPE R"_(\s*(shared|number|packet|stack|ctx|map_fd|map_fd_programs)\s*)_" static const std::map str_to_binop = { {"", Bin::Op::MOV}, {"+", Bin::Op::ADD}, {"-", Bin::Op::SUB}, {"*", Bin::Op::MUL}, diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index dc90c2df6..5741e7051 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -64,6 +64,13 @@ static linear_constraint_t neq(variable_t a, variable_t b) { } constexpr int MAX_PACKET_SIZE = 0xffff; + +// Pointers in the BPF VM are defined to be 64 bits. Some contexts, like +// data, data_end, and meta in Linux's struct xdp_md are only 32 bit offsets +// from a base address not exposed to the program, but when a program is loaded, +// the offsets get replaced with 64-bit address pointers. However, we currently +// need to do pointer arithmetic on 64-bit numbers so for now we cap the interval +// to 32 bits. constexpr int64_t PTR_MAX = std::numeric_limits::max() - MAX_PACKET_SIZE; /** Linear constraint for a pointer comparison. @@ -1906,12 +1913,20 @@ void ebpf_domain_t::do_load_ctx(NumAbsDomain& inv, const Reg& target_reg, const number_t addr = *maybe_addr; + // We use offsets for packet data, data_end, and meta during verification, + // but at runtime they will be 64-bit pointers. We can use the offset values + // for verification like we use map_fd's as a proxy for maps which + // at runtime are actually 64-bit memory pointers. + int offset_width = desc->end - desc->data; if (addr == desc->data) { - inv.assign(target.packet_offset, 0); + if (width == offset_width) + inv.assign(target.packet_offset, 0); } else if (addr == desc->end) { - inv.assign(target.packet_offset, variable_t::packet_size()); + if (width == offset_width) + inv.assign(target.packet_offset, variable_t::packet_size()); } else if (addr == desc->meta) { - inv.assign(target.packet_offset, variable_t::meta_offset()); + if (width == offset_width) + inv.assign(target.packet_offset, variable_t::meta_offset()); } else { if (may_touch_ptr) type_inv.havoc_type(inv, target_reg); @@ -1919,9 +1934,11 @@ void ebpf_domain_t::do_load_ctx(NumAbsDomain& inv, const Reg& target_reg, const type_inv.assign_type(inv, target_reg, T_NUM); return; } - type_inv.assign_type(inv, target_reg, T_PACKET); - inv += 4098 <= target.svalue; - inv += target.svalue <= PTR_MAX; + if (width == offset_width) { + type_inv.assign_type(inv, target_reg, T_PACKET); + inv += 4098 <= target.svalue; + inv += target.svalue <= PTR_MAX; + } } void ebpf_domain_t::do_load_packet_or_shared(NumAbsDomain& inv, const Reg& target_reg, const linear_expression_t& addr, @@ -2733,30 +2750,52 @@ void ebpf_domain_t::operator()(const Bin& bin) { assign(dst.uvalue, src.uvalue); havoc_offsets(bin.dst); m_inv = type_inv.join_over_types(m_inv, src_reg, [&](NumAbsDomain& inv, type_encoding_t type) { - inv.assign(dst.type, type); - switch (type) { - case T_CTX: inv.assign(dst.ctx_offset, src.ctx_offset); break; + case T_CTX: + if (bin.is64) { + inv.assign(dst.type, type); + inv.assign(dst.ctx_offset, src.ctx_offset); + } + break; case T_MAP: - case T_MAP_PROGRAMS: inv.assign(dst.map_fd, src.map_fd); break; - case T_PACKET: inv.assign(dst.packet_offset, src.packet_offset); break; + case T_MAP_PROGRAMS: + if (bin.is64) { + inv.assign(dst.type, type); + inv.assign(dst.map_fd, src.map_fd); + } + break; + case T_PACKET: + if (bin.is64) { + inv.assign(dst.type, type); + inv.assign(dst.packet_offset, src.packet_offset); + } + break; case T_SHARED: - inv.assign(dst.shared_region_size, src.shared_region_size); - inv.assign(dst.shared_offset, src.shared_offset); + if (bin.is64) { + inv.assign(dst.type, type); + inv.assign(dst.shared_region_size, src.shared_region_size); + inv.assign(dst.shared_offset, src.shared_offset); + } break; case T_STACK: - inv.assign(dst.stack_offset, src.stack_offset); - inv.assign(dst.stack_numeric_size, src.stack_numeric_size); + if (bin.is64) { + inv.assign(dst.type, type); + inv.assign(dst.stack_offset, src.stack_offset); + inv.assign(dst.stack_numeric_size, src.stack_numeric_size); + } break; - default: break; + default: inv.assign(dst.type, type); break; } }); - if ((bin.dst.v != std::get(bin.v).v) || (type_inv.get_type(m_inv, dst.type) == T_UNINIT)) { - // Only forget the destination type if we're copying from a different register, - // or from the same uninitialized register. - havoc(dst.type); + if (bin.is64) { + // Add dst.type=src.type invariant. + if ((bin.dst.v != std::get(bin.v).v) || (type_inv.get_type(m_inv, dst.type) == T_UNINIT)) { + // Only forget the destination type if we're copying from a different register, + // or from the same uninitialized register. + havoc(dst.type); + } + type_inv.assign_type(m_inv, bin.dst, std::get(bin.v)); } - type_inv.assign_type(m_inv, bin.dst, std::get(bin.v)); break; } } diff --git a/src/crab/split_dbm.cpp b/src/crab/split_dbm.cpp index 8336ba40e..af07d54d5 100644 --- a/src/crab/split_dbm.cpp +++ b/src/crab/split_dbm.cpp @@ -1052,7 +1052,7 @@ static std::string to_string(variable_t vd, variable_t vs, const SplitDBM::Param } static const std::vector type_string = { - "shared", "stack", "packet", "ctx", "number", "map_fd", "map_fd_program", "uninitialized" + "shared", "stack", "packet", "ctx", "number", "map_fd", "map_fd_programs", "uninitialized" }; string_invariant SplitDBM::to_set() const { diff --git a/test-data/assign.yaml b/test-data/assign.yaml index 4385c0ceb..22a765b0f 100644 --- a/test-data/assign.yaml +++ b/test-data/assign.yaml @@ -172,6 +172,21 @@ post: - r1.uvalue=r2.uvalue - r1.stack_numeric_size=r2.stack_numeric_size --- +test-case: 32-bit assign register stack value + +pre: ["r10.type=stack", "r10.stack_offset=512"] + +code: + : | + w2 = r10 + +post: + - r2.svalue=[0, 4294967295] + - r2.svalue=r2.uvalue + - r2.uvalue=[0, 4294967295] + - r10.type=stack + - r10.stack_offset=512 +--- test-case: assign register shared value pre: ["r1.type=shared", "r1.shared_offset=0", "r1.shared_region_size=16"] @@ -190,6 +205,22 @@ post: - r1.svalue=r2.svalue - r1.uvalue=r2.uvalue --- +test-case: 32-bit assign register shared value + +pre: ["r1.type=shared", "r1.shared_offset=0", "r1.shared_region_size=16"] + +code: + : | + w2 = r1 + +post: + - r1.type=shared + - r1.shared_offset=0 + - r1.shared_region_size=16 + - r2.svalue=[0, 4294967295] + - r2.uvalue=[0, 4294967295] + - r2.svalue=r2.uvalue +--- test-case: assign register combination value pre: ["r1.type=[-1,0]", "r1.shared_offset=0", "r1.shared_region_size=16", "r1.stack_offset=500", "r1.stack_numeric_size=16"] @@ -229,3 +260,165 @@ post: - r1.uvalue=[1, 2147418112] - r2.type=packet - r2.svalue=[4098, 2147418112] +--- +test-case: 16-bit indirect assignment from context + +pre: ["r1.ctx_offset=0", "r1.type=ctx", "r1.svalue=[1, 2147418112]", "r1.uvalue=[1, 2147418112]"] + +code: + : | + r2 = *(u16 *)(r1 + 4) + +post: + - r1.ctx_offset=0 + - r1.type=ctx + - r1.svalue=[1, 2147418112] + - r1.uvalue=[1, 2147418112] +--- +test-case: 64-bit indirect assignment from context + +pre: ["r1.ctx_offset=0", "r1.type=ctx", "r1.svalue=[1, 2147418112]", "r1.uvalue=[1, 2147418112]"] + +code: + : | + r2 = *(u64 *)(r1 + 4) + +post: + - r1.ctx_offset=0 + - r1.type=ctx + - r1.svalue=[1, 2147418112] + - r1.uvalue=[1, 2147418112] +--- +test-case: assign register packet value + +pre: ["packet_size=r2.packet_offset", "r2.type=packet", "r2.svalue=[4098, 2147418112]"] + +code: + : | + r1 = r2 + +post: + - packet_size=r1.packet_offset + - r1.type=packet + - r1.svalue=[4098, 2147418112] + - packet_size=r2.packet_offset + - r2.type=packet + - r2.svalue=[4098, 2147418112] + - r1.packet_offset=r2.packet_offset + - r1.svalue=r2.svalue + - r1.uvalue=r2.uvalue +--- +test-case: 32-bit assign register packet value + +pre: ["packet_size=r2.packet_offset", "r2.type=packet", "r2.svalue=[4098, 2147418112]"] + +code: + : | + w1 = r2 + +post: + - r1.svalue=[0, 4294967295] + - r1.uvalue=[0, 4294967295] + - r1.svalue=r1.uvalue + - packet_size=r2.packet_offset + - r2.type=packet + - r2.svalue=[4098, 2147418112] +--- +test-case: assign register context value + +pre: ["r1.ctx_offset=0", "r1.type=ctx", "r1.svalue=[1, 2147418112]", "r1.uvalue=[1, 2147418112]"] + +code: + : | + r2 = r1 + +post: + - r1.ctx_offset=0 + - r1.type=ctx + - r1.svalue=[1, 2147418112] + - r1.uvalue=[1, 2147418112] + - r2.ctx_offset=0 + - r2.type=ctx + - r2.svalue=[1, 2147418112] + - r2.uvalue=[1, 2147418112] + - r1.svalue=r2.svalue + - r1.uvalue=r2.uvalue +--- +test-case: 32-bit assign register context value + +pre: ["r1.ctx_offset=0", "r1.type=ctx", "r1.svalue=[1, 2147418112]", "r1.uvalue=[1, 2147418112]"] + +code: + : | + w2 = r1 + +post: + - r1.ctx_offset=0 + - r1.type=ctx + - r1.svalue=[1, 2147418112] + - r1.uvalue=[1, 2147418112] + - r2.svalue=[1, 2147418112] + - r2.uvalue=[1, 2147418112] + - r2.svalue=r2.uvalue +--- +test-case: assign register map value + +pre: ["r1.type=map_fd", "r1.map_fd=1"] + +code: + : | + r2 = r1 + +post: + - r1.map_fd=1 + - r1.type=map_fd + - r2.map_fd=1 + - r2.type=map_fd + - r1.svalue=r2.svalue + - r1.uvalue=r2.uvalue +--- +test-case: 32-bit assign register map value + +pre: ["r1.type=map_fd", "r1.map_fd=1"] + +code: + : | + w2 = r1 + +post: + - r1.map_fd=1 + - r1.type=map_fd + - r2.svalue=[0, 4294967295] + - r2.uvalue=[0, 4294967295] + - r2.svalue=r2.uvalue +--- +test-case: assign register map programs value + +pre: ["r1.type=map_fd_programs", "r1.map_fd=1"] + +code: + : | + r2 = r1 + +post: + - r1.map_fd=1 + - r1.type=map_fd_programs + - r2.map_fd=1 + - r2.type=map_fd_programs + - r1.svalue=r2.svalue + - r1.uvalue=r2.uvalue +--- +test-case: 32-bit assign register map programs value + +pre: ["r1.type=map_fd_programs", "r1.map_fd=1"] + +code: + : | + w2 = r1 + +post: + - r1.map_fd=1 + - r1.type=map_fd_programs + - r2.svalue=[0, 4294967295] + - r2.uvalue=[0, 4294967295] + - r2.svalue=r2.uvalue From f2f284f4ab536959d9d95cfc90764e7a63949f70 Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Mon, 12 Feb 2024 10:57:50 -0800 Subject: [PATCH 041/373] adjust_head statistic is not cross-platform safe Replace with a stat counting calls that reallocate packet, in a cross-platform way similar to the map_in_map stat. Fixes #583 Signed-off-by: Dave Thaler --- src/asm_cfg.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/asm_cfg.cpp b/src/asm_cfg.cpp index 3787507fc..508f603ba 100644 --- a/src/asm_cfg.cpp +++ b/src/asm_cfg.cpp @@ -203,7 +203,7 @@ std::vector stats_headers() { return { "basic_blocks", "joins", "other", "jumps", "assign", "arith", "load", "store", "load_store", "packet_access", "call_1", "call_mem", - "call_nomem", "adjust_head", "map_in_map", "arith64", "arith32", + "call_nomem", "reallocate", "map_in_map", "arith64", "arith32", }; } @@ -224,8 +224,8 @@ std::map collect_stats(const cfg_t& cfg) { } if (std::holds_alternative(ins)) { auto call = std::get(ins); - if (call.func == 43 || call.func == 44) - res["adjust_head"] = 1; + if (call.reallocate_packet) + res["reallocate"] = 1; } if (std::holds_alternative(ins)) { auto const& bin = std::get(ins); From 5ea9ec7aa703b04738cc3d480dbac27418722be2 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Fri, 22 Sep 2023 15:03:12 +0300 Subject: [PATCH 042/373] zero-extension and sign-extension Signed-off-by: Dave Thaler --- src/asm_unmarshal.cpp | 37 +++++++++++++++++--- src/crab/ebpf_domain.cpp | 38 +++++++++++++-------- src/crab/interval.hpp | 13 ++++--- src/test/test_marshal.cpp | 33 +++++++++++++++++- src/test/test_yaml.cpp | 1 + test-data/movsx.yaml | 3 ++ test-data/sext.yaml | 72 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 172 insertions(+), 25 deletions(-) create mode 100644 test-data/sext.yaml diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index 4c5eceb95..4e12356c3 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -87,6 +87,10 @@ static auto getMemWidth(uint8_t opcode) -> int { // return {}; // } +static Instruction shift32(Reg dst, Bin::Op op) { + return (Instruction)Bin{.op=op,.dst = dst, .v = Imm{32}, .is64=true, .lddw=false}; +} + struct Unmarshaller { vector>& notes; const program_info& info; @@ -478,14 +482,14 @@ struct Unmarshaller { for (size_t pc = 0; pc < insts.size();) { ebpf_inst inst = insts[pc]; Instruction new_ins; - bool lddw = false; + bool skip_instruction = false; bool fallthrough = true; switch (inst.opcode & INST_CLS_MASK) { case INST_CLS_LD: if (inst.opcode == INST_OP_LDDW_IMM) { int32_t next_imm = pc < insts.size() - 1 ? insts[pc + 1].imm : 0; new_ins = makeLddw(inst, next_imm, insts, static_cast(pc)); - lddw = true; + skip_instruction = true; break; } // fallthrough @@ -494,7 +498,32 @@ struct Unmarshaller { case INST_CLS_STX: new_ins = makeMemOp(pc, inst); break; case INST_CLS_ALU: - case INST_CLS_ALU64: new_ins = makeAluOp(pc, inst); break; + case INST_CLS_ALU64: { + new_ins = makeAluOp(pc, inst); + + // Merge (rX <<= 32; rX >>>= 32) into wX = rX + // (rX <<= 32; rX >>= 32) into rX s32= rX + if (pc >= insts.size() - 1) + break; + ebpf_inst next = insts[pc + 1]; + auto dst = Reg{inst.dst}; + + if (new_ins != shift32(dst, Bin::Op::LSH)) + break; + + if ((next.opcode & INST_CLS_MASK) != INST_CLS_ALU64) + break; + auto next_ins = makeAluOp(pc+1, next); + if (next_ins == shift32(dst, Bin::Op::RSH)) { + new_ins = Bin{.op = Bin::Op::MOV, .dst = dst, .v = dst, .is64 = false}; + skip_instruction = true; + } else if (next_ins == shift32(dst, Bin::Op::ARSH)) { + new_ins = Bin{.op = Bin::Op::MOVSX32, .dst = dst, .v = dst, .is64 = true}; + skip_instruction = true; + } + + break; + } case INST_CLS_JMP32: case INST_CLS_JMP: { @@ -522,7 +551,7 @@ struct Unmarshaller { pc++; note_next_pc(); - if (lddw) { + if (skip_instruction) { pc++; note_next_pc(); } diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index 5741e7051..67e74fd2b 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -2376,18 +2376,20 @@ void ebpf_domain_t::lshr(const Reg& dst_reg, int imm, int finite_width) { havoc_offsets(dst_reg); } -void ebpf_domain_t::sign_extend(const Reg& dst_reg, const linear_expression_t& right_svalue, int finite_width, - Bin::Op op) { - using namespace crab; - - int bits; +static inline int _movsx_bits(Bin::Op op) { switch (op) { - case Bin::Op::MOVSX8: bits = 8; break; - case Bin::Op::MOVSX16: bits = 16; break; - case Bin::Op::MOVSX32: bits = 32; break; + case Bin::Op::MOVSX8: return 8; + case Bin::Op::MOVSX16: return 16; + case Bin::Op::MOVSX32: return 32; default: throw std::exception(); } +} +void ebpf_domain_t::sign_extend(const Reg& dst_reg, const linear_expression_t& right_svalue, int finite_width, + Bin::Op op) { + using namespace crab; + + int bits = _movsx_bits(op); reg_pack_t dst = reg_pack(dst_reg); interval_t right_interval = m_inv.eval_interval(right_svalue); type_inv.assign_type(m_inv, dst_reg, T_NUM); @@ -2395,8 +2397,11 @@ void ebpf_domain_t::sign_extend(const Reg& dst_reg, const linear_expression_t& r int64_t span = 1ULL << bits; if (right_interval.ub() - right_interval.lb() >= number_t{span}) { // Interval covers the full space. - havoc(dst.svalue); - return; + if (bits == 64) { + havoc(dst.svalue); + return; + } + right_interval = interval_t::signed_int(bits); } int64_t mask = 1ULL << (bits - 1); @@ -2487,11 +2492,6 @@ void ebpf_domain_t::operator()(const Bin& bin) { type_inv.assign_type(m_inv, bin.dst, T_NUM); havoc_offsets(bin.dst); break; - case Bin::Op::MOVSX8: - case Bin::Op::MOVSX16: - case Bin::Op::MOVSX32: - sign_extend(bin.dst, number_t{(int32_t)imm}, finite_width, bin.op); - break; case Bin::Op::ADD: if (imm == 0) return; @@ -2737,6 +2737,10 @@ void ebpf_domain_t::operator()(const Bin& bin) { case Bin::Op::MOVSX8: case Bin::Op::MOVSX16: case Bin::Op::MOVSX32: + // Keep relational information if operation is a no-op. + if ((dst.svalue == src.svalue) && (m_inv.eval_interval(dst.svalue) <= interval_t::signed_int(_movsx_bits(bin.op)))) { + return; + } if (m_inv.entail(type_is_number(src_reg))) { sign_extend(bin.dst, src.svalue, finite_width, bin.op); break; @@ -2746,6 +2750,10 @@ void ebpf_domain_t::operator()(const Bin& bin) { havoc_offsets(bin.dst); break; case Bin::Op::MOV: + // Keep relational information if operation is a no-op. + if ((dst.svalue == src.svalue) && (m_inv.eval_interval(dst.uvalue) <= interval_t::unsigned_int(bin.is64))) { + return; + } assign(dst.svalue, src.svalue); assign(dst.uvalue, src.uvalue); havoc_offsets(bin.dst); diff --git a/src/crab/interval.hpp b/src/crab/interval.hpp index bf28cd2f5..47878de00 100644 --- a/src/crab/interval.hpp +++ b/src/crab/interval.hpp @@ -497,13 +497,16 @@ class interval_t final { } // Return an interval in the range [INT_MIN, INT_MAX] which can only // be represented as an svalue. - static interval_t signed_int(bool is64) { - if (is64) { - return {number_t{std::numeric_limits::min()}, number_t{std::numeric_limits::max()}}; - } else { - return {number_t{std::numeric_limits::min()}, number_t{std::numeric_limits::max()}}; + static interval_t signed_int(int bits) { + switch (bits) { + case 64: return {number_t{std::numeric_limits::min()}, number_t{std::numeric_limits::max()}}; + case 32: return {number_t{std::numeric_limits::min()}, number_t{std::numeric_limits::max()}}; + case 16: return {number_t{std::numeric_limits::min()}, number_t{std::numeric_limits::max()}}; + case 8: return {number_t{std::numeric_limits::min()}, number_t{std::numeric_limits::max()}}; + default: throw std::exception(); } } + static interval_t signed_int(bool is64) { return signed_int(is64 ? 64 : 32); } // Return an interval in the range [0, UINT_MAX] which can only be // represented as a uvalue. static interval_t unsigned_int(bool is64) { diff --git a/src/test/test_marshal.cpp b/src/test/test_marshal.cpp index 1c5f66184..05eef0bb2 100644 --- a/src/test/test_marshal.cpp +++ b/src/test/test_marshal.cpp @@ -23,6 +23,23 @@ static void compare_unmarshal_marshal(const ebpf_inst& ins, const ebpf_inst& exp REQUIRE(memcmp(&expected_result, &result, sizeof(result)) == 0); } +// Verify that if we unmarshal two instructions and then re-marshal it, +// we get what we expect. +static void compare_unmarshal_marshal(const ebpf_inst& ins1, const ebpf_inst& ins2, const ebpf_inst& expected_result) { + program_info info{.platform = &g_ebpf_platform_linux, + .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")}; + const ebpf_inst exit{.opcode = INST_OP_EXIT}; + InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", {ins1, ins2, exit, exit}, info})); + REQUIRE(parsed.size() == 3); + auto [_, single, _2] = parsed.front(); + (void)_; // unused + (void)_2; // unused + std::vector marshaled = marshal(single, 0); + REQUIRE(marshaled.size() == 1); + ebpf_inst result = marshaled.back(); + REQUIRE(memcmp(&expected_result, &result, sizeof(result)) == 0); +} + // Verify that if we unmarshal a 64-bit immediate instruction and then re-marshal it, // we get what we expect. static void compare_unmarshal_marshal(const ebpf_inst& ins1, const ebpf_inst& ins2, const ebpf_inst& expected_result1, @@ -131,7 +148,7 @@ TEST_CASE("disasm_marshal", "[disasm][marshal]") { Un::Op::SWAP64 }; for (auto op : ops) - compare_marshal_unmarshal(Un{.op = op, .dst = Reg{1}}); + compare_marshal_unmarshal(Un{.op = op, .dst = Reg{1}, .is64 = true}); } SECTION("LoadMapFd") { compare_marshal_unmarshal(LoadMapFd{.dst = Reg{1}, .mapfd = 1}, true); } @@ -272,6 +289,20 @@ TEST_CASE("disasm_marshal_Mem", "[disasm][marshal]") { } } +TEST_CASE("unmarshal extension opcodes", "[disasm][marshal]") { + // Merge (rX <<= 32; rX >>>= 32) into wX = rX. + compare_unmarshal_marshal( + ebpf_inst{.opcode = INST_ALU_OP_LSH | INST_SRC_IMM | INST_CLS_ALU64, .dst = 1, .imm = 32}, + ebpf_inst{.opcode = INST_ALU_OP_RSH | INST_SRC_IMM | INST_CLS_ALU64, .dst = 1, .imm = 32}, + ebpf_inst{.opcode = INST_ALU_OP_MOV | INST_SRC_REG | INST_CLS_ALU, .dst = 1, .src = 1}); + + // Merge (rX <<= 32; rX >>= 32) into rX s32= rX. + compare_unmarshal_marshal( + ebpf_inst{.opcode = INST_ALU_OP_LSH | INST_SRC_IMM | INST_CLS_ALU64, .dst = 1, .imm = 32}, + ebpf_inst{.opcode = INST_ALU_OP_ARSH | INST_SRC_IMM | INST_CLS_ALU64, .dst = 1, .imm = 32}, + ebpf_inst{.opcode = INST_ALU_OP_MOV | INST_SRC_REG | INST_CLS_ALU64, .dst = 1, .src = 1, .offset = 32}); +} + TEST_CASE("fail unmarshal invalid opcodes", "[disasm][marshal]") { // The following opcodes are undefined and should generate bad instruction errors. uint8_t bad_opcodes[] = { diff --git a/src/test/test_yaml.cpp b/src/test/test_yaml.cpp index 2456bfe3f..8e95638ad 100644 --- a/src/test/test_yaml.cpp +++ b/src/test/test_yaml.cpp @@ -31,6 +31,7 @@ YAML_CASE("test-data/loop.yaml") YAML_CASE("test-data/movsx.yaml") YAML_CASE("test-data/packet.yaml") YAML_CASE("test-data/parse.yaml") +YAML_CASE("test-data/sext.yaml") YAML_CASE("test-data/shift.yaml") YAML_CASE("test-data/stack.yaml") YAML_CASE("test-data/subtract.yaml") diff --git a/test-data/movsx.yaml b/test-data/movsx.yaml index ef1fe399c..2645dddd8 100644 --- a/test-data/movsx.yaml +++ b/test-data/movsx.yaml @@ -201,6 +201,7 @@ post: - r1.uvalue=[255, 511] - r1.svalue=r1.uvalue - r2.type=number + - r2.svalue=[-128, 127] --- test-case: movsx16 register range to 64 bits @@ -232,6 +233,7 @@ post: - r1.uvalue=[65535, 131071] - r1.svalue=r1.uvalue - r2.type=number + - r2.svalue=[-32768, 32767] --- test-case: movsx32 register range to 64 bits @@ -263,3 +265,4 @@ post: - r1.uvalue=[4294967295, 8589934591] - r1.svalue=r1.uvalue - r2.type=number + - r2.svalue=[-2147483648, 2147483647] diff --git a/test-data/sext.yaml b/test-data/sext.yaml new file mode 100644 index 000000000..0846b4f0e --- /dev/null +++ b/test-data/sext.yaml @@ -0,0 +1,72 @@ +# Copyright (c) Prevail Verifier contributors. +# SPDX-License-Identifier: MIT + +# Tests for signed/unsigned extension +--- +test-case: small zext relational nop + +pre: ["r1.type=number", "r1.uvalue=[0, 5]", + "r2.type=number", "r2.uvalue=[0, 5]", + "r1.uvalue=r2.uvalue"] + +code: + : | + w1 = r1 ; nop + +post: + - r1.type=number + - r1.uvalue=[0, 5] + - r2.type=number + - r2.uvalue=[0, 5] + - r1.uvalue=r2.uvalue +--- +test-case: small sext relational nop + +pre: ["r1.type=number", "r1.svalue=[-5, 5]", + "r2.type=number", "r2.svalue=[-5, 5]", + "r1.svalue=r2.svalue"] + +code: + : | + r1 s32= r1 ; nop + +post: + - r1.type=number + - r1.svalue=[-5, 5] + - r2.type=number + - r2.svalue=[-5, 5] + - r1.svalue=r2.svalue +--- +test-case: large zext relational is two shifts + +pre: ["r1.type=number", "r1.uvalue=[0, 4294967299]", + "r2.type=number", "r2.uvalue=[0, 4294967299]", + "r1.uvalue=r2.uvalue"] + +code: + : | + w1 = r1 ; not a nop + +post: + - r1.type=number + - r1.svalue=[0, 4294967295] + - r1.svalue=r1.uvalue + - r1.uvalue=[0, 4294967295] + - r2.type=number + - r2.uvalue=[0, 4294967299] +--- +test-case: large sext relational is two shifts + +pre: ["r1.type=number", "r1.svalue=[-5, 4294967299]", + "r2.type=number", "r2.svalue=[-5, 4294967299]", + "r1.svalue=r2.svalue"] + +code: + : | + r1 s32= r1 ; not a nop + +post: + - r1.type=number + - r1.svalue=[-2147483648, 2147483647] + - r2.type=number + - r2.svalue=[-5, 4294967299] From 2567b30c92859b9029783921017d4e8409b8d29c Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 15 Feb 2024 15:47:55 +0200 Subject: [PATCH 043/373] bump Catch2 to v3.5.2 Signed-off-by: Elazar Gershuni --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d4c795491..3b0f1a6ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ include(FetchContent) FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git - GIT_TAG ac93f1943762f6fc92f0dc5bac0d720a33a27530 + GIT_TAG 05e10dfccc28c7f973727c54f850237d07d5e10f # v3.5.2 ) FetchContent_MakeAvailable(Catch2) From 1056586f13b5f95f0f16b897197f3fd8bd1fe409 Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Sun, 18 Feb 2024 04:15:27 -0800 Subject: [PATCH 044/373] Deprecate legacy packet access instructions (#577) The BPF ISA specification does not define them, and just says "these instructions are deprecated and should no longer be used". To comply with that, this PR disables support for legacy packet access instructions by default. If some other project depends on them, options.legacy can be set to true. * Deprecate legacy packet access instructions * Add YAML tests for packet access instructions Signed-off-by: Dave Thaler --- src/asm_syntax.hpp | 2 +- src/asm_unmarshal.cpp | 12 ++--- src/asm_unmarshal.hpp | 2 +- src/ebpf_vm_isa.hpp | 4 +- src/ebpf_yaml.cpp | 3 +- src/linux/linux_platform.cpp | 3 +- src/main/check.cpp | 9 ++-- src/platform.hpp | 3 ++ src/test/test_marshal.cpp | 68 ++++++++++++++++------- src/test/test_verify.cpp | 68 +++++++++++++---------- test-data/packet.yaml | 102 +++++++++++++++++++++++++++++++++++ 11 files changed, 213 insertions(+), 63 deletions(-) diff --git a/src/asm_syntax.hpp b/src/asm_syntax.hpp index c8e621161..eff038ba0 100644 --- a/src/asm_syntax.hpp +++ b/src/asm_syntax.hpp @@ -202,7 +202,7 @@ struct Mem { bool is_load{}; }; -/// A special instruction for checked access to packets; it is actually a +/// A deprecated instruction for checked access to packets; it is actually a /// function call, and analyzed as one, e.g., by scratching caller-saved /// registers after it is performed. struct Packet { diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index 4e12356c3..8c2cffded 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -226,17 +226,13 @@ struct Unmarshaller { switch ((inst.opcode & INST_MODE_MASK) >> 5) { case 0: throw InvalidInstruction(pc, inst.opcode); case INST_ABS: - if (!isLD) - throw InvalidInstruction(pc, "ABS but not LD"); - if (width == 8) - note("invalid opcode LDABSDW"); + if (!info.platform->legacy || !isLD || (width == 8)) + throw InvalidInstruction(pc, inst.opcode); return Packet{.width = width, .offset = inst.imm, .regoffset = {}}; case INST_IND: - if (!isLD) - throw InvalidInstruction(pc, "IND but not LD"); - if (width == 8) - note("invalid opcode LDINDDW"); + if (!info.platform->legacy || !isLD || (width == 8)) + throw InvalidInstruction(pc, inst.opcode); return Packet{.width = width, .offset = inst.imm, .regoffset = Reg{inst.src}}; case INST_MEM: { diff --git a/src/asm_unmarshal.hpp b/src/asm_unmarshal.hpp index 1089d1eb0..f5d2ce85f 100644 --- a/src/asm_unmarshal.hpp +++ b/src/asm_unmarshal.hpp @@ -16,7 +16,7 @@ * of Instructions. * * \param raw_prog is the input program to parse. - * \param notes is where errors and warnings are written to. + * \param[out] notes is a vector for storing errors and warnings. * \return a sequence of instruction if successful, an error string otherwise. */ std::variant unmarshal(const raw_program& raw_prog, std::vector>& notes); diff --git a/src/ebpf_vm_isa.hpp b/src/ebpf_vm_isa.hpp index ad308b9af..d734352ff 100644 --- a/src/ebpf_vm_isa.hpp +++ b/src/ebpf_vm_isa.hpp @@ -44,8 +44,8 @@ enum { INST_MODE_MASK = 0xe0, - INST_ABS = 1, - INST_IND = 2, + INST_ABS = 1, // Deprecated + INST_IND = 2, // Deprecated INST_MEM = 3, INST_LEN = 4, INST_MSH = 5, diff --git a/src/ebpf_yaml.cpp b/src/ebpf_yaml.cpp index 25b27a006..f728d6c88 100644 --- a/src/ebpf_yaml.cpp +++ b/src/ebpf_yaml.cpp @@ -57,7 +57,8 @@ ebpf_platform_t g_platform_test = { .map_record_size = 0, .parse_maps_section = ebpf_parse_maps_section, .get_map_descriptor = ebpf_get_map_descriptor, - .get_map_type = ebpf_get_map_type + .get_map_type = ebpf_get_map_type, + .legacy = true, }; static EbpfProgramType make_program_type(const string& name, ebpf_context_descriptor_t* context_descriptor) { diff --git a/src/linux/linux_platform.cpp b/src/linux/linux_platform.cpp index 85fb568af..1ae825cdc 100644 --- a/src/linux/linux_platform.cpp +++ b/src/linux/linux_platform.cpp @@ -254,5 +254,6 @@ const ebpf_platform_t g_ebpf_platform_linux = { parse_maps_section_linux, get_map_descriptor_linux, get_map_type_linux, - resolve_inner_map_references_linux + resolve_inner_map_references_linux, + true // Legacy packet access instructions }; diff --git a/src/main/check.cpp b/src/main/check.cpp index 8b91d6d03..bf832fc73 100644 --- a/src/main/check.cpp +++ b/src/main/check.cpp @@ -66,6 +66,8 @@ int main(int argc, char** argv) { app.add_flag("-f", ebpf_verifier_options.print_failures, "Print verifier's failure logs"); app.add_flag("-s", ebpf_verifier_options.strict, "Apply additional checks that would cause runtime failures"); app.add_flag("-v", verbose, "Print both invariants and failures"); + bool legacy = false; + app.add_flag("--legacy", legacy, "Allow deprecated packet access instructions"); bool no_division_by_zero = false; app.add_flag("--no-division-by-zero", no_division_by_zero, "Do not allow division by zero"); app.add_flag("--no-simplify", ebpf_verifier_options.no_simplify, "Do not simplify"); @@ -111,12 +113,13 @@ int main(int argc, char** argv) { if (domain == "linux") ebpf_verifier_options.mock_map_fds = false; - const ebpf_platform_t* platform = &g_ebpf_platform_linux; + ebpf_platform_t platform = g_ebpf_platform_linux; + platform.legacy = legacy; // Read a set of raw program sections from an ELF file. vector 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; @@ -130,7 +133,7 @@ int main(int argc, char** argv) { if (!desired_section.empty() && raw_progs.empty()) { // We could not find the desired section, 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 << raw_prog.section << " "; diff --git a/src/platform.hpp b/src/platform.hpp index 0816479ab..bc50b9601 100644 --- a/src/platform.hpp +++ b/src/platform.hpp @@ -42,6 +42,9 @@ struct ebpf_platform_t { ebpf_get_map_descriptor_fn get_map_descriptor; ebpf_get_map_type_fn get_map_type; ebpf_resolve_inner_map_references_fn resolve_inner_map_references; + + // Option indicating support for various deprecated instructions. + bool legacy; }; extern const ebpf_platform_t g_ebpf_platform_linux; diff --git a/src/test/test_marshal.cpp b/src/test/test_marshal.cpp index 05eef0bb2..d3a25829e 100644 --- a/src/test/test_marshal.cpp +++ b/src/test/test_marshal.cpp @@ -8,9 +8,9 @@ // Verify that if we unmarshal an instruction and then re-marshal it, // we get what we expect. -static void compare_unmarshal_marshal(const ebpf_inst& ins, const ebpf_inst& expected_result) { - program_info info{.platform = &g_ebpf_platform_linux, - .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")}; +static void compare_unmarshal_marshal(const ebpf_inst& ins, const ebpf_inst& expected_result, const ebpf_platform_t* platform = &g_ebpf_platform_linux) { + program_info info{.platform = platform, + .type = platform->get_program_type("unspec", "unspec")}; const ebpf_inst exit{.opcode = INST_OP_EXIT}; InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", {ins, exit, exit}, info})); REQUIRE(parsed.size() == 3); @@ -62,9 +62,9 @@ static void compare_unmarshal_marshal(const ebpf_inst& ins1, const ebpf_inst& in // Verify that if we marshal an instruction and then unmarshal it, // we get the original. -static void compare_marshal_unmarshal(const Instruction& ins, bool double_cmd = false) { - program_info info{.platform = &g_ebpf_platform_linux, - .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")}; +static void compare_marshal_unmarshal(const Instruction& ins, bool double_cmd = false, const ebpf_platform_t* platform = &g_ebpf_platform_linux) { + program_info info{.platform = platform, + .type = platform->get_program_type("unspec", "unspec")}; InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", marshal(ins, 0), info})); REQUIRE(parsed.size() == 1); auto [_, single, _2] = parsed.back(); @@ -73,16 +73,16 @@ static void compare_marshal_unmarshal(const Instruction& ins, bool double_cmd = REQUIRE(single == ins); } -static void check_marshal_unmarshal_fail(const Instruction& ins, std::string expected_error_message) { - program_info info{.platform = &g_ebpf_platform_linux, - .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")}; +static void check_marshal_unmarshal_fail(const Instruction& ins, std::string expected_error_message, const ebpf_platform_t* platform = &g_ebpf_platform_linux) { + program_info info{.platform = platform, + .type = platform->get_program_type("unspec", "unspec")}; std::string error_message = std::get(unmarshal(raw_program{"", "", marshal(ins, 0), info})); REQUIRE(error_message == expected_error_message); } -static void check_unmarshal_fail(ebpf_inst inst, std::string expected_error_message) { - program_info info{.platform = &g_ebpf_platform_linux, - .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")}; +static void check_unmarshal_fail(ebpf_inst inst, std::string expected_error_message, const ebpf_platform_t* platform = &g_ebpf_platform_linux) { + program_info info{.platform = platform, + .type = platform->get_program_type("unspec", "unspec")}; std::vector insns = {inst}; auto result = unmarshal(raw_program{"", "", insns, info}); REQUIRE(std::holds_alternative(result)); @@ -194,8 +194,10 @@ TEST_CASE("disasm_marshal", "[disasm][marshal]") { SECTION("Packet") { for (int w : ws) { - compare_marshal_unmarshal(Packet{.width = w, .offset = 7, .regoffset = {}}); - compare_marshal_unmarshal(Packet{.width = w, .offset = 7, .regoffset = Reg{2}}); + if (w != 8) { + compare_marshal_unmarshal(Packet{.width = w, .offset = 7, .regoffset = {}}); + compare_marshal_unmarshal(Packet{.width = w, .offset = 7, .regoffset = Reg{2}}); + } } } @@ -367,6 +369,39 @@ TEST_CASE("fail unmarshal offset opcodes", "[disasm][marshal]") { } } +TEST_CASE("check unmarshal legacy opcodes", "[disasm][marshal]") { + // The following opcodes are deprecated and should no longer be used. + static uint8_t supported_legacy_opcodes[] = {0x20, 0x28, 0x30, 0x40, 0x48, 0x50}; + static uint8_t unsupported_legacy_opcodes[] = {0x21, 0x22, 0x23, 0x29, 0x2a, 0x2b, 0x31, 0x32, 0x33, + 0x38, 0x39, 0x3a, 0x3b, 0x41, 0x42, 0x43, 0x49, 0x4a, + 0x4b, 0x51, 0x52, 0x53, 0x58, 0x59, 0x5a, 0x5b}; + + for (uint8_t opcode : unsupported_legacy_opcodes) { + std::ostringstream oss; + oss << "0: Bad instruction op 0x" << std::hex << (int)opcode << std::endl; + check_unmarshal_fail(ebpf_inst{.opcode = opcode}, oss.str().c_str()); + } + + for (uint8_t opcode : supported_legacy_opcodes) { + compare_unmarshal_marshal(ebpf_inst{.opcode = opcode}, ebpf_inst{.opcode = opcode}); + } + + // Disable legacy support. + ebpf_platform_t platform = g_ebpf_platform_linux; + platform.legacy = false; + + for (uint8_t opcode : unsupported_legacy_opcodes) { + std::ostringstream oss; + oss << "0: Bad instruction op 0x" << std::hex << (int)opcode << std::endl; + check_unmarshal_fail(ebpf_inst{.opcode = opcode}, oss.str().c_str(), &platform); + } + for (uint8_t opcode : supported_legacy_opcodes) { + std::ostringstream oss; + oss << "0: Bad instruction op 0x" << std::hex << (int)opcode << std::endl; + check_unmarshal_fail(ebpf_inst{.opcode = opcode}, oss.str().c_str(), &platform); + } +} + TEST_CASE("unmarshal 64bit immediate", "[disasm][marshal]") { compare_unmarshal_marshal(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 0, .imm = 1}, ebpf_inst{.imm = 2}, ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 0, .imm = 1}, ebpf_inst{.imm = 2}); @@ -399,13 +434,10 @@ TEST_CASE("unmarshal 64bit immediate", "[disasm][marshal]") { } } + TEST_CASE("fail unmarshal misc", "[disasm][marshal]") { check_unmarshal_fail(ebpf_inst{.opcode = /* 0x06 */ INST_CLS_JMP32}, "0: jump out of bounds\n"); check_unmarshal_fail(ebpf_inst{.opcode = /* 0x16 */ 0x10 | INST_CLS_JMP32}, "0: jump out of bounds\n"); - check_unmarshal_fail(ebpf_inst{.opcode = /* 0x21 */ (INST_ABS << 5) | INST_SIZE_W | INST_CLS_LDX, .imm = 8}, - "0: ABS but not LD\n"); - check_unmarshal_fail(ebpf_inst{.opcode = /* 0x41 */ (INST_IND << 5) | INST_SIZE_W | INST_CLS_LDX, .imm = 8}, - "0: IND but not LD\n"); check_unmarshal_fail(ebpf_inst{.opcode = /* 0x71 */ ((INST_MEM << 5) | INST_SIZE_B | INST_CLS_LDX), .dst = 11, .imm = 8}, "0: Bad register\n"); check_unmarshal_fail(ebpf_inst{.opcode = /* 0x71 */ ((INST_MEM << 5) | INST_SIZE_B | INST_CLS_LDX), .dst = 1, .src = 11}, diff --git a/src/test/test_verify.cpp b/src/test/test_verify.cpp index 023d780cf..bca93d05b 100644 --- a/src/test/test_verify.cpp +++ b/src/test/test_verify.cpp @@ -20,7 +20,7 @@ FAIL_LOAD_ELF("build", "badrelo.o", ".text") FAIL_LOAD_ELF("invalid", "badsymsize.o", "xdp_redirect_map") #define FAIL_UNMARSHAL(dirname, filename, sectionname) \ - TEST_CASE("Try unmarshalling bad program: " dirname "/" filename, "[unmarshal]") { \ + TEST_CASE("Try unmarshalling bad program: " dirname "/" filename " " sectionname, "[unmarshal]") { \ auto raw_progs = read_elf("ebpf-samples/" dirname "/" filename, sectionname, nullptr, &g_ebpf_platform_linux); \ REQUIRE(raw_progs.size() == 1); \ raw_program raw_prog = raw_progs.back(); \ @@ -32,9 +32,9 @@ FAIL_LOAD_ELF("invalid", "badsymsize.o", "xdp_redirect_map") FAIL_UNMARSHAL("build", "wronghelper.o", "xdp") FAIL_UNMARSHAL("invalid", "invalid-lddw.o", ".text") -#define VERIFY_SECTION(dirname, filename, sectionname, options, pass) \ +#define VERIFY_SECTION(dirname, filename, sectionname, options, platform, pass) \ do { \ - auto raw_progs = read_elf("ebpf-samples/" dirname "/" filename, sectionname, nullptr, &g_ebpf_platform_linux); \ + auto raw_progs = read_elf("ebpf-samples/" dirname "/" filename, sectionname, nullptr, platform); \ REQUIRE(raw_progs.size() == 1); \ raw_program raw_prog = raw_progs.back(); \ std::variant prog_or_error = unmarshal(raw_prog); \ @@ -49,30 +49,42 @@ FAIL_UNMARSHAL("invalid", "invalid-lddw.o", ".text") #define TEST_SECTION(project, filename, section) \ TEST_CASE("./check ebpf-samples/" project "/" filename " " section, "[verify][samples][" project "]") { \ - VERIFY_SECTION(project, filename, section, nullptr, true); \ + VERIFY_SECTION(project, filename, section, nullptr, &g_ebpf_platform_linux, true); \ } #define TEST_SECTION_REJECT(project, filename, section) \ TEST_CASE("./check ebpf-samples/" project "/" filename " " section, "[verify][samples][" project "]") { \ - VERIFY_SECTION(project, filename, section, nullptr, false); \ + VERIFY_SECTION(project, filename, section, nullptr, &g_ebpf_platform_linux, false); \ } #define TEST_SECTION_REJECT_IF_STRICT(project, filename, section) \ TEST_CASE("./check ebpf-samples/" project "/" filename " " section, "[verify][samples][" project "]") { \ ebpf_verifier_options_t options = ebpf_verifier_default_options; \ - VERIFY_SECTION(project, filename, section, &options, true); \ + VERIFY_SECTION(project, filename, section, &options, &g_ebpf_platform_linux, true); \ options.strict = true; \ - VERIFY_SECTION(project, filename, section, &options, false); \ + VERIFY_SECTION(project, filename, section, &options, &g_ebpf_platform_linux, false); \ } #define TEST_SECTION_FAIL(project, filename, section) \ TEST_CASE("expect failure ebpf-samples/" project "/" filename " " section, "[!shouldfail][verify][samples][" project "]") { \ - VERIFY_SECTION(project, filename, section, nullptr, true); \ + VERIFY_SECTION(project, filename, section, nullptr, &g_ebpf_platform_linux, true); \ } #define TEST_SECTION_REJECT_FAIL(project, filename, section) \ TEST_CASE("expect failure ebpf-samples/" project "/" filename " " section, "[!shouldfail][verify][samples][" project "]") { \ - VERIFY_SECTION(project, filename, section, nullptr, false); \ + VERIFY_SECTION(project, filename, section, nullptr, &g_ebpf_platform_linux, false); \ + } + +#define TEST_SECTION_LEGACY(dirname, filename, sectionname) \ + TEST_SECTION(dirname, filename, sectionname) \ + TEST_CASE("Try unmarshalling bad program: " dirname "/" filename " " sectionname, "[unmarshal]") { \ + ebpf_platform_t platform = g_ebpf_platform_linux; \ + platform.legacy = false; \ + auto raw_progs = read_elf("ebpf-samples/" dirname "/" filename, sectionname, nullptr, &platform); \ + REQUIRE(raw_progs.size() == 1); \ + raw_program raw_prog = raw_progs.back(); \ + std::variant prog_or_error = unmarshal(raw_prog); \ + REQUIRE(std::holds_alternative(prog_or_error)); \ } TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "1/0xdc06") @@ -82,7 +94,7 @@ TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/4") TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/5") TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/6") TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/7") -TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/10") +TEST_SECTION_LEGACY("bpf_cilium_test", "bpf_lxc_jit.o", "2/10") TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "from-container") TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "1/0x1010") @@ -93,7 +105,7 @@ TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "2/4") TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "2/5") TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "2/6") TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "2/7") -TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "from-container") +TEST_SECTION_LEGACY("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "from-container") TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "1/0x1010") TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "2/1") @@ -111,7 +123,7 @@ TEST_SECTION("bpf_cilium_test", "bpf_netdev.o", "2/3") TEST_SECTION("bpf_cilium_test", "bpf_netdev.o", "2/4") TEST_SECTION("bpf_cilium_test", "bpf_netdev.o", "2/5") TEST_SECTION("bpf_cilium_test", "bpf_netdev.o", "2/7") -TEST_SECTION("bpf_cilium_test", "bpf_netdev.o", "from-netdev") +TEST_SECTION_LEGACY("bpf_cilium_test", "bpf_netdev.o", "from-netdev") TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "2/1") TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "2/2") @@ -120,7 +132,7 @@ TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "2/4") TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "2/5") TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "2/7") TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "3/2") -TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "from-overlay") +TEST_SECTION_LEGACY("bpf_cilium_test", "bpf_overlay.o", "from-overlay") TEST_SECTION("bpf_cilium_test", "bpf_lb-DLB_L3.o", "2/1") TEST_SECTION("bpf_cilium_test", "bpf_lb-DLB_L3.o", "2/2") @@ -146,7 +158,7 @@ TEST_SECTION("cilium", "bpf_lxc.o", "2/6") TEST_SECTION("cilium", "bpf_lxc.o", "2/7") TEST_SECTION("cilium", "bpf_lxc.o", "2/8") TEST_SECTION("cilium", "bpf_lxc.o", "2/9") -TEST_SECTION("cilium", "bpf_lxc.o", "2/10") +TEST_SECTION_LEGACY("cilium", "bpf_lxc.o", "2/10") TEST_SECTION("cilium", "bpf_lxc.o", "2/11") TEST_SECTION("cilium", "bpf_lxc.o", "2/12") TEST_SECTION("cilium", "bpf_lxc.o", "from-container") @@ -156,14 +168,14 @@ TEST_SECTION("cilium", "bpf_netdev.o", "2/3") TEST_SECTION("cilium", "bpf_netdev.o", "2/4") TEST_SECTION("cilium", "bpf_netdev.o", "2/5") TEST_SECTION("cilium", "bpf_netdev.o", "2/7") -TEST_SECTION("cilium", "bpf_netdev.o", "from-netdev") +TEST_SECTION_LEGACY("cilium", "bpf_netdev.o", "from-netdev") TEST_SECTION("cilium", "bpf_overlay.o", "2/1") TEST_SECTION("cilium", "bpf_overlay.o", "2/3") TEST_SECTION("cilium", "bpf_overlay.o", "2/4") TEST_SECTION("cilium", "bpf_overlay.o", "2/5") TEST_SECTION("cilium", "bpf_overlay.o", "2/7") -TEST_SECTION("cilium", "bpf_overlay.o", "from-overlay") +TEST_SECTION_LEGACY("cilium", "bpf_overlay.o", "from-overlay") TEST_SECTION("cilium", "bpf_xdp.o", "from-netdev") @@ -191,13 +203,13 @@ TEST_SECTION("linux", "offwaketime_kern.o", "tracepoint/sched/sched_switch") TEST_SECTION("linux", "sampleip_kern.o", "perf_event") TEST_SECTION("linux", "sock_flags_kern.o", "cgroup/sock1") TEST_SECTION("linux", "sock_flags_kern.o", "cgroup/sock2") -TEST_SECTION("linux", "sockex1_kern.o", "socket1") -TEST_SECTION("linux", "sockex2_kern.o", "socket2") -TEST_SECTION("linux", "sockex3_kern.o", "socket/3") -TEST_SECTION("linux", "sockex3_kern.o", "socket/4") -TEST_SECTION("linux", "sockex3_kern.o", "socket/1") -TEST_SECTION("linux", "sockex3_kern.o", "socket/2") -TEST_SECTION("linux", "sockex3_kern.o", "socket/0") +TEST_SECTION_LEGACY("linux", "sockex1_kern.o", "socket1") +TEST_SECTION_LEGACY("linux", "sockex2_kern.o", "socket2") +TEST_SECTION_LEGACY("linux", "sockex3_kern.o", "socket/3") +TEST_SECTION_LEGACY("linux", "sockex3_kern.o", "socket/4") +TEST_SECTION_LEGACY("linux", "sockex3_kern.o", "socket/1") +TEST_SECTION_LEGACY("linux", "sockex3_kern.o", "socket/2") +TEST_SECTION_LEGACY("linux", "sockex3_kern.o", "socket/0") TEST_SECTION("linux", "spintest_kern.o", "kprobe/__htab_percpu_map_update_elem") TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_lock") TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_lock_bh") @@ -227,7 +239,7 @@ TEST_SECTION("linux", "tcp_basertt_kern.o", "sockops") TEST_SECTION("linux", "tcp_bufs_kern.o", "sockops") TEST_SECTION("linux", "tcp_cong_kern.o", "sockops") TEST_SECTION("linux", "tcp_iw_kern.o", "sockops") -TEST_SECTION("linux", "tcbpf1_kern.o", "classifier") +TEST_SECTION_LEGACY("linux", "tcbpf1_kern.o", "classifier") TEST_SECTION("linux", "tcbpf1_kern.o", "clone_redirect_recv") TEST_SECTION("linux", "tcbpf1_kern.o", "clone_redirect_xmit") TEST_SECTION("linux", "tcbpf1_kern.o", "redirect_recv") @@ -335,7 +347,7 @@ TEST_SECTION("prototype-kernel", "xdp_vlan01_kern.o", "xdp_vlan_remove_outer2") TEST_SECTION("ovs", "datapath.o", "tail-0") TEST_SECTION("ovs", "datapath.o", "tail-1") TEST_SECTION("ovs", "datapath.o", "tail-2") -TEST_SECTION("ovs", "datapath.o", "tail-3") +TEST_SECTION_LEGACY("ovs", "datapath.o", "tail-3") TEST_SECTION("ovs", "datapath.o", "tail-4") TEST_SECTION("ovs", "datapath.o", "tail-5") TEST_SECTION("ovs", "datapath.o", "tail-7") @@ -343,7 +355,7 @@ TEST_SECTION("ovs", "datapath.o", "tail-8") TEST_SECTION("ovs", "datapath.o", "tail-11") TEST_SECTION("ovs", "datapath.o", "tail-12") TEST_SECTION("ovs", "datapath.o", "tail-13") -TEST_SECTION("ovs", "datapath.o", "tail-32") +TEST_SECTION_LEGACY("ovs", "datapath.o", "tail-32") TEST_SECTION("ovs", "datapath.o", "tail-33") TEST_SECTION("ovs", "datapath.o", "tail-35") TEST_SECTION("ovs", "datapath.o", "af_xdp") @@ -352,8 +364,8 @@ TEST_SECTION("ovs", "datapath.o", "egress") TEST_SECTION("ovs", "datapath.o", "ingress") TEST_SECTION("ovs", "datapath.o", "xdp") -TEST_SECTION("suricata", "bypass_filter.o", "filter") -TEST_SECTION("suricata", "lb.o", "loadbalancer") +TEST_SECTION_LEGACY("suricata", "bypass_filter.o", "filter") +TEST_SECTION_LEGACY("suricata", "lb.o", "loadbalancer") TEST_SECTION("suricata", "filter.o", "filter") TEST_SECTION("suricata", "vlan_filter.o", "filter") TEST_SECTION("suricata", "xdp_filter.o", "xdp") diff --git a/test-data/packet.yaml b/test-data/packet.yaml index 4de7f6745..57c31c203 100644 --- a/test-data/packet.yaml +++ b/test-data/packet.yaml @@ -106,3 +106,105 @@ post: [ "r3.svalue=r3.uvalue", "r2.packet_offset-r3.packet_offset<=65526", "r3.packet_offset-r2.packet_offset<=8" ] +--- +test-case: legacy 1 byte packet access imm + +pre: [ + "r1.type=number", + "r6.type=ctx", "r6.ctx_offset=0" +] + +code: + : | + r0 = *(u8 *)skb[23] + +post: [ + "r0.type=number", + "r6.type=ctx", "r6.ctx_offset=0" +] +--- +test-case: legacy 2 byte packet access imm + +pre: [ + "r1.type=number", + "r6.type=ctx", "r6.ctx_offset=0" +] + +code: + : | + r0 = *(u16 *)skb[23] + +post: [ + "r0.type=number", + "r6.type=ctx", "r6.ctx_offset=0" +] +--- +test-case: legacy 4 byte packet access imm + +pre: [ + "r1.type=number", + "r6.type=ctx", "r6.ctx_offset=0" +] + +code: + : | + r0 = *(u32 *)skb[23] + +post: [ + "r0.type=number", + "r6.type=ctx", "r6.ctx_offset=0" +] +--- +test-case: legacy 1 byte packet access reg + +pre: [ + "r1.type=number", + "r6.type=ctx", "r6.ctx_offset=0", + "r7.type=number", "r7.svalue=23", "r7.uvalue=23" +] + +code: + : | + r0 = *(u8 *)skb[r7] + +post: [ + "r0.type=number", + "r6.type=ctx", "r6.ctx_offset=0", + "r7.type=number", "r7.svalue=23", "r7.uvalue=23" +] +--- +test-case: legacy 2 byte packet access reg + +pre: [ + "r1.type=number", + "r6.type=ctx", "r6.ctx_offset=0", + "r7.type=number", "r7.svalue=23", "r7.uvalue=23" +] + +code: + : | + r0 = *(u16 *)skb[r7] + +post: [ + "r0.type=number", + "r6.type=ctx", "r6.ctx_offset=0", + "r7.type=number", "r7.svalue=23", "r7.uvalue=23" +] +--- +test-case: legacy 4 byte packet access reg + +pre: [ + "r1.type=number", + "r6.type=ctx", "r6.ctx_offset=0", + "r7.type=number", "r7.svalue=23", "r7.uvalue=23" +] + +code: + : | + r0 = *(u32 *)skb[r7] + +post: [ + "r0.type=number", + "r6.type=ctx", "r6.ctx_offset=0", + "r7.type=number", "r7.svalue=23", "r7.uvalue=23" +] From 3db1b62c4c878ed6e64f9bef0b6aefa8cfc29a19 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Wed, 21 Feb 2024 20:21:52 +0200 Subject: [PATCH 045/373] Use default equality for asm_syntax structs (#592) * Use default equality for asm_syntax structs * Add test for NEG where .id64=true Signed-off-by: Elazar Gershuni Co-authored-by: Dave Thaler --- src/asm_marshal.cpp | 17 +++--- src/asm_syntax.hpp | 109 +++++++++++++------------------------- src/asm_unmarshal.cpp | 5 +- src/test/test_marshal.cpp | 47 +++++++++------- 4 files changed, 76 insertions(+), 102 deletions(-) diff --git a/src/asm_marshal.cpp b/src/asm_marshal.cpp index 5b42c5914..114baed19 100644 --- a/src/asm_marshal.cpp +++ b/src/asm_marshal.cpp @@ -67,10 +67,10 @@ static int16_t offset(Bin::Op op) { return 0; } -static uint8_t imm(Un::Op op) { +static uint8_t imm_endian(Un::Op op) { using Op = Un::Op; switch (op) { - case Op::NEG: return 0; + case Op::NEG: assert(false); return 0; case Op::BE16: case Op::LE16: case Op::SWAP16: return 16; @@ -132,22 +132,21 @@ struct MarshalVisitor { switch (b.op) { case Un::Op::NEG: return {ebpf_inst{ - // FIX: should be INST_CLS_ALU / INST_CLS_ALU64 - .opcode = static_cast(INST_CLS_ALU | 0x3 | INST_ALU_OP_NEG), + .opcode = static_cast((b.is64 ? INST_CLS_ALU64 : INST_CLS_ALU) | INST_ALU_OP_NEG), .dst = b.dst.v, .src = 0, .offset = 0, - .imm = imm(b.op), + .imm = 0, }}; case Un::Op::LE16: case Un::Op::LE32: case Un::Op::LE64: return {ebpf_inst{ - .opcode = static_cast(INST_CLS_ALU | INST_ALU_OP_END), + .opcode = static_cast(INST_CLS_ALU | INST_END_LE | INST_ALU_OP_END), .dst = b.dst.v, .src = 0, .offset = 0, - .imm = imm(b.op), + .imm = imm_endian(b.op), }}; case Un::Op::BE16: case Un::Op::BE32: @@ -157,7 +156,7 @@ struct MarshalVisitor { .dst = b.dst.v, .src = 0, .offset = 0, - .imm = imm(b.op), + .imm = imm_endian(b.op), }}; case Un::Op::SWAP16: case Un::Op::SWAP32: @@ -167,7 +166,7 @@ struct MarshalVisitor { .dst = b.dst.v, .src = 0, .offset = 0, - .imm = imm(b.op), + .imm = imm_endian(b.op), }}; } assert(false); diff --git a/src/asm_syntax.hpp b/src/asm_syntax.hpp index eff038ba0..b05574cb6 100644 --- a/src/asm_syntax.hpp +++ b/src/asm_syntax.hpp @@ -22,8 +22,8 @@ struct label_t { return label_t{src_label.from, target_label.from}; } - constexpr bool operator==(const label_t& other) const { return from == other.from && to == other.to; } - constexpr bool operator!=(const label_t& other) const { return !(*this == other); } + constexpr bool operator==(const label_t&) const = default; + constexpr bool operator<(const label_t& other) const { if (this == &other) return false; if (*this == label_t::exit) return false; @@ -61,11 +61,13 @@ namespace asm_syntax { /// Immediate argument. struct Imm { uint64_t v{}; + constexpr bool operator==(const Imm&) const = default; }; /// Register argument. struct Reg { uint8_t v{}; + constexpr bool operator==(const Reg&) const = default; }; using Value = std::variant; @@ -97,6 +99,7 @@ struct Bin { Value v; bool is64{}; bool lddw{}; + constexpr bool operator==(const Bin&) const = default; }; /// Unary operation. @@ -117,6 +120,7 @@ struct Un { Op op; Reg dst; bool is64{}; + constexpr bool operator==(const Un&) const = default; }; /// This instruction is encoded similarly to LDDW. @@ -124,6 +128,7 @@ struct Un { struct LoadMapFd { Reg dst; int32_t mapfd{}; + constexpr bool operator==(const LoadMapFd&) const = default; }; struct Condition { @@ -146,11 +151,13 @@ struct Condition { Reg left; Value right; bool is64{}; + constexpr bool operator==(const Condition&) const = default; }; struct Jmp { std::optional cond; label_t target; + constexpr bool operator==(const Jmp&) const = default; }; struct ArgSingle { @@ -164,6 +171,7 @@ struct ArgSingle { ANYTHING, } kind{}; Reg reg; + constexpr bool operator==(const ArgSingle&) const = default; }; /// Pair of arguments to a function for pointer and size. @@ -176,10 +184,16 @@ struct ArgPair { Reg mem; ///< Pointer. Reg size; ///< Size of space pointed to. bool can_be_zero{}; + constexpr bool operator==(const ArgPair&) const = default; }; struct Call { int32_t func{}; + constexpr bool operator==(const Call& other) const { + return func == other.func; + } + + // TODO: move name and signature information somewhere else std::string name; bool is_map_lookup{}; bool reallocate_packet{}; @@ -187,12 +201,15 @@ struct Call { std::vector pairs; }; -struct Exit {}; +struct Exit { + constexpr bool operator==(const Exit&) const = default; +}; struct Deref { int32_t width{}; Reg basereg; int32_t offset{}; + constexpr bool operator==(const Deref&) const = default; }; /// Load/store instruction. @@ -200,6 +217,7 @@ struct Mem { Deref access; Value value; bool is_load{}; + constexpr bool operator==(const Mem&) const = default; }; /// A deprecated instruction for checked access to packets; it is actually a @@ -209,17 +227,20 @@ struct Packet { int32_t width{}; int32_t offset{}; std::optional regoffset; + constexpr bool operator==(const Packet&) const = default; }; /// Special instruction for incrementing values inside shared memory. struct LockAdd { Deref access; Reg valreg; + constexpr bool operator==(const LockAdd&) const = default; }; /// Not an instruction, just used for failure cases. struct Undefined { int opcode{}; + constexpr bool operator==(const Undefined&) const = default; }; /// When a CFG is translated to its nondeterministic form, Conditional Jump @@ -227,6 +248,7 @@ struct Undefined { /// the branch and before each jump target. struct Assume { Condition cond; + constexpr bool operator==(const Assume&) const = default; }; enum class TypeGroup { @@ -250,6 +272,7 @@ enum class TypeGroup { struct ValidSize { Reg reg; bool can_be_zero{}; + constexpr bool operator==(const ValidSize&) const = default; }; /// Condition check whether two registers can be compared with each other. @@ -259,18 +282,21 @@ struct Comparable { Reg r1; Reg r2; bool or_r2_is_number{}; ///< true for subtraction, false for comparison + constexpr bool operator==(const Comparable&) const = default; }; // ptr: ptr -> num : num struct Addable { Reg ptr; Reg num; + constexpr bool operator==(const Addable&) const = default; }; // Condition check whether a register contains a non-zero number. struct ValidDivisor { Reg reg; bool is_signed{}; + constexpr bool operator==(const ValidDivisor&) const = default; }; enum class AccessType { @@ -285,6 +311,7 @@ struct ValidAccess { Value width{Imm{0}}; bool or_null{}; AccessType access_type{}; + constexpr bool operator==(const ValidAccess&) const = default; }; /// Condition check whether something is a valid key value. @@ -292,22 +319,26 @@ struct ValidMapKeyValue { Reg access_reg; Reg map_fd_reg; bool key{}; + constexpr bool operator==(const ValidMapKeyValue&) const = default; }; // "if mem is not stack, val is num" struct ValidStore { Reg mem; Reg val; + constexpr bool operator==(const ValidStore&) const = default; }; struct TypeConstraint { Reg reg; TypeGroup types; + constexpr bool operator==(const TypeConstraint&) const = default; }; /// Condition check whether something is a valid size. struct ZeroCtxOffset { Reg reg; + constexpr bool operator==(const ZeroCtxOffset&) const = default; }; using AssertionConstraint = @@ -316,10 +347,12 @@ using AssertionConstraint = struct Assert { AssertionConstraint cst; Assert(AssertionConstraint cst): cst(cst) { } + constexpr bool operator==(const Assert&) const = default; }; struct IncrementLoopCounter { label_t name; + constexpr bool operator==(const IncrementLoopCounter&) const = default; }; using Instruction = std::variant; @@ -327,77 +360,9 @@ using Instruction = std::variant>; using InstructionSeq = std::vector; - -#define DECLARE_EQ5(T, f1, f2, f3, f4, f5) \ - inline bool operator==(T const& a, T const& b) { \ - return a.f1 == b.f1 && a.f2 == b.f2 && a.f3 == b.f3 && a.f4 == b.f4 && a.f5 == b.f5; \ - } -#define DECLARE_EQ3(T, f1, f2, f3) \ - inline bool operator==(T const& a, T const& b) { return a.f1 == b.f1 && a.f2 == b.f2 && a.f3 == b.f3; } -#define DECLARE_EQ2(T, f1, f2) \ - inline bool operator==(T const& a, T const& b) { return a.f1 == b.f1 && a.f2 == b.f2; } -#define DECLARE_EQ1(T, f1) \ - inline bool operator==(T const& a, T const& b) { return a.f1 == b.f1; } - // cpu=v4 supports 32-bit PC offsets so we need a large enough type. using pc_t = size_t; -// Helpers: - -struct InstructionVisitorPrototype { - void operator()(Undefined const& a); - void operator()(LoadMapFd const& a); - void operator()(Bin const& a); - void operator()(Un const& a); - void operator()(Call const& a); - void operator()(Exit const& a); - void operator()(Jmp const& a); - void operator()(Assume const& a); - void operator()(Assert const& a); - void operator()(Packet const& a); - void operator()(Mem const& a); - void operator()(LockAdd const& a); -}; - -inline bool operator==(Imm const& a, Imm const& b) { return a.v == b.v; } -inline bool operator==(Reg const& a, Reg const& b) { return a.v == b.v; } -inline bool operator==(Deref const& a, Deref const& b) { - return a.basereg == b.basereg && a.offset == b.offset && a.width == b.width; -} -inline bool operator==(Condition const& a, Condition const& b) { - return a.left == b.left && a.op == b.op && a.right == b.right; -} -inline bool operator==(Undefined const& a, Undefined const& b) { return a.opcode == b.opcode; } -inline bool operator==(LoadMapFd const& a, LoadMapFd const& b) { return a.dst == b.dst && a.mapfd == b.mapfd; } -inline bool operator==(Bin const& a, Bin const& b) { - return a.op == b.op && a.dst == b.dst && a.is64 == b.is64 && a.v == b.v && a.lddw == b.lddw; -} -inline bool operator==(Un const& a, Un const& b) { return a.op == b.op && a.dst == b.dst; } -inline bool operator==(Call const& a, Call const& b) { return a.func == b.func; } -inline bool operator==(Exit const& a, Exit const& b) { return true; } -inline bool operator==(Jmp const& a, Jmp const& b) { return a.cond == b.cond && a.target == b.target; } -inline bool operator==(Packet const& a, Packet const& b) { - return a.offset == b.offset && a.regoffset == b.regoffset && a.width == b.width; -} -inline bool operator==(Mem const& a, Mem const& b) { - return a.access == b.access && a.value == b.value && a.is_load == b.is_load; -} -inline bool operator==(LockAdd const& a, LockAdd const& b) { return a.access == b.access && a.valreg == b.valreg; } -inline bool operator==(Assume const& a, Assume const& b) { return a.cond == b.cond; } -bool operator==(Assert const& a, Assert const& b); - -DECLARE_EQ2(TypeConstraint, reg, types) -DECLARE_EQ2(ValidSize, reg, can_be_zero) -DECLARE_EQ2(Comparable, r1, r2) -DECLARE_EQ2(Addable, ptr, num) -DECLARE_EQ2(ValidDivisor, reg, is_signed) -DECLARE_EQ2(ValidStore, mem, val) -DECLARE_EQ5(ValidAccess, reg, offset, width, or_null, access_type) -DECLARE_EQ3(ValidMapKeyValue, access_reg, map_fd_reg, key) -DECLARE_EQ1(ZeroCtxOffset, reg) -DECLARE_EQ1(Assert, cst) -DECLARE_EQ1(IncrementLoopCounter, name) - } using namespace asm_syntax; @@ -406,5 +371,3 @@ template struct overloaded : Ts... { using Ts::operator()...; }; -template -overloaded(Ts...)->overloaded; diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index 8c2cffded..22d0dfff8 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -293,13 +293,14 @@ struct Unmarshaller { throw InvalidInstruction(pc, "Invalid target r10"); if (inst.dst > R10_STACK_POINTER || inst.src > R10_STACK_POINTER) throw InvalidInstruction(pc, "Bad register"); - return std::visit(overloaded{[&](Un::Op op) -> Instruction { return Un{.op = op, .dst = Reg{inst.dst}, .is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64}; }, + bool is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64; + return std::visit(overloaded{[&](Un::Op op) -> Instruction { return Un{.op = op, .dst = Reg{inst.dst}, .is64 = is64}; }, [&](Bin::Op op) -> Instruction { Bin res{ .op = op, .dst = Reg{inst.dst}, .v = getBinValue(pc, inst), - .is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64, + .is64 = is64, }; if (!thread_local_options.allow_division_by_zero && (op == Bin::Op::UDIV || op == Bin::Op::UMOD)) if (std::holds_alternative(res.v) && std::get(res.v).v == 0) diff --git a/src/test/test_marshal.cpp b/src/test/test_marshal.cpp index d3a25829e..a92161bbe 100644 --- a/src/test/test_marshal.cpp +++ b/src/test/test_marshal.cpp @@ -133,22 +133,33 @@ TEST_CASE("disasm_marshal", "[disasm][marshal]") { } } } - - SECTION("Un") { - auto ops = { - Un::Op::BE16, - Un::Op::BE32, - Un::Op::BE64, - Un::Op::LE16, - Un::Op::LE32, - Un::Op::LE64, - Un::Op::NEG, - Un::Op::SWAP16, - Un::Op::SWAP32, - Un::Op::SWAP64 - }; - for (auto op : ops) - compare_marshal_unmarshal(Un{.op = op, .dst = Reg{1}, .is64 = true}); + SECTION("Neg") { + compare_marshal_unmarshal(Un{.op = Un::Op::NEG, .dst = Reg{1}, .is64 = false}); + compare_marshal_unmarshal(Un{.op = Un::Op::NEG, .dst = Reg{1}, .is64 = true}); + } + SECTION("Endian") { + // FIX: `.is64` comes from the instruction class (BPF_ALU or BPF_ALU64) but is unused since it can be derived from `.op`. + { + auto ops = { + Un::Op::BE16, + Un::Op::BE32, + Un::Op::BE64, + Un::Op::LE16, + Un::Op::LE32, + Un::Op::LE64, + }; + for (auto op : ops) + compare_marshal_unmarshal(Un{.op = op, .dst = Reg{1}, .is64 = false}); + } + { + auto ops = { + Un::Op::SWAP16, + Un::Op::SWAP32, + Un::Op::SWAP64, + }; + for (auto op : ops) + compare_marshal_unmarshal(Un{.op = op, .dst = Reg{1}, .is64 = true}); + } } SECTION("LoadMapFd") { compare_marshal_unmarshal(LoadMapFd{.dst = Reg{1}, .mapfd = 1}, true); } @@ -167,7 +178,7 @@ TEST_CASE("disasm_marshal", "[disasm][marshal]") { } SECTION("Reg right") { for (auto op : ops) { - Condition cond{.op = op, .left = Reg{1}, .right = Reg{2}}; + Condition cond{.op = op, .left = Reg{1}, .right = Reg{2}, .is64 = true}; compare_marshal_unmarshal(Jmp{.cond = cond, .target = label_t(0)}); // The following should fail unmarshalling since it jumps past the end of the instruction set. @@ -176,7 +187,7 @@ TEST_CASE("disasm_marshal", "[disasm][marshal]") { } SECTION("Imm right") { for (auto op : ops) { - Condition cond{.op = op, .left = Reg{1}, .right = Imm{2}}; + Condition cond{.op = op, .left = Reg{1}, .right = Imm{2}, .is64 = true}; compare_marshal_unmarshal(Jmp{.cond = cond, .target = label_t(0)}); // The following should fail unmarshalling since it jumps past the end of the instruction set. From ec074dfcd5202253bfcff6542b33efc998eefa2b Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Tue, 27 Feb 2024 11:51:40 -0800 Subject: [PATCH 046/373] Bump external/bpf_conformance from 583758e to f61ccaf (#594) * Bump external/bpf_conformance from `583758e` to `f61ccaf` * Update per bpf_conformance API change * Include div32-by-zero-reg-2.data conformacne test --------- Signed-off-by: Dave Thaler Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- external/bpf_conformance | 2 +- src/test/test_conformance.cpp | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/external/bpf_conformance b/external/bpf_conformance index 583758eee..642b1b0f5 160000 --- a/external/bpf_conformance +++ b/external/bpf_conformance @@ -1 +1 @@ -Subproject commit 583758eee4907dd3a356a0f73939751415d46bf7 +Subproject commit 642b1b0f585ef394420dcd47fead2b8291ddfd72 diff --git a/src/test/test_conformance.cpp b/src/test/test_conformance.cpp index ff9340c78..ca04dd11b 100644 --- a/src/test/test_conformance.cpp +++ b/src/test/test_conformance.cpp @@ -12,9 +12,9 @@ void test_conformance(std::string filename, bpf_conformance_test_result_t expect boost::filesystem::path extension = test_path.extension(); std::filesystem::path plugin_path = test_path.remove_filename().append("conformance_check" + extension.string()).string(); - std::map> result = - bpf_conformance(test_files, plugin_path, {}, {}, {}, bpf_conformance_test_CPU_version_t::v4, - bpf_conformance_list_instructions_t::LIST_INSTRUCTIONS_NONE, true); + std::map> result = bpf_conformance( + test_files, plugin_path, {}, {}, {}, bpf_conformance_test_CPU_version_t::v4, + _bpf_conformance_groups::default_groups, bpf_conformance_list_instructions_t::LIST_INSTRUCTIONS_NONE, true); for (auto file : test_files) { auto& [file_result, reason] = result[file]; REQUIRE(file_result == expected_result); @@ -77,6 +77,7 @@ TEST_CONFORMANCE("be64.data") TEST_CONFORMANCE_VERIFICATION_FAILED("call_local.data") TEST_CONFORMANCE("call_unwind_fail.data") TEST_CONFORMANCE("div32-by-zero-reg.data") +TEST_CONFORMANCE("div32-by-zero-reg-2.data") TEST_CONFORMANCE("div32-high-divisor.data") TEST_CONFORMANCE("div32-imm.data") TEST_CONFORMANCE("div32-reg.data") From e0c25fc9544b1eb8dfa21ee568a21c13eab72eee Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Wed, 28 Feb 2024 09:30:22 -0800 Subject: [PATCH 047/373] Add more negative unmarshaling tests (#591) * Add more negative unmarshaling tests * table based tests * Lower case unmarshaling errors for consistency * Simplify c_str uses --------- Signed-off-by: Dave Thaler --- src/asm_unmarshal.cpp | 66 +++-- src/ebpf_vm_isa.hpp | 1 + src/test/test_conformance.cpp | 2 +- src/test/test_marshal.cpp | 453 +++++++++++++++++++++++++--------- 4 files changed, 385 insertions(+), 137 deletions(-) diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index 22d0dfff8..351493355 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -50,7 +50,7 @@ struct InvalidInstruction : std::invalid_argument { size_t pc; explicit InvalidInstruction(size_t pc, const char* what) : std::invalid_argument{what}, pc{pc} {} InvalidInstruction(size_t pc, std::string what) : std::invalid_argument{what}, pc{pc} {} - InvalidInstruction(size_t pc, uint8_t opcode) : std::invalid_argument{make_opcode_message("Bad instruction", opcode)}, pc{pc} {} + InvalidInstruction(size_t pc, uint8_t opcode) : std::invalid_argument{make_opcode_message("bad instruction", opcode)}, pc{pc} {} }; struct UnsupportedMemoryMode : std::invalid_argument { @@ -115,7 +115,7 @@ struct Unmarshaller { } case INST_ALU_OP_MOV: if (inst.offset > 0 && !(inst.opcode & INST_SRC_REG)) - throw InvalidInstruction(pc, make_opcode_message("invalid offset for", inst.opcode)); + throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode)); switch (inst.offset) { case 0: return Bin::Op::MOV; case 8: return Bin::Op::MOVSX8; @@ -142,7 +142,7 @@ struct Unmarshaller { if (inst.opcode & INST_SRC_REG) throw InvalidInstruction{pc, inst.opcode}; if (inst.src != 0) - throw InvalidInstruction{pc, make_opcode_message("nonzero src for register", inst.opcode)}; + throw InvalidInstruction{pc, inst.opcode}; if (inst.imm != 0) throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode)); return Un::Op::NEG; @@ -153,7 +153,7 @@ struct Unmarshaller { return Bin::Op::ARSH; case INST_ALU_OP_END: if (inst.src != 0) - throw InvalidInstruction{pc, make_opcode_message("nonzero src for register", inst.opcode)}; + throw InvalidInstruction{pc, inst.opcode}; if ((inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64) { if (inst.opcode & INST_END_BE) throw InvalidInstruction(pc, inst.opcode); @@ -161,7 +161,7 @@ struct Unmarshaller { case 16: return Un::Op::SWAP16; case 32: return Un::Op::SWAP32; case 64: return Un::Op::SWAP64; - default: throw InvalidInstruction(pc, "invalid endian immediate"); + default: throw InvalidInstruction(pc, "unsupported immediate"); } } switch (inst.imm) { @@ -169,7 +169,7 @@ struct Unmarshaller { case 32: return (inst.opcode & INST_END_BE) ? Un::Op::BE32 : Un::Op::LE32; case 64: return (inst.opcode & INST_END_BE) ? Un::Op::BE64 : Un::Op::LE64; default: - throw InvalidInstruction(pc, "invalid endian immediate"); + throw InvalidInstruction(pc, "unsupported immediate"); } case 0xe0: throw InvalidInstruction{pc, inst.opcode}; case 0xf0: throw InvalidInstruction{pc, inst.opcode}; @@ -188,7 +188,7 @@ struct Unmarshaller { return Reg{inst.src}; } else { if (inst.src != 0) - throw InvalidInstruction{pc, make_opcode_message("nonzero src for register", inst.opcode)}; + throw InvalidInstruction{pc, inst.opcode}; // Imm is a signed 32-bit number. Sign extend it to 64-bits for storage. return Imm{sign_extend(inst.imm)}; } @@ -219,7 +219,7 @@ struct Unmarshaller { auto makeMemOp(pc_t pc, ebpf_inst inst) -> Instruction { if (inst.dst > R10_STACK_POINTER || inst.src > R10_STACK_POINTER) - throw InvalidInstruction(pc, "Bad register"); + throw InvalidInstruction(pc, "bad register"); int width = getMemWidth(inst.opcode); bool isLD = (inst.opcode & INST_CLS_MASK) == INST_CLS_LD; @@ -228,11 +228,23 @@ struct Unmarshaller { case INST_ABS: if (!info.platform->legacy || !isLD || (width == 8)) throw InvalidInstruction(pc, inst.opcode); + if (inst.dst != 0) + throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode)); + if (inst.src > 0) + throw InvalidInstruction(pc, make_opcode_message("bad instruction", inst.opcode)); + if (inst.offset != 0) + throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode)); return Packet{.width = width, .offset = inst.imm, .regoffset = {}}; case INST_IND: if (!info.platform->legacy || !isLD || (width == 8)) throw InvalidInstruction(pc, inst.opcode); + if (inst.dst != 0) + throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode)); + if (inst.src > R10_STACK_POINTER) + throw InvalidInstruction(pc, "bad register"); + if (inst.offset != 0) + throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode)); return Packet{.width = width, .offset = inst.imm, .regoffset = Reg{inst.src}}; case INST_MEM: { @@ -243,7 +255,7 @@ struct Unmarshaller { throw InvalidInstruction(pc, "Cannot modify r10"); bool isImm = !(inst.opcode & 1); if (isImm && inst.src != 0) - throw InvalidInstruction(pc, make_opcode_message("nonzero src for register", inst.opcode)); + throw InvalidInstruction(pc, inst.opcode); if (!isImm && inst.imm != 0) throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode)); @@ -273,7 +285,7 @@ struct Unmarshaller { (inst.opcode & INST_SIZE_MASK) != INST_SIZE_DW)) throw InvalidInstruction(pc, inst.opcode); if (inst.imm != 0) - throw InvalidInstruction(pc, "Unsupported atomic instruction"); + throw InvalidInstruction(pc, "unsupported immediate"); return LockAdd{ .access = Deref{ @@ -292,7 +304,7 @@ struct Unmarshaller { if (inst.dst == R10_STACK_POINTER) throw InvalidInstruction(pc, "Invalid target r10"); if (inst.dst > R10_STACK_POINTER || inst.src > R10_STACK_POINTER) - throw InvalidInstruction(pc, "Bad register"); + throw InvalidInstruction(pc, "bad register"); bool is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64; return std::visit(overloaded{[&](Un::Op op) -> Instruction { return Un{.op = op, .dst = Reg{inst.dst}, .is64 = is64}; }, [&](Bin::Op op) -> Instruction { @@ -312,18 +324,22 @@ struct Unmarshaller { auto makeLddw(ebpf_inst inst, int32_t next_imm, const vector& insts, pc_t pc) -> Instruction { if (pc >= insts.size() - 1) - throw InvalidInstruction(pc, "incomplete LDDW"); + throw InvalidInstruction(pc, "incomplete lddw"); ebpf_inst next = insts[pc + 1]; if (next.opcode != 0 || next.dst != 0 || next.src != 0 || next.offset != 0) - throw InvalidInstruction(pc, "invalid LDDW"); - if (inst.src > 1 || inst.dst > R10_STACK_POINTER || inst.offset != 0) - throw InvalidInstruction(pc, "LDDW uses reserved fields"); + throw InvalidInstruction(pc, "invalid lddw"); + if (inst.src > 1) + throw InvalidInstruction(pc, make_opcode_message("bad instruction", inst.opcode)); + if (inst.offset != 0) + throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode)); + if (inst.dst > R10_STACK_POINTER) + throw InvalidInstruction(pc, "bad register"); if (inst.src == 1) { // magic number, meaning we're a per-process file descriptor defining the map. // (for details, look for BPF_PSEUDO_MAP_FD in the kernel) if (next.imm != 0) { - throw InvalidInstruction(pc, "LDDW uses reserved fields"); + throw InvalidInstruction(pc, "lddw uses reserved fields"); } return LoadMapFd{.dst = Reg{inst.dst}, .mapfd = inst.imm}; } @@ -413,8 +429,12 @@ struct Unmarshaller { throw InvalidInstruction(pc, inst.opcode); if (inst.opcode & INST_SRC_REG) throw InvalidInstruction(pc, inst.opcode); + if (inst.src > 0) + throw InvalidInstruction(pc, inst.opcode); if (inst.offset != 0) throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode)); + if (inst.dst != 0) + throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode)); if (!info.platform->is_helper_usable(inst.imm)) throw InvalidInstruction(pc, "invalid helper function id"); return makeCall(inst.imm); @@ -422,7 +442,9 @@ struct Unmarshaller { if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP || (inst.opcode & INST_SRC_REG)) throw InvalidInstruction(pc, inst.opcode); if (inst.src != 0) - throw InvalidInstruction(pc, make_opcode_message("nonzero src for register", inst.opcode)); + throw InvalidInstruction(pc, inst.opcode); + if (inst.dst != 0) + throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode)); if (inst.imm != 0) throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode)); if (inst.offset != 0) @@ -438,11 +460,13 @@ struct Unmarshaller { throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode)); if ((inst.opcode & INST_CLS_MASK) == INST_CLS_JMP32 && (inst.offset != 0)) throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode)); + if (inst.dst != 0) + throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode)); default: { // First validate the opcode, src, and imm. auto op = getJmpOp(pc, inst.opcode); if (!(inst.opcode & INST_SRC_REG) && (inst.src != 0)) - throw InvalidInstruction(pc, make_opcode_message("nonzero src for register", inst.opcode)); + throw InvalidInstruction(pc, inst.opcode); if ((inst.opcode & INST_SRC_REG) && (inst.imm != 0)) throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode)); @@ -452,6 +476,12 @@ struct Unmarshaller { throw InvalidInstruction(pc, "jump out of bounds"); else if (insts[new_pc].opcode == 0) throw InvalidInstruction(pc, "jump to middle of lddw"); + if (inst.opcode != INST_OP_JA16 && inst.opcode != INST_OP_JA32) { + if (inst.dst > R10_STACK_POINTER) + throw InvalidInstruction(pc, "bad register"); + if ((inst.opcode & INST_SRC_REG) && (inst.src > R10_STACK_POINTER)) + throw InvalidInstruction(pc, "bad register"); + } auto cond = (inst.opcode == INST_OP_JA16 || inst.opcode == INST_OP_JA32) ? std::optional{} diff --git a/src/ebpf_vm_isa.hpp b/src/ebpf_vm_isa.hpp index d734352ff..9941bfeeb 100644 --- a/src/ebpf_vm_isa.hpp +++ b/src/ebpf_vm_isa.hpp @@ -15,6 +15,7 @@ struct ebpf_inst { std::uint8_t src : 4; //< Source register std::int16_t offset; std::int32_t imm; //< Immediate constant + constexpr bool operator==(const ebpf_inst&) const = default; }; enum { diff --git a/src/test/test_conformance.cpp b/src/test/test_conformance.cpp index ca04dd11b..abb00e63f 100644 --- a/src/test/test_conformance.cpp +++ b/src/test/test_conformance.cpp @@ -14,7 +14,7 @@ void test_conformance(std::string filename, bpf_conformance_test_result_t expect test_path.remove_filename().append("conformance_check" + extension.string()).string(); std::map> result = bpf_conformance( test_files, plugin_path, {}, {}, {}, bpf_conformance_test_CPU_version_t::v4, - _bpf_conformance_groups::default_groups, bpf_conformance_list_instructions_t::LIST_INSTRUCTIONS_NONE, true); + bpf_conformance_groups_t::default_groups, bpf_conformance_list_instructions_t::LIST_INSTRUCTIONS_NONE, true); for (auto file : test_files) { auto& [file_result, reason] = result[file]; REQUIRE(file_result == expected_result); diff --git a/src/test/test_marshal.cpp b/src/test/test_marshal.cpp index a92161bbe..55d02fa98 100644 --- a/src/test/test_marshal.cpp +++ b/src/test/test_marshal.cpp @@ -6,6 +6,196 @@ #include "asm_marshal.hpp" #include "asm_unmarshal.hpp" +// Below we define a tample of instruction templates that specify +// what values each field are allowed to contain. We first define +// a set of sentinel values that mean certain types of wildcards. +// For example, MEM_OFFSET and JMP_OFFSET are different wildcards +// for the 'offset' field of an instruction. Any non-sentinel values +// in an instruction template are treated as literals. + +constexpr int MEM_OFFSET = 3; // Any valid memory offset value. +constexpr int JMP_OFFSET = 5; // Any valid jump offset value. +constexpr int DST = 7; // Any destination register number. +constexpr int SRC = 9; // Any source register number. +constexpr int IMM = -1; // Any imm value. +constexpr int INVALID_REGISTER = R10_STACK_POINTER + 1; // Not a valid register. + +// The following table is derived from the table in the Appendix of the +// BPF ISA specification (https://datatracker.ietf.org/doc/draft-ietf-bpf-isa/). +static const ebpf_inst instruction_template[] = { + // opcode, dst, src, offset, imm. + {0x04, DST, 0, 0, IMM}, + {0x05, 0, 0, JMP_OFFSET, 0}, + {0x06, 0, 0, 0, JMP_OFFSET}, + {0x07, DST, 0, 0, IMM}, + {0x0c, DST, SRC, 0, 0}, + {0x0f, DST, SRC, 0, 0}, + {0x14, DST, 0, 0, IMM}, + {0x15, DST, 0, JMP_OFFSET, IMM}, + {0x16, DST, 0, JMP_OFFSET, IMM}, + {0x17, DST, 0, 0, IMM}, + {0x18, DST, 0, 0, IMM}, + {0x18, DST, 1, 0, IMM}, + // TODO(issue #533): add support for LDDW with src_reg > 1. + // {0x18, DST, 2, 0, IMM}, + // {0x18, DST, 3, 0, IMM}, + // {0x18, DST, 4, 0, IMM}, + // {0x18, DST, 5, 0, IMM}, + // {0x18, DST, 6, 0, IMM}, + {0x1c, DST, SRC, 0, 0}, + {0x1d, DST, SRC, JMP_OFFSET, 0}, + {0x1e, DST, SRC, JMP_OFFSET, 0}, + {0x1f, DST, SRC, 0, 0}, + {0x20, 0, 0, 0, IMM}, + {0x24, DST, 0, 0, IMM}, + {0x25, DST, 0, JMP_OFFSET, IMM}, + {0x26, DST, 0, JMP_OFFSET, IMM}, + {0x27, DST, 0, 0, IMM}, + {0x28, 0, 0, 0, IMM}, + {0x2c, DST, SRC, 0, 0}, + {0x2d, DST, SRC, JMP_OFFSET, 0}, + {0x2e, DST, SRC, JMP_OFFSET, 0}, + {0x2f, DST, SRC, 0, 0}, + {0x30, 0, 0, 0, IMM}, + {0x34, DST, 0, 0, IMM}, + {0x34, DST, 0, 1, IMM}, + {0x35, DST, 0, JMP_OFFSET, IMM}, + {0x36, DST, 0, JMP_OFFSET, IMM}, + {0x37, DST, 0, 0, IMM}, + {0x37, DST, 0, 1, IMM}, + {0x3c, DST, SRC, 0, 0}, + {0x3c, DST, SRC, 1, 0}, + {0x3d, DST, SRC, JMP_OFFSET, 0}, + {0x3e, DST, SRC, JMP_OFFSET, 0}, + {0x3f, DST, SRC, 0, 0}, + {0x3f, DST, SRC, 1, 0}, + {0x40, 0, SRC, 0, IMM}, + {0x44, DST, 0, 0, IMM}, + {0x45, DST, 0, JMP_OFFSET, IMM}, + {0x46, DST, 0, JMP_OFFSET, IMM}, + {0x47, DST, 0, 0, IMM}, + {0x48, 0, SRC, 0, IMM}, + {0x4c, DST, SRC, 0, 0}, + {0x4d, DST, SRC, JMP_OFFSET, 0}, + {0x4e, DST, SRC, JMP_OFFSET, 0}, + {0x4f, DST, SRC, 0, 0}, + {0x50, 0, SRC, 0, IMM}, + {0x54, DST, 0, 0, IMM}, + {0x55, DST, 0, JMP_OFFSET, IMM}, + {0x56, DST, 0, JMP_OFFSET, IMM}, + {0x57, DST, 0, 0, IMM}, + {0x5c, DST, SRC, 0, 0}, + {0x5d, DST, SRC, JMP_OFFSET, 0}, + {0x5e, DST, SRC, JMP_OFFSET, 0}, + {0x5f, DST, SRC, 0, 0}, + {0x61, DST, SRC, MEM_OFFSET, 0}, + {0x62, DST, 0, MEM_OFFSET, IMM}, + {0x63, DST, SRC, MEM_OFFSET, 0}, + {0x64, DST, 0, 0, IMM}, + {0x65, DST, 0, JMP_OFFSET, IMM}, + {0x66, DST, 0, JMP_OFFSET, IMM}, + {0x67, DST, 0, 0, IMM}, + {0x69, DST, SRC, MEM_OFFSET, 0}, + {0x6a, DST, 0, MEM_OFFSET, IMM}, + {0x6b, DST, SRC, MEM_OFFSET, 0}, + {0x6c, DST, SRC, 0, 0}, + {0x6d, DST, SRC, JMP_OFFSET, 0}, + {0x6e, DST, SRC, JMP_OFFSET, 0}, + {0x6f, DST, SRC, 0, 0}, + {0x71, DST, SRC, MEM_OFFSET, 0}, + {0x72, DST, 0, MEM_OFFSET, IMM}, + {0x73, DST, SRC, MEM_OFFSET, 0}, + {0x74, DST, 0, 0, IMM}, + {0x75, DST, 0, JMP_OFFSET, IMM}, + {0x76, DST, 0, JMP_OFFSET, IMM}, + {0x77, DST, 0, 0, IMM}, + {0x79, DST, SRC, MEM_OFFSET, 0}, + {0x7a, DST, 0, MEM_OFFSET, IMM}, + {0x7b, DST, SRC, MEM_OFFSET, 0}, + {0x7c, DST, SRC, 0, 0}, + {0x7d, DST, SRC, JMP_OFFSET, 0}, + {0x7e, DST, SRC, JMP_OFFSET, 0}, + {0x7f, DST, SRC, 0, 0}, + {0x84, DST, 0, 0, 0}, + {0x85, 0, 0, 0, IMM}, + // TODO(issue #582): Add support for subprograms (call_local). + // {0x85, 0, 1, 0, IMM}, + // TODO(issue #590): Add support for calling a helper function by BTF ID. + // {0x85, 0, 2, 0, IMM}, + {0x87, DST, 0, 0, 0}, + {0x94, DST, 0, 0, IMM}, + {0x94, DST, 0, 1, IMM}, + {0x95, 0, 0, 0, 0}, + {0x97, DST, 0, 0, IMM}, + {0x97, DST, 0, 1, IMM}, + {0x9c, DST, SRC, 0, 0}, + {0x9c, DST, SRC, 1, 0}, + {0x9f, DST, SRC, 0, 0}, + {0x9f, DST, SRC, 1, 0}, + {0xa4, DST, 0, 0, IMM}, + {0xa5, DST, 0, JMP_OFFSET, IMM}, + {0xa6, DST, 0, JMP_OFFSET, IMM}, + {0xa7, DST, 0, 0, IMM}, + {0xac, DST, SRC, 0, 0}, + {0xad, DST, SRC, JMP_OFFSET, 0}, + {0xae, DST, SRC, JMP_OFFSET, 0}, + {0xaf, DST, SRC, 0, 0}, + {0xb4, DST, 0, 0, IMM}, + {0xb5, DST, 0, JMP_OFFSET, IMM}, + {0xb6, DST, 0, JMP_OFFSET, IMM}, + {0xb7, DST, 0, 0, IMM}, + {0xbc, DST, SRC, 0, 0}, + {0xbc, DST, SRC, 8, 0}, + {0xbc, DST, SRC, 16, 0}, + {0xbd, DST, SRC, JMP_OFFSET, 0}, + {0xbe, DST, SRC, JMP_OFFSET, 0}, + {0xbf, DST, SRC, 0, 0}, + {0xbf, DST, SRC, 8, 0}, + {0xbf, DST, SRC, 16, 0}, + {0xbf, DST, SRC, 32, 0}, + {0xc3, DST, SRC, MEM_OFFSET, 0x00}, + {0xc3, DST, SRC, MEM_OFFSET, 0x01}, + {0xc3, DST, SRC, MEM_OFFSET, 0x40}, + {0xc3, DST, SRC, MEM_OFFSET, 0x41}, + {0xc3, DST, SRC, MEM_OFFSET, 0x50}, + {0xc3, DST, SRC, MEM_OFFSET, 0x51}, + {0xc3, DST, SRC, MEM_OFFSET, 0xa0}, + {0xc3, DST, SRC, MEM_OFFSET, 0xa1}, + {0xc3, DST, SRC, MEM_OFFSET, 0xe1}, + {0xc3, DST, SRC, MEM_OFFSET, 0xf1}, + {0xc4, DST, 0, 0, IMM}, + {0xc5, DST, 0, JMP_OFFSET, IMM}, + {0xc6, DST, 0, JMP_OFFSET, IMM}, + {0xc7, DST, 0, 0, IMM}, + {0xcc, DST, SRC, 0, 0}, + {0xcd, DST, SRC, JMP_OFFSET, 0}, + {0xce, DST, SRC, JMP_OFFSET, 0}, + {0xcf, DST, SRC, 0, 0}, + {0xd4, DST, 0, 0, 0x10}, + {0xd4, DST, 0, 0, 0x20}, + {0xd4, DST, 0, 0, 0x40}, + {0xd5, DST, 0, JMP_OFFSET, IMM}, + {0xd6, DST, 0, JMP_OFFSET, IMM}, + {0xd7, DST, 0, 0, 0x10}, + {0xd7, DST, 0, 0, 0x20}, + {0xd7, DST, 0, 0, 0x40}, + {0xdb, DST, SRC, MEM_OFFSET, 0x00}, + {0xdb, DST, SRC, MEM_OFFSET, 0x01}, + {0xdb, DST, SRC, MEM_OFFSET, 0x40}, + {0xdb, DST, SRC, MEM_OFFSET, 0x41}, + {0xdb, DST, SRC, MEM_OFFSET, 0x50}, + {0xdb, DST, SRC, MEM_OFFSET, 0x51}, + {0xdb, DST, SRC, MEM_OFFSET, 0xa0}, + {0xdb, DST, SRC, MEM_OFFSET, 0xa1}, + {0xdb, DST, SRC, MEM_OFFSET, 0xe1}, + {0xdb, DST, SRC, MEM_OFFSET, 0xf1}, + {0xdc, DST, 0, 0, 0x10}, + {0xdc, DST, 0, 0, 0x20}, + {0xdc, DST, 0, 0, 0x40}, + {0xdd, DST, SRC, JMP_OFFSET, 0}, + {0xde, DST, SRC, JMP_OFFSET, 0}, +}; + // Verify that if we unmarshal an instruction and then re-marshal it, // we get what we expect. static void compare_unmarshal_marshal(const ebpf_inst& ins, const ebpf_inst& expected_result, const ebpf_platform_t* platform = &g_ebpf_platform_linux) { @@ -62,9 +252,9 @@ static void compare_unmarshal_marshal(const ebpf_inst& ins1, const ebpf_inst& in // Verify that if we marshal an instruction and then unmarshal it, // we get the original. -static void compare_marshal_unmarshal(const Instruction& ins, bool double_cmd = false, const ebpf_platform_t* platform = &g_ebpf_platform_linux) { - program_info info{.platform = platform, - .type = platform->get_program_type("unspec", "unspec")}; +static void compare_marshal_unmarshal(const Instruction& ins, bool double_cmd = false, const ebpf_platform_t& platform = g_ebpf_platform_linux) { + program_info info{.platform = &platform, + .type = platform.get_program_type("unspec", "unspec")}; InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", marshal(ins, 0), info})); REQUIRE(parsed.size() == 1); auto [_, single, _2] = parsed.back(); @@ -73,16 +263,16 @@ static void compare_marshal_unmarshal(const Instruction& ins, bool double_cmd = REQUIRE(single == ins); } -static void check_marshal_unmarshal_fail(const Instruction& ins, std::string expected_error_message, const ebpf_platform_t* platform = &g_ebpf_platform_linux) { - program_info info{.platform = platform, - .type = platform->get_program_type("unspec", "unspec")}; +static void check_marshal_unmarshal_fail(const Instruction& ins, std::string expected_error_message, const ebpf_platform_t& platform = g_ebpf_platform_linux) { + program_info info{.platform = &platform, + .type = platform.get_program_type("unspec", "unspec")}; std::string error_message = std::get(unmarshal(raw_program{"", "", marshal(ins, 0), info})); REQUIRE(error_message == expected_error_message); } -static void check_unmarshal_fail(ebpf_inst inst, std::string expected_error_message, const ebpf_platform_t* platform = &g_ebpf_platform_linux) { - program_info info{.platform = platform, - .type = platform->get_program_type("unspec", "unspec")}; +static void check_unmarshal_fail(ebpf_inst inst, std::string expected_error_message, const ebpf_platform_t& platform = g_ebpf_platform_linux) { + program_info info{.platform = &platform, + .type = platform.get_program_type("unspec", "unspec")}; std::vector insns = {inst}; auto result = unmarshal(raw_program{"", "", insns, info}); REQUIRE(std::holds_alternative(result)); @@ -90,6 +280,17 @@ static void check_unmarshal_fail(ebpf_inst inst, std::string expected_error_mess REQUIRE(error_message == expected_error_message); } +static void check_unmarshal_fail_goto(ebpf_inst inst, const std::string& expected_error_message) { + program_info info{.platform = &g_ebpf_platform_linux, + .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")}; + const ebpf_inst exit{.opcode = INST_OP_EXIT}; + std::vector insns{inst, exit, exit}; + auto result = unmarshal(raw_program{"", "", insns, info}); + REQUIRE(std::holds_alternative(result)); + std::string error_message = std::get(result); + REQUIRE(error_message == expected_error_message); +} + // Check that unmarshaling a 64-bit immediate instruction fails. static void check_unmarshal_fail(ebpf_inst inst1, ebpf_inst inst2, std::string expected_error_message) { program_info info{.platform = &g_ebpf_platform_linux, @@ -316,83 +517,143 @@ TEST_CASE("unmarshal extension opcodes", "[disasm][marshal]") { ebpf_inst{.opcode = INST_ALU_OP_MOV | INST_SRC_REG | INST_CLS_ALU64, .dst = 1, .src = 1, .offset = 32}); } -TEST_CASE("fail unmarshal invalid opcodes", "[disasm][marshal]") { - // The following opcodes are undefined and should generate bad instruction errors. - uint8_t bad_opcodes[] = { - 0x00, 0x01, 0x02, 0x03, 0x08, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x10, 0x11, 0x12, 0x13, 0x19, 0x1a, 0x1b, 0x60, - 0x68, 0x70, 0x78, 0x80, 0x81, 0x82, 0x83, 0x86, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, - 0x92, 0x93, 0x96, 0x98, 0x99, 0x9a, 0x9b, 0x9d, 0x9e, 0xa0, 0xa1, 0xa2, 0xa3, 0xa8, 0xa9, 0xaa, 0xab, 0xb0, - 0xb1, 0xb2, 0xb3, 0xb8, 0xb9, 0xba, 0xbb, 0xc0, 0xc1, 0xc2, 0xc8, 0xc9, 0xca, 0xcb, 0xd0, 0xd1, 0xd2, 0xd3, - 0xd8, 0xd9, 0xda, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, - 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff}; - for (int i = 0; i < sizeof(bad_opcodes); i++) { - std::ostringstream oss; - oss << "0: Bad instruction op 0x" << std::hex << (int)bad_opcodes[i] << std::endl; - check_unmarshal_fail(ebpf_inst{.opcode = bad_opcodes[i]}, oss.str().c_str()); +// Check that unmarshaling an invalid instruction fails with a given message. +static void check_unmarshal_instruction_fail(ebpf_inst& inst, const std::string& message) { + if (inst.offset == JMP_OFFSET) { + inst.offset = 1; + check_unmarshal_fail_goto(inst, message); + } else if (inst.opcode == INST_OP_LDDW_IMM) + check_unmarshal_fail(inst, ebpf_inst{}, message); + else + check_unmarshal_fail(inst, message); +} + +// Check that various 'dst' variations between two valid instruction templates fail. +static void check_instruction_dst_variations(const ebpf_inst& previous_template, std::optional next_template) { + ebpf_inst inst = previous_template; + if (inst.dst == DST) { + inst.dst = INVALID_REGISTER; + check_unmarshal_instruction_fail(inst, "0: bad register\n"); + } else { + // This instruction doesn't put a register number in the 'dst' field. + // Just try the next value unless that's what the next template has. + inst.dst++; + if (inst != next_template) { + std::ostringstream oss; + if (inst.dst == 1) + oss << "0: nonzero dst for register op 0x" << std::hex << (int)inst.opcode << std::endl; + else + oss << "0: bad instruction op 0x" << std::hex << (int)inst.opcode << std::endl; + check_unmarshal_instruction_fail(inst, oss.str()); + } } } -TEST_CASE("fail unmarshal src0 opcodes", "[disasm][marshal]") { - // The following opcodes are only defined for src = 0. - uint8_t src0_opcodes[] = {0x04, 0x05, 0x06, 0x07, 0x14, 0x15, 0x16, 0x17, 0x24, 0x25, 0x26, 0x27, 0x34, 0x35, 0x36, - 0x37, 0x44, 0x45, 0x46, 0x47, 0x54, 0x55, 0x56, 0x57, 0x62, 0x64, 0x65, 0x66, 0x67, 0x6a, - 0x72, 0x74, 0x75, 0x76, 0x77, 0x7a, 0x84, 0x87, 0x94, 0x95, 0x97, 0xa4, 0xa5, 0xa6, 0xa7, - 0xb4, 0xb5, 0xb6, 0xb7, 0xc4, 0xc5, 0xc6, 0xc7, 0xd4, 0xd5, 0xd6, 0xd7, 0xdc}; - for (int i = 0; i < sizeof(src0_opcodes); i++) { - std::ostringstream oss; - oss << "0: nonzero src for register op 0x" << std::hex << (int)src0_opcodes[i] << std::endl; - check_unmarshal_fail(ebpf_inst{.opcode = src0_opcodes[i], .src = 1}, oss.str().c_str()); +// Check that various 'src' variations between two valid instruction templates fail. +static void check_instruction_src_variations(const ebpf_inst& previous_template, std::optional next_template) { + ebpf_inst inst = previous_template; + if (inst.src == SRC) { + inst.src = INVALID_REGISTER; + check_unmarshal_instruction_fail(inst, "0: bad register\n"); + } else { + // This instruction doesn't put a register number in the 'src' field. + // Just try the next value unless that's what the next template has. + inst.src++; + if (inst != next_template) { + std::ostringstream oss; + oss << "0: bad instruction op 0x" << std::hex << (int)inst.opcode << std::endl; + check_unmarshal_instruction_fail(inst, oss.str()); + } } } -TEST_CASE("fail unmarshal imm0 opcodes", "[disasm][marshal]") { - // The following opcodes are only defined for imm = 0. - uint8_t imm0_opcodes[] = {0x05, 0x0c, 0x0f, 0x1c, 0x1d, 0x1e, 0x1f, 0x2c, 0x2d, 0x2e, 0x2f, 0x3d, 0x3e, 0x3f, 0x4c, - 0x4d, 0x4e, 0x4f, 0x5c, 0x5d, 0x5e, 0x5f, 0x61, 0x63, 0x69, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, - 0x71, 0x73, 0x79, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x84, 0x87, 0x95, 0x9c, 0x9f, 0xac, 0xad, - 0xae, 0xaf, 0xbc, 0xbd, 0xbe, 0xbf, 0xcc, 0xcd, 0xce, 0xcf, 0xdd, 0xde}; - for (int i = 0; i < sizeof(imm0_opcodes); i++) { - std::ostringstream oss; - oss << "0: nonzero imm for op 0x" << std::hex << (int)imm0_opcodes[i] << std::endl; - check_unmarshal_fail(ebpf_inst{.opcode = imm0_opcodes[i], .imm = 1}, oss.str().c_str()); +// Check that various 'offset' variations between two valid instruction templates fail. +static void check_instruction_offset_variations(const ebpf_inst& previous_template, std::optional next_template) { + ebpf_inst inst = previous_template; + if (inst.offset == JMP_OFFSET) { + inst.offset = 0; // Not a valid jump offset. + check_unmarshal_instruction_fail(inst, "0: jump out of bounds\n"); + } else if (inst.offset != MEM_OFFSET) { + // This instruction limits what can appear in the 'offset' field. + // Just try the next value unless that's what the next template has. + inst.offset++; + if (inst != next_template) { + std::ostringstream oss; + if (inst.offset == 1 && + (!next_template || next_template->opcode != inst.opcode || next_template->offset == 0)) + oss << "0: nonzero offset for op 0x" << std::hex << (int)inst.opcode << std::endl; + else + oss << "0: invalid offset for op 0x" << std::hex << (int)inst.opcode << std::endl; + check_unmarshal_instruction_fail(inst, oss.str()); + } } } -TEST_CASE("fail unmarshal off0 opcodes", "[disasm][marshal]") { - // The following opcodes are only defined for offset = 0. - uint8_t off0_opcodes[] = {0x04, 0x06, 0x07, 0x0c, 0x0f, 0x14, 0x17, 0x1c, 0x1f, 0x24, 0x27, 0x2c, 0x2f, 0x44, 0x47, - 0x4c, 0x4f, 0x54, 0x57, 0x5c, 0x5f, 0x64, 0x67, 0x6c, 0x6f, 0x74, 0x77, 0x7c, 0x7f, 0x84, - 0x85, 0x87, 0x95, 0xa4, 0xa7, 0xac, 0xaf, 0xc4, 0xc7, 0xcc, 0xcf, 0xd4, 0xd7, 0xdc}; - for (int i = 0; i < sizeof(off0_opcodes); i++) { - std::ostringstream oss; - oss << "0: nonzero offset for op 0x" << std::hex << (int)off0_opcodes[i] << std::endl; - check_unmarshal_fail(ebpf_inst{.opcode = off0_opcodes[i], .offset = 1}, oss.str().c_str()); +// Check that various 'imm' variations between two valid instruction templates fail. +static void check_instruction_imm_variations(const ebpf_inst& previous_template, std::optional next_template) { + ebpf_inst inst = previous_template; + if (inst.imm == JMP_OFFSET) { + inst.imm = 0; // Not a valid jump offset. + check_unmarshal_instruction_fail(inst, "0: jump out of bounds\n"); + } else if (inst.imm != IMM) { + // This instruction limits what can appear in the 'imm' field. + // Just try the next value unless that's what the next template has. + inst.imm++; + if (inst != next_template) { + std::ostringstream oss; + if (inst.imm == 1) + oss << "0: nonzero imm for op 0x" << std::hex << (int)inst.opcode << std::endl; + else + oss << "0: unsupported immediate" << std::endl; + check_unmarshal_instruction_fail(inst, oss.str()); + } + } + + // Some instructions only permit non-zero imm values. + // If the next template is for one of those, check the zero value now. + if (next_template && (previous_template.opcode != next_template->opcode) && (next_template->imm > 0) && (next_template->imm != JMP_OFFSET)) { + inst = *next_template; + inst.imm = 0; + check_unmarshal_instruction_fail(inst, "0: unsupported immediate\n"); } } -TEST_CASE("fail unmarshal offset opcodes", "[disasm][marshal]") { - // The following opcodes are defined for multiple other offset values, but not offset = 2 for example. - uint8_t off2_opcodes[] = {0x34, 0x37, 0x3c, 0x3f, 0x94, 0x97, 0x9c, 0x9f, 0xb4, 0xb7, 0xbc, 0xbf}; - for (int i = 0; i < sizeof(off2_opcodes); i++) { +// Check that various variations between two valid instruction templates fail. +static void check_instruction_variations(std::optional previous_template, std::optional next_template) { + if (previous_template) { + check_instruction_dst_variations(*previous_template, next_template); + check_instruction_src_variations(*previous_template, next_template); + check_instruction_offset_variations(*previous_template, next_template); + check_instruction_imm_variations(*previous_template, next_template); + } + + // Check any invalid opcodes in between the previous and next templates. + int previous_opcode = previous_template ? previous_template->opcode : -1; + int next_opcode = next_template ? next_template->opcode : 0x100; + for (int opcode = previous_opcode + 1; opcode < next_opcode; opcode++) { + ebpf_inst inst{.opcode = (uint8_t)opcode}; std::ostringstream oss; - oss << "0: invalid offset for op 0x" << std::hex << (int)off2_opcodes[i] << std::endl; - check_unmarshal_fail(ebpf_inst{.opcode = off2_opcodes[i], .offset = 2}, oss.str().c_str()); + oss << "0: bad instruction op 0x" << std::hex << opcode << std::endl; + check_unmarshal_fail(inst, oss.str()); } } +TEST_CASE("fail unmarshal bad instructions", "[disasm][marshal]") { + size_t template_count = std::size(instruction_template); + + // Check any variations before the first template. + check_instruction_variations({}, instruction_template[0]); + + for (int index = 1; index < template_count; index++) + check_instruction_variations(instruction_template[index - 1], instruction_template[index]); + + // Check any remaining variations after the last template. + check_instruction_variations(instruction_template[template_count - 1], {}); +} + TEST_CASE("check unmarshal legacy opcodes", "[disasm][marshal]") { // The following opcodes are deprecated and should no longer be used. static uint8_t supported_legacy_opcodes[] = {0x20, 0x28, 0x30, 0x40, 0x48, 0x50}; - static uint8_t unsupported_legacy_opcodes[] = {0x21, 0x22, 0x23, 0x29, 0x2a, 0x2b, 0x31, 0x32, 0x33, - 0x38, 0x39, 0x3a, 0x3b, 0x41, 0x42, 0x43, 0x49, 0x4a, - 0x4b, 0x51, 0x52, 0x53, 0x58, 0x59, 0x5a, 0x5b}; - - for (uint8_t opcode : unsupported_legacy_opcodes) { - std::ostringstream oss; - oss << "0: Bad instruction op 0x" << std::hex << (int)opcode << std::endl; - check_unmarshal_fail(ebpf_inst{.opcode = opcode}, oss.str().c_str()); - } - for (uint8_t opcode : supported_legacy_opcodes) { compare_unmarshal_marshal(ebpf_inst{.opcode = opcode}, ebpf_inst{.opcode = opcode}); } @@ -400,16 +661,10 @@ TEST_CASE("check unmarshal legacy opcodes", "[disasm][marshal]") { // Disable legacy support. ebpf_platform_t platform = g_ebpf_platform_linux; platform.legacy = false; - - for (uint8_t opcode : unsupported_legacy_opcodes) { - std::ostringstream oss; - oss << "0: Bad instruction op 0x" << std::hex << (int)opcode << std::endl; - check_unmarshal_fail(ebpf_inst{.opcode = opcode}, oss.str().c_str(), &platform); - } for (uint8_t opcode : supported_legacy_opcodes) { std::ostringstream oss; - oss << "0: Bad instruction op 0x" << std::hex << (int)opcode << std::endl; - check_unmarshal_fail(ebpf_inst{.opcode = opcode}, oss.str().c_str(), &platform); + oss << "0: bad instruction op 0x" << std::hex << (int)opcode << std::endl; + check_unmarshal_fail(ebpf_inst{.opcode = opcode}, oss.str(), platform); } } @@ -420,51 +675,13 @@ TEST_CASE("unmarshal 64bit immediate", "[disasm][marshal]") { ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 0, .imm = 1}, ebpf_inst{}); for (uint8_t src = 0; src <= 7; src++) { - check_unmarshal_fail(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = src}, "0: incomplete LDDW\n"); + check_unmarshal_fail(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = src}, "0: incomplete lddw\n"); check_unmarshal_fail(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = src}, - ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM}, "0: invalid LDDW\n"); - } - - // No supported src values use the offset field. - for (uint8_t src = 0; src <= 1; src++) { - check_unmarshal_fail(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = src, .offset = 1}, ebpf_inst{}, - "0: LDDW uses reserved fields\n"); - } - - // Verify that unsupported src values fail. - // TODO: support src = 2 through 6. - for (uint8_t src = 2; src <= 7; src++) { - check_unmarshal_fail(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = src}, ebpf_inst{}, - "0: LDDW uses reserved fields\n"); + ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM}, "0: invalid lddw\n"); } // When src = {1, 3, 4, 5}, next_imm must be 0. - for (uint8_t src : {1, 3, 4, 5}) { - check_unmarshal_fail(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = src}, ebpf_inst{.imm = 1}, - "0: LDDW uses reserved fields\n"); - } -} - - -TEST_CASE("fail unmarshal misc", "[disasm][marshal]") { - check_unmarshal_fail(ebpf_inst{.opcode = /* 0x06 */ INST_CLS_JMP32}, "0: jump out of bounds\n"); - check_unmarshal_fail(ebpf_inst{.opcode = /* 0x16 */ 0x10 | INST_CLS_JMP32}, "0: jump out of bounds\n"); - check_unmarshal_fail(ebpf_inst{.opcode = /* 0x71 */ ((INST_MEM << 5) | INST_SIZE_B | INST_CLS_LDX), .dst = 11, .imm = 8}, - "0: Bad register\n"); - check_unmarshal_fail(ebpf_inst{.opcode = /* 0x71 */ ((INST_MEM << 5) | INST_SIZE_B | INST_CLS_LDX), .dst = 1, .src = 11}, - "0: Bad register\n"); - check_unmarshal_fail(ebpf_inst{.opcode = /* 0xb4 */ (INST_ALU_OP_MOV | INST_SRC_IMM | INST_CLS_ALU), .dst = 11, .imm = 8}, - "0: Bad register\n"); - check_unmarshal_fail(ebpf_inst{.opcode = /* 0xb4 */ INST_ALU_OP_MOV | INST_SRC_IMM | INST_CLS_ALU, .offset = 8}, - "0: invalid offset for op 0xb4\n"); - check_unmarshal_fail(ebpf_inst{.opcode = /* 0xbc */ (INST_ALU_OP_MOV | INST_SRC_REG | INST_CLS_ALU), .dst = 1, .src = 11}, - "0: Bad register\n"); - check_unmarshal_fail(ebpf_inst{.opcode = /* 0xd4 */ INST_ALU_OP_END | INST_END_LE | INST_CLS_ALU, .dst = 1, .imm = 8}, - "0: invalid endian immediate\n"); - check_unmarshal_fail(ebpf_inst{.opcode = /* 0xd4 */ INST_ALU_OP_END | INST_END_LE | INST_CLS_ALU, .imm = 0}, - "0: invalid endian immediate\n"); - check_unmarshal_fail(ebpf_inst{.opcode = /* 0xd7 */ INST_ALU_OP_END | INST_END_LE | INST_CLS_ALU64, .imm = 0}, - "0: invalid endian immediate\n"); - check_unmarshal_fail(ebpf_inst{.opcode = /* 0xdc */ INST_ALU_OP_END | INST_END_BE | INST_CLS_ALU, .dst = 1, .imm = 8}, - "0: invalid endian immediate\n"); + // TODO(issue #533): add support for LDDW with src_reg > 1. + check_unmarshal_fail(ebpf_inst{.opcode = /* 0x18 */ INST_OP_LDDW_IMM, .src = 1}, ebpf_inst{.imm = 1}, + "0: lddw uses reserved fields\n"); } From 4203e990be112629ef3319e31ed55316bd3559eb Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Thu, 29 Feb 2024 09:46:09 -0800 Subject: [PATCH 048/373] Implement callx instruction (#584) * Implement callx instruction These instructions are generated by both clang (under -O0 or -O1) and gcc (if the experimental -mxbpf option is passed to the compiler), but are not supported by Linux. Per mailing list discussion at https://mailarchive.ietf.org/arch/msg/bpf/CDQjTO8R8gdPdfeKVnoxWco8_Lw/ the intent is to support them eventually, and with this PR, ebpf-for-windows can support them now. This will also unblock the ability to use versions of clang later than clang-11 by using -O1 instead of -O2 (which generates correlated branches PREVAIL can't deal with). The mailing list discussion at https://mailarchive.ietf.org/arch/msg/bpf/Vx1H3ViPUWoGKNssCO22lOIjyXU/ agreed that inst.dst is where to put the register to use going forward and clang v.19 will do so but earlier versions of clang used inst.imm. --------- Signed-off-by: Dave Thaler --- external/bpf_conformance | 2 +- src/asm_cfg.cpp | 2 + src/asm_marshal.cpp | 8 +- src/asm_ostream.cpp | 6 + src/asm_ostream.hpp | 1 + src/asm_parse.cpp | 3 + src/asm_syntax.hpp | 14 +- src/asm_unmarshal.cpp | 29 +++- src/assertions.cpp | 13 +- src/crab/cfg.hpp | 1 + src/crab/ebpf_domain.cpp | 51 +++++- src/crab/ebpf_domain.hpp | 2 + src/ebpf_vm_isa.hpp | 3 +- src/ebpf_yaml.cpp | 20 ++- src/linux/gpl/spec_prototypes.cpp | 1 + src/linux/linux_platform.cpp | 3 +- src/main/check.cpp | 3 + src/platform.hpp | 3 +- src/test/test_conformance.cpp | 4 +- src/test/test_marshal.cpp | 127 ++++++++------ src/test/test_yaml.cpp | 1 + test-data/call.yaml | 6 +- test-data/callx.yaml | 266 ++++++++++++++++++++++++++++++ test-data/jump.yaml | 2 +- test-data/movsx.yaml | 2 +- 25 files changed, 497 insertions(+), 76 deletions(-) create mode 100644 test-data/callx.yaml diff --git a/external/bpf_conformance b/external/bpf_conformance index 642b1b0f5..cfec46b36 160000 --- a/external/bpf_conformance +++ b/external/bpf_conformance @@ -1 +1 @@ -Subproject commit 642b1b0f585ef394420dcd47fead2b8291ddfd72 +Subproject commit cfec46b36fae802c2534285229927bef351ad7a7 diff --git a/src/asm_cfg.cpp b/src/asm_cfg.cpp index 508f603ba..1b37a4328 100644 --- a/src/asm_cfg.cpp +++ b/src/asm_cfg.cpp @@ -174,6 +174,8 @@ static std::string instype(Instruction ins) { } } return "call_mem"; + } else if (std::holds_alternative(ins)) { + return "callx"; } else if (std::holds_alternative(ins)) { return std::get(ins).is_load ? "load" : "store"; } else if (std::holds_alternative(ins)) { diff --git a/src/asm_marshal.cpp b/src/asm_marshal.cpp index 114baed19..a80f20345 100644 --- a/src/asm_marshal.cpp +++ b/src/asm_marshal.cpp @@ -175,7 +175,13 @@ struct MarshalVisitor { vector operator()(Call const& b) { return { - ebpf_inst{.opcode = static_cast(INST_OP_CALL), .dst = 0, .src = 0, .offset = 0, .imm = b.func}}; + ebpf_inst{.opcode = static_cast(INST_OP_CALL | INST_SRC_IMM), .dst = 0, .src = 0, .offset = 0, .imm = b.func}}; + } + + vector operator()(Callx const& b) { + // callx is defined to have the register in 'dst' not in 'src'. + return { + ebpf_inst{.opcode = static_cast(INST_OP_CALL | INST_SRC_REG), .dst = b.func.v, .src = 0, .offset = 0}}; } vector operator()(Exit const& b) { diff --git a/src/asm_ostream.cpp b/src/asm_ostream.cpp index 0285d6a27..66e8f1991 100644 --- a/src/asm_ostream.cpp +++ b/src/asm_ostream.cpp @@ -189,6 +189,10 @@ std::ostream& operator<<(std::ostream& os, TypeConstraint const& tc) { return os << typereg(tc.reg) << " " << cmp_op << " " << tc.types; } +std::ostream& operator<<(std::ostream& os, FuncConstraint const& fc) { + return os << typereg(fc.reg) << " is helper"; +} + std::ostream& operator<<(std::ostream& os, AssertionConstraint const& a) { return std::visit([&](const auto& a) -> std::ostream& { return os << a; }, a); } @@ -262,6 +266,8 @@ struct InstructionPrinterVisitor { os_ << ")"; } + void operator()(Callx const& callx) { os_ << "callx " << callx.func; } + void operator()(Exit const& b) { os_ << "exit"; } void operator()(Jmp const& b) { diff --git a/src/asm_ostream.hpp b/src/asm_ostream.hpp index e36f25fa6..31af5d63c 100644 --- a/src/asm_ostream.hpp +++ b/src/asm_ostream.hpp @@ -54,6 +54,7 @@ inline std::ostream& operator<<(std::ostream& os, LoadMapFd const& a) { return o inline std::ostream& operator<<(std::ostream& os, Bin const& a) { return os << (Instruction)a; } inline std::ostream& operator<<(std::ostream& os, Un const& a) { return os << (Instruction)a; } inline std::ostream& operator<<(std::ostream& os, Call const& a) { return os << (Instruction)a; } +inline std::ostream& operator<<(std::ostream& os, Callx const& a) { return os << (Instruction)a; } inline std::ostream& operator<<(std::ostream& os, Exit const& a) { return os << (Instruction)a; } inline std::ostream& operator<<(std::ostream& os, Jmp const& a) { return os << (Instruction)a; } inline std::ostream& operator<<(std::ostream& os, Packet const& a) { return os << (Instruction)a; } diff --git a/src/asm_parse.cpp b/src/asm_parse.cpp index 56069ee0c..a6e00d89b 100644 --- a/src/asm_parse.cpp +++ b/src/asm_parse.cpp @@ -148,6 +148,9 @@ Instruction parse_instruction(const std::string& line, const std::map(m[1]); return make_call(func, g_ebpf_platform_linux); } + if (regex_match(text, m, regex("callx " REG))) { + return Callx{reg(m[1])}; + } if (regex_match(text, m, regex(WREG OPASSIGN REG))) { std::string r = m[1]; return Bin{.op = str_to_binop.at(m[2]), .dst = reg(r), .v = reg(m[3]), .is64 = r.at(0) != 'w', .lddw = false}; diff --git a/src/asm_syntax.hpp b/src/asm_syntax.hpp index b05574cb6..f37f141bd 100644 --- a/src/asm_syntax.hpp +++ b/src/asm_syntax.hpp @@ -205,6 +205,11 @@ struct Exit { constexpr bool operator==(const Exit&) const = default; }; +struct Callx { + Reg func; + constexpr bool operator==(const Callx&) const = default; +}; + struct Deref { int32_t width{}; Reg basereg; @@ -335,6 +340,11 @@ struct TypeConstraint { constexpr bool operator==(const TypeConstraint&) const = default; }; +struct FuncConstraint { + Reg reg; + constexpr bool operator==(const FuncConstraint&) const = default; +}; + /// Condition check whether something is a valid size. struct ZeroCtxOffset { Reg reg; @@ -342,7 +352,7 @@ struct ZeroCtxOffset { }; using AssertionConstraint = - std::variant; + std::variant; struct Assert { AssertionConstraint cst; @@ -355,7 +365,7 @@ struct IncrementLoopCounter { constexpr bool operator==(const IncrementLoopCounter&) const = default; }; -using Instruction = std::variant; +using Instruction = std::variant; using LabeledInstruction = std::tuple>; using InstructionSeq = std::vector; diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index 351493355..b6ad9b670 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -252,7 +252,7 @@ struct Unmarshaller { throw InvalidInstruction(pc, inst.opcode); bool isLoad = getMemIsLoad(inst.opcode); if (isLoad && inst.dst == R10_STACK_POINTER) - throw InvalidInstruction(pc, "Cannot modify r10"); + throw InvalidInstruction(pc, "cannot modify r10"); bool isImm = !(inst.opcode & 1); if (isImm && inst.src != 0) throw InvalidInstruction(pc, inst.opcode); @@ -302,7 +302,7 @@ struct Unmarshaller { auto makeAluOp(size_t pc, ebpf_inst inst) -> Instruction { if (inst.dst == R10_STACK_POINTER) - throw InvalidInstruction(pc, "Invalid target r10"); + throw InvalidInstruction(pc, "invalid target r10"); if (inst.dst > R10_STACK_POINTER || inst.src > R10_STACK_POINTER) throw InvalidInstruction(pc, "bad register"); bool is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64; @@ -379,7 +379,7 @@ struct Unmarshaller { auto makeCall(int32_t imm) const { EbpfHelperPrototype proto = info.platform->get_helper_prototype(imm); if (proto.return_type == EBPF_RETURN_TYPE_UNSUPPORTED) { - throw std::runtime_error(std::string("Unsupported function: ") + proto.name); + throw std::runtime_error(std::string("unsupported function: ") + proto.name); } Call res; res.func = imm; @@ -397,7 +397,7 @@ struct Unmarshaller { for (size_t i = 1; i < args.size() - 1; i++) { switch (args[i]) { case EBPF_ARGUMENT_TYPE_UNSUPPORTED: { - throw std::runtime_error(std::string("Unsupported function: ") + proto.name); + throw std::runtime_error(std::string("unsupported function: ") + proto.name); } case EBPF_ARGUMENT_TYPE_DONTCARE: return res; case EBPF_ARGUMENT_TYPE_ANYTHING: @@ -422,21 +422,38 @@ struct Unmarshaller { return res; } + auto makeCallx(ebpf_inst inst, pc_t pc) const { + // callx puts the register number in the 'dst' field rather than the 'src' field. + if (inst.dst > R10_STACK_POINTER) + throw InvalidInstruction(pc, "bad register"); + if (inst.imm != 0) { + // Clang prior to v19 put the register number into the 'imm' field. + if (inst.dst > 0) + throw InvalidInstruction(pc, make_opcode_message("nonzero imm for", inst.opcode)); + if (inst.imm < 0 || inst.imm > R10_STACK_POINTER) + throw InvalidInstruction(pc, "bad register"); + return Callx{(uint8_t)inst.imm}; + } + return Callx{inst.dst}; + } + auto makeJmp(ebpf_inst inst, const vector& insts, pc_t pc) -> Instruction { switch ((inst.opcode >> 4) & 0xF) { case INST_CALL: if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP) throw InvalidInstruction(pc, inst.opcode); - if (inst.opcode & INST_SRC_REG) + if (!info.platform->callx && (inst.opcode & INST_SRC_REG)) throw InvalidInstruction(pc, inst.opcode); if (inst.src > 0) throw InvalidInstruction(pc, inst.opcode); if (inst.offset != 0) throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode)); + if (inst.opcode & INST_SRC_REG) + return makeCallx(inst, pc); if (inst.dst != 0) throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode)); if (!info.platform->is_helper_usable(inst.imm)) - throw InvalidInstruction(pc, "invalid helper function id"); + throw InvalidInstruction(pc, "invalid helper function id " + std::to_string(inst.imm)); return makeCall(inst.imm); case INST_EXIT: if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP || (inst.opcode & INST_SRC_REG)) diff --git a/src/assertions.cpp b/src/assertions.cpp index f99a67d6f..d760a5420 100644 --- a/src/assertions.cpp +++ b/src/assertions.cpp @@ -105,6 +105,13 @@ class AssertExtractor { return res; } + vector operator()(Callx const& callx) const { + vector res; + res.emplace_back(TypeConstraint{callx.func, TypeGroup::number}); + res.emplace_back(FuncConstraint{callx.func}); + return res; + } + [[nodiscard]] vector explicate(Condition cond) const { if (info.type.is_privileged) @@ -234,6 +241,10 @@ class AssertExtractor { } }; +vector get_assertions(Instruction ins, const program_info& info) { + return std::visit(AssertExtractor{info}, ins); +} + /// Annotate the CFG by adding explicit assertions for all the preconditions /// of any instruction. For example, jump instructions are asserted not to /// compare numbers and pointers, or pointers to potentially distinct memory @@ -244,7 +255,7 @@ void explicate_assertions(cfg_t& cfg, const program_info& info) { (void)label; // unused vector insts; for (const auto& ins : vector(bb.begin(), bb.end())) { - for (auto a : std::visit(AssertExtractor{info}, ins)) + for (auto a : get_assertions(ins, info)) insts.emplace_back(a); insts.push_back(ins); } diff --git a/src/crab/cfg.hpp b/src/crab/cfg.hpp index c4ebe7404..dc9872402 100644 --- a/src/crab/cfg.hpp +++ b/src/crab/cfg.hpp @@ -519,6 +519,7 @@ std::map collect_stats(const cfg_t&); cfg_t prepare_cfg(const InstructionSeq& prog, const program_info& info, bool simplify, bool must_have_exit=true); void explicate_assertions(cfg_t& cfg, const program_info& info); +std::vector get_assertions(Instruction ins, const program_info& info); void print_dot(const cfg_t& cfg, std::ostream& out); void print_dot(const cfg_t& cfg, const std::string& outfile); diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index 67e74fd2b..f0b06bcc1 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -15,6 +15,7 @@ #include "crab/ebpf_domain.hpp" #include "asm_ostream.hpp" +#include "asm_unmarshal.hpp" #include "config.hpp" #include "dsl_syntax.hpp" #include "platform.hpp" @@ -1558,13 +1559,35 @@ void ebpf_domain_t::operator()(const ValidStore& s) { void ebpf_domain_t::operator()(const TypeConstraint& s) { if (!type_inv.is_in_group(m_inv, s.reg, s.types)) - require(m_inv, linear_constraint_t::FALSE(), ""); + require(m_inv, linear_constraint_t::FALSE(), "Invalid type"); +} + +void ebpf_domain_t::operator()(const FuncConstraint& s) { + // Look up the helper function id. + const reg_pack_t& reg = reg_pack(s.reg); + auto src_interval = m_inv.eval_interval(reg.svalue); + if (auto sn = src_interval.singleton()) { + if (sn->fits_sint32()) { + // We can now process it as if the id was immediate. + int32_t imm = sn->cast_to_sint32(); + if (!global_program_info->platform->is_helper_usable(imm)) { + require(m_inv, linear_constraint_t::FALSE(), "invalid helper function id " + std::to_string(imm)); + return; + } + Call call = make_call(imm, *global_program_info->platform); + for (Assert a : get_assertions(call, *global_program_info)) { + (*this)(a); + } + return; + } + } + require(m_inv, linear_constraint_t::FALSE(), "callx helper function id is not a valid singleton"); } void ebpf_domain_t::operator()(const ValidSize& s) { using namespace crab::dsl_syntax; auto r = reg_pack(s.reg); - require(m_inv, s.can_be_zero ? r.svalue >= 0 : r.svalue > 0, ""); + require(m_inv, s.can_be_zero ? r.svalue >= 0 : r.svalue > 0, "Invalid size"); } // Get the start and end of the range of possible map fd values. @@ -1825,7 +1848,7 @@ void ebpf_domain_t::operator()(const ValidAccess& s) { void ebpf_domain_t::operator()(const ZeroCtxOffset& s) { using namespace crab::dsl_syntax; auto reg = reg_pack(s.reg); - require(m_inv, reg.ctx_offset == 0, ""); + require(m_inv, reg.ctx_offset == 0, "Nonzero context offset"); } void ebpf_domain_t::operator()(const Assert& stmt) { @@ -2232,6 +2255,28 @@ void ebpf_domain_t::operator()(const Call& call) { } } +void ebpf_domain_t::operator()(const Callx& callx) { + using namespace crab::dsl_syntax; + if (m_inv.is_bottom()) + return; + + // Look up the helper function id. + const reg_pack_t& reg = reg_pack(callx.func); + auto src_interval = m_inv.eval_interval(reg.svalue); + if (auto sn = src_interval.singleton()) { + if (sn->fits_sint32()) { + // We can now process it as if the id was immediate. + int32_t imm = sn->cast_to_sint32(); + if (!global_program_info->platform->is_helper_usable(imm)) { + return; + } + Call call = make_call(imm, *global_program_info->platform); + (*this)(call); + return; + } + } +} + void ebpf_domain_t::do_load_mapfd(const Reg& dst_reg, int mapfd, bool maybe_null) { const EbpfMapDescriptor& desc = global_program_info->platform->get_map_descriptor(mapfd); const EbpfMapType& type = global_program_info->platform->get_map_type(desc.type); diff --git a/src/crab/ebpf_domain.hpp b/src/crab/ebpf_domain.hpp index fb3ef276a..efc2dabc8 100644 --- a/src/crab/ebpf_domain.hpp +++ b/src/crab/ebpf_domain.hpp @@ -61,8 +61,10 @@ class ebpf_domain_t final { void operator()(const Assume&); void operator()(const Bin&); void operator()(const Call&); + void operator()(const Callx&); void operator()(const Comparable&); void operator()(const Exit&); + void operator()(const FuncConstraint&); void operator()(const Jmp&); void operator()(const LoadMapFd&); void operator()(const LockAdd&); diff --git a/src/ebpf_vm_isa.hpp b/src/ebpf_vm_isa.hpp index 9941bfeeb..1b60c8478 100644 --- a/src/ebpf_vm_isa.hpp +++ b/src/ebpf_vm_isa.hpp @@ -61,7 +61,8 @@ enum { INST_OP_JA32 = ((INST_JA << 4) | INST_CLS_JMP32), INST_OP_JA16 = ((INST_JA << 4) | INST_CLS_JMP), - INST_OP_CALL = ((INST_CALL << 4) | INST_CLS_JMP), + INST_OP_CALL = ((INST_CALL << 4) | INST_SRC_IMM | INST_CLS_JMP), + INST_OP_CALLX = ((INST_CALL << 4) | INST_SRC_REG | INST_CLS_JMP), INST_OP_EXIT = ((INST_EXIT << 4) | INST_CLS_JMP), INST_ALU_OP_ADD = 0x00, diff --git a/src/ebpf_yaml.cpp b/src/ebpf_yaml.cpp index f728d6c88..dee8d9e3b 100644 --- a/src/ebpf_yaml.cpp +++ b/src/ebpf_yaml.cpp @@ -19,20 +19,25 @@ using std::vector; using std::string; +// The YAML tests for Call depend on Linux prototypes. +// parse_instruction() in asm_parse.cpp explicitly uses +// g_ebpf_platform_linux when parsing Call instructions +// so we do the same here. + static EbpfProgramType ebpf_get_program_type(const string& section, const string& path) { - return {}; + return g_ebpf_platform_linux.get_program_type(section, path); } static EbpfMapType ebpf_get_map_type(uint32_t platform_specific_type) { - return {}; + return g_ebpf_platform_linux.get_map_type(platform_specific_type); } static EbpfHelperPrototype ebpf_get_helper_prototype(int32_t n) { - return {}; + return g_ebpf_platform_linux.get_helper_prototype(n); } -static bool ebpf_is_helper_usable(int32_t n){ - return false; +static bool ebpf_is_helper_usable(int32_t n) { + return g_ebpf_platform_linux.is_helper_usable(n); } static void ebpf_parse_maps_section(vector& map_descriptors, const char* data, size_t map_record_size, int map_count, @@ -59,6 +64,7 @@ ebpf_platform_t g_platform_test = { .get_map_descriptor = ebpf_get_map_descriptor, .get_map_type = ebpf_get_map_type, .legacy = true, + .callx = true }; static EbpfProgramType make_program_type(const string& name, ebpf_context_descriptor_t* context_descriptor) { @@ -321,7 +327,9 @@ ConformanceTestResult run_conformance_test_case(const std::vector& memo pre_invariant = pre_invariant + stack_contents_invariant(memory_bytes); } raw_program raw_prog{.prog = insts}; - raw_prog.info.platform = &g_ebpf_platform_linux; + ebpf_platform_t platform = g_ebpf_platform_linux; + platform.callx = true; + raw_prog.info.platform = &platform; // Convert the raw program section to a set of instructions. std::variant prog_or_error = unmarshal(raw_prog); diff --git a/src/linux/gpl/spec_prototypes.cpp b/src/linux/gpl/spec_prototypes.cpp index 4f600a72c..915432509 100644 --- a/src/linux/gpl/spec_prototypes.cpp +++ b/src/linux/gpl/spec_prototypes.cpp @@ -44,6 +44,7 @@ const ebpf_context_descriptor_t g_sock_ops_descr = sock_ops_descr; static const struct EbpfHelperPrototype bpf_unspec_proto = { .name = "unspec", + .return_type = EBPF_RETURN_TYPE_UNSUPPORTED }; const struct EbpfHelperPrototype bpf_tail_call_proto = { diff --git a/src/linux/linux_platform.cpp b/src/linux/linux_platform.cpp index 1ae825cdc..a57ccc16b 100644 --- a/src/linux/linux_platform.cpp +++ b/src/linux/linux_platform.cpp @@ -255,5 +255,6 @@ const ebpf_platform_t g_ebpf_platform_linux = { get_map_descriptor_linux, get_map_type_linux, resolve_inner_map_references_linux, - true // Legacy packet access instructions + true, // Legacy packet access instructions + false // No callx instructions }; diff --git a/src/main/check.cpp b/src/main/check.cpp index bf832fc73..887177c92 100644 --- a/src/main/check.cpp +++ b/src/main/check.cpp @@ -68,6 +68,8 @@ int main(int argc, char** argv) { app.add_flag("-v", verbose, "Print both invariants and failures"); bool legacy = false; app.add_flag("--legacy", legacy, "Allow deprecated packet access instructions"); + bool callx = false; + app.add_flag("--callx", callx, "Allow callx instructions"); bool no_division_by_zero = false; app.add_flag("--no-division-by-zero", no_division_by_zero, "Do not allow division by zero"); app.add_flag("--no-simplify", ebpf_verifier_options.no_simplify, "Do not simplify"); @@ -115,6 +117,7 @@ int main(int argc, char** argv) { ebpf_verifier_options.mock_map_fds = false; ebpf_platform_t platform = g_ebpf_platform_linux; platform.legacy = legacy; + platform.callx = callx; // Read a set of raw program sections from an ELF file. vector raw_progs; diff --git a/src/platform.hpp b/src/platform.hpp index bc50b9601..d3635bfcd 100644 --- a/src/platform.hpp +++ b/src/platform.hpp @@ -43,8 +43,9 @@ struct ebpf_platform_t { ebpf_get_map_type_fn get_map_type; ebpf_resolve_inner_map_references_fn resolve_inner_map_references; - // Option indicating support for various deprecated instructions. + // Fields indicating support for various instruction types. bool legacy; + bool callx; }; extern const ebpf_platform_t g_ebpf_platform_linux; diff --git a/src/test/test_conformance.cpp b/src/test/test_conformance.cpp index abb00e63f..7474d7257 100644 --- a/src/test/test_conformance.cpp +++ b/src/test/test_conformance.cpp @@ -14,7 +14,8 @@ void test_conformance(std::string filename, bpf_conformance_test_result_t expect test_path.remove_filename().append("conformance_check" + extension.string()).string(); std::map> result = bpf_conformance( test_files, plugin_path, {}, {}, {}, bpf_conformance_test_CPU_version_t::v4, - bpf_conformance_groups_t::default_groups, bpf_conformance_list_instructions_t::LIST_INSTRUCTIONS_NONE, true); + bpf_conformance_groups_t::default_groups | bpf_conformance_groups_t::callx, + bpf_conformance_list_instructions_t::LIST_INSTRUCTIONS_NONE, true); for (auto file : test_files) { auto& [file_result, reason] = result[file]; REQUIRE(file_result == expected_result); @@ -76,6 +77,7 @@ TEST_CONFORMANCE("be32.data") TEST_CONFORMANCE("be64.data") TEST_CONFORMANCE_VERIFICATION_FAILED("call_local.data") TEST_CONFORMANCE("call_unwind_fail.data") +TEST_CONFORMANCE("callx.data") TEST_CONFORMANCE("div32-by-zero-reg.data") TEST_CONFORMANCE("div32-by-zero-reg-2.data") TEST_CONFORMANCE("div32-high-divisor.data") diff --git a/src/test/test_marshal.cpp b/src/test/test_marshal.cpp index 55d02fa98..c3ff87c30 100644 --- a/src/test/test_marshal.cpp +++ b/src/test/test_marshal.cpp @@ -5,6 +5,7 @@ #include "asm_ostream.hpp" #include "asm_marshal.hpp" #include "asm_unmarshal.hpp" +#include "../external/bpf_conformance/include/bpf_conformance.h" // Below we define a tample of instruction templates that specify // what values each field are allowed to contain. We first define @@ -20,9 +21,14 @@ constexpr int SRC = 9; // Any source register number. constexpr int IMM = -1; // Any imm value. constexpr int INVALID_REGISTER = R10_STACK_POINTER + 1; // Not a valid register. +struct ebpf_instruction_template_t { + ebpf_inst inst; + bpf_conformance_groups_t groups; +}; + // The following table is derived from the table in the Appendix of the // BPF ISA specification (https://datatracker.ietf.org/doc/draft-ietf-bpf-isa/). -static const ebpf_inst instruction_template[] = { +static const ebpf_instruction_template_t instruction_template[] = { // opcode, dst, src, offset, imm. {0x04, DST, 0, 0, IMM}, {0x05, 0, 0, JMP_OFFSET, 0}, @@ -123,6 +129,7 @@ static const ebpf_inst instruction_template[] = { // TODO(issue #590): Add support for calling a helper function by BTF ID. // {0x85, 0, 2, 0, IMM}, {0x87, DST, 0, 0, 0}, + {{0x8d, DST, 0, 0, 0}, bpf_conformance_groups_t::callx}, {0x94, DST, 0, 0, IMM}, {0x94, DST, 0, 1, IMM}, {0x95, 0, 0, 0, 0}, @@ -198,9 +205,9 @@ static const ebpf_inst instruction_template[] = { // Verify that if we unmarshal an instruction and then re-marshal it, // we get what we expect. -static void compare_unmarshal_marshal(const ebpf_inst& ins, const ebpf_inst& expected_result, const ebpf_platform_t* platform = &g_ebpf_platform_linux) { - program_info info{.platform = platform, - .type = platform->get_program_type("unspec", "unspec")}; +static void compare_unmarshal_marshal(const ebpf_inst& ins, const ebpf_inst& expected_result, const ebpf_platform_t& platform = g_ebpf_platform_linux) { + program_info info{.platform = &platform, + .type = platform.get_program_type("unspec", "unspec")}; const ebpf_inst exit{.opcode = INST_OP_EXIT}; InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", {ins, exit, exit}, info})); REQUIRE(parsed.size() == 3); @@ -253,8 +260,7 @@ static void compare_unmarshal_marshal(const ebpf_inst& ins1, const ebpf_inst& in // Verify that if we marshal an instruction and then unmarshal it, // we get the original. static void compare_marshal_unmarshal(const Instruction& ins, bool double_cmd = false, const ebpf_platform_t& platform = g_ebpf_platform_linux) { - program_info info{.platform = &platform, - .type = platform.get_program_type("unspec", "unspec")}; + program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", marshal(ins, 0), info})); REQUIRE(parsed.size() == 1); auto [_, single, _2] = parsed.back(); @@ -264,15 +270,13 @@ static void compare_marshal_unmarshal(const Instruction& ins, bool double_cmd = } static void check_marshal_unmarshal_fail(const Instruction& ins, std::string expected_error_message, const ebpf_platform_t& platform = g_ebpf_platform_linux) { - program_info info{.platform = &platform, - .type = platform.get_program_type("unspec", "unspec")}; + program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; std::string error_message = std::get(unmarshal(raw_program{"", "", marshal(ins, 0), info})); REQUIRE(error_message == expected_error_message); } static void check_unmarshal_fail(ebpf_inst inst, std::string expected_error_message, const ebpf_platform_t& platform = g_ebpf_platform_linux) { - program_info info{.platform = &platform, - .type = platform.get_program_type("unspec", "unspec")}; + program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; std::vector insns = {inst}; auto result = unmarshal(raw_program{"", "", insns, info}); REQUIRE(std::holds_alternative(result)); @@ -280,9 +284,8 @@ static void check_unmarshal_fail(ebpf_inst inst, std::string expected_error_mess REQUIRE(error_message == expected_error_message); } -static void check_unmarshal_fail_goto(ebpf_inst inst, const std::string& expected_error_message) { - program_info info{.platform = &g_ebpf_platform_linux, - .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")}; +static void check_unmarshal_fail_goto(ebpf_inst inst, const std::string& expected_error_message, const ebpf_platform_t& platform = g_ebpf_platform_linux) { + program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; const ebpf_inst exit{.opcode = INST_OP_EXIT}; std::vector insns{inst, exit, exit}; auto result = unmarshal(raw_program{"", "", insns, info}); @@ -292,9 +295,8 @@ static void check_unmarshal_fail_goto(ebpf_inst inst, const std::string& expecte } // Check that unmarshaling a 64-bit immediate instruction fails. -static void check_unmarshal_fail(ebpf_inst inst1, ebpf_inst inst2, std::string expected_error_message) { - program_info info{.platform = &g_ebpf_platform_linux, - .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")}; +static void check_unmarshal_fail(ebpf_inst inst1, ebpf_inst inst2, std::string expected_error_message, const ebpf_platform_t& platform = g_ebpf_platform_linux) { + program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; std::vector insns = {inst1, inst2}; auto result = unmarshal(raw_program{"", "", insns, info}); REQUIRE(std::holds_alternative(result)); @@ -330,7 +332,7 @@ TEST_CASE("disasm_marshal", "[disasm][marshal]") { } SECTION("r10") { check_marshal_unmarshal_fail(Bin{.op = Bin::Op::ADD, .dst = Reg{10}, .v = Imm{4}, .is64=true}, - "0: Invalid target r10\n"); + "0: invalid target r10\n"); } } } @@ -400,6 +402,25 @@ TEST_CASE("disasm_marshal", "[disasm][marshal]") { SECTION("Call") { for (int func : {1, 17}) compare_marshal_unmarshal(Call{func}); + + // Test callx without support. + std::ostringstream oss; + oss << "0: bad instruction op 0x" << std::hex << INST_OP_CALLX << std::endl; + check_unmarshal_fail(ebpf_inst{.opcode = INST_OP_CALLX}, oss.str()); + + // Test callx with support. Note that callx puts the register number in 'dst' not 'src'. + ebpf_platform_t platform = g_ebpf_platform_linux; + platform.callx = true; + compare_marshal_unmarshal(Callx{8}, false, platform); + ebpf_inst callx{.opcode = INST_OP_CALLX, .dst = 8}; + compare_unmarshal_marshal(callx, callx, platform); + check_unmarshal_fail({.opcode = INST_OP_CALLX, .dst = 11}, "0: bad register\n", platform); + check_unmarshal_fail({.opcode = INST_OP_CALLX, .dst = 8, .imm = 8}, "0: nonzero imm for op 0x8d\n", platform); + + // clang prior to v19 put the register into 'imm' instead of 'dst' so we treat it as equivalent. + compare_unmarshal_marshal(ebpf_inst{.opcode = /* 0x8d */ INST_OP_CALLX, .imm = 8}, callx, platform); + check_unmarshal_fail({.opcode = INST_OP_CALLX, .imm = 11}, "0: bad register\n", platform); + check_unmarshal_fail({.opcode = INST_OP_CALLX, .imm = -1}, "0: bad register\n", platform); } SECTION("Exit") { compare_marshal_unmarshal(Exit{}); } @@ -481,7 +502,7 @@ TEST_CASE("disasm_marshal_Mem", "[disasm][marshal]") { access.offset = 0; access.width = 8; check_marshal_unmarshal_fail(Mem{.access = access, .value = Reg{10}, .is_load = true}, - "0: Cannot modify r10\n"); + "0: cannot modify r10\n"); } SECTION("Store Register") { for (int w : ws) { @@ -518,108 +539,120 @@ TEST_CASE("unmarshal extension opcodes", "[disasm][marshal]") { } // Check that unmarshaling an invalid instruction fails with a given message. -static void check_unmarshal_instruction_fail(ebpf_inst& inst, const std::string& message) { +static void check_unmarshal_instruction_fail(ebpf_inst& inst, const std::string& message, const ebpf_platform_t& platform = g_ebpf_platform_linux) { if (inst.offset == JMP_OFFSET) { inst.offset = 1; check_unmarshal_fail_goto(inst, message); } else if (inst.opcode == INST_OP_LDDW_IMM) - check_unmarshal_fail(inst, ebpf_inst{}, message); + check_unmarshal_fail(inst, ebpf_inst{}, message, platform); else - check_unmarshal_fail(inst, message); + check_unmarshal_fail(inst, message, platform); +} + +static ebpf_platform_t get_template_platform(const ebpf_instruction_template_t& previous_template) { + ebpf_platform_t platform = g_ebpf_platform_linux; + if ((previous_template.groups & bpf_conformance_groups_t::callx) != bpf_conformance_groups_t::none) + platform.callx = true; + return platform; } // Check that various 'dst' variations between two valid instruction templates fail. -static void check_instruction_dst_variations(const ebpf_inst& previous_template, std::optional next_template) { - ebpf_inst inst = previous_template; +static void check_instruction_dst_variations(const ebpf_instruction_template_t& previous_template, std::optional next_template) { + ebpf_inst inst = previous_template.inst; + ebpf_platform_t platform = get_template_platform(previous_template); if (inst.dst == DST) { inst.dst = INVALID_REGISTER; - check_unmarshal_instruction_fail(inst, "0: bad register\n"); + check_unmarshal_instruction_fail(inst, "0: bad register\n", platform); } else { // This instruction doesn't put a register number in the 'dst' field. // Just try the next value unless that's what the next template has. inst.dst++; - if (inst != next_template) { + if (!next_template || (inst != next_template->inst)) { std::ostringstream oss; if (inst.dst == 1) oss << "0: nonzero dst for register op 0x" << std::hex << (int)inst.opcode << std::endl; else oss << "0: bad instruction op 0x" << std::hex << (int)inst.opcode << std::endl; - check_unmarshal_instruction_fail(inst, oss.str()); + check_unmarshal_instruction_fail(inst, oss.str(), platform); } } } // Check that various 'src' variations between two valid instruction templates fail. -static void check_instruction_src_variations(const ebpf_inst& previous_template, std::optional next_template) { - ebpf_inst inst = previous_template; +static void check_instruction_src_variations(const ebpf_instruction_template_t& previous_template, std::optional next_template) { + ebpf_inst inst = previous_template.inst; + ebpf_platform_t platform = get_template_platform(previous_template); if (inst.src == SRC) { inst.src = INVALID_REGISTER; - check_unmarshal_instruction_fail(inst, "0: bad register\n"); + check_unmarshal_instruction_fail(inst, "0: bad register\n", platform); } else { // This instruction doesn't put a register number in the 'src' field. // Just try the next value unless that's what the next template has. inst.src++; - if (inst != next_template) { + if (!next_template || (inst != next_template->inst)) { std::ostringstream oss; oss << "0: bad instruction op 0x" << std::hex << (int)inst.opcode << std::endl; - check_unmarshal_instruction_fail(inst, oss.str()); + check_unmarshal_instruction_fail(inst, oss.str(), platform); } } } // Check that various 'offset' variations between two valid instruction templates fail. -static void check_instruction_offset_variations(const ebpf_inst& previous_template, std::optional next_template) { - ebpf_inst inst = previous_template; +static void check_instruction_offset_variations(const ebpf_instruction_template_t& previous_template, std::optional next_template) { + ebpf_inst inst = previous_template.inst; + ebpf_platform_t platform = get_template_platform(previous_template); if (inst.offset == JMP_OFFSET) { inst.offset = 0; // Not a valid jump offset. - check_unmarshal_instruction_fail(inst, "0: jump out of bounds\n"); + check_unmarshal_instruction_fail(inst, "0: jump out of bounds\n", platform); } else if (inst.offset != MEM_OFFSET) { // This instruction limits what can appear in the 'offset' field. // Just try the next value unless that's what the next template has. inst.offset++; - if (inst != next_template) { + if (!next_template || (inst != next_template->inst)) { std::ostringstream oss; if (inst.offset == 1 && - (!next_template || next_template->opcode != inst.opcode || next_template->offset == 0)) + (!next_template || next_template->inst.opcode != inst.opcode || next_template->inst.offset == 0)) oss << "0: nonzero offset for op 0x" << std::hex << (int)inst.opcode << std::endl; else oss << "0: invalid offset for op 0x" << std::hex << (int)inst.opcode << std::endl; - check_unmarshal_instruction_fail(inst, oss.str()); + check_unmarshal_instruction_fail(inst, oss.str(), platform); } } } // Check that various 'imm' variations between two valid instruction templates fail. -static void check_instruction_imm_variations(const ebpf_inst& previous_template, std::optional next_template) { - ebpf_inst inst = previous_template; +static void check_instruction_imm_variations(const ebpf_instruction_template_t& previous_template, std::optional next_template) { + ebpf_inst inst = previous_template.inst; + ebpf_platform_t platform = get_template_platform(previous_template); if (inst.imm == JMP_OFFSET) { inst.imm = 0; // Not a valid jump offset. - check_unmarshal_instruction_fail(inst, "0: jump out of bounds\n"); + check_unmarshal_instruction_fail(inst, "0: jump out of bounds\n", platform); } else if (inst.imm != IMM) { // This instruction limits what can appear in the 'imm' field. // Just try the next value unless that's what the next template has. inst.imm++; - if (inst != next_template) { + if (!next_template || (inst != next_template->inst)) { std::ostringstream oss; if (inst.imm == 1) oss << "0: nonzero imm for op 0x" << std::hex << (int)inst.opcode << std::endl; else oss << "0: unsupported immediate" << std::endl; - check_unmarshal_instruction_fail(inst, oss.str()); + check_unmarshal_instruction_fail(inst, oss.str(), platform); } } // Some instructions only permit non-zero imm values. // If the next template is for one of those, check the zero value now. - if (next_template && (previous_template.opcode != next_template->opcode) && (next_template->imm > 0) && (next_template->imm != JMP_OFFSET)) { - inst = *next_template; + if (next_template && (previous_template.inst.opcode != next_template->inst.opcode) && + (next_template->inst.imm > 0) && (next_template->inst.imm != JMP_OFFSET)) { + inst = next_template->inst; inst.imm = 0; check_unmarshal_instruction_fail(inst, "0: unsupported immediate\n"); } } // Check that various variations between two valid instruction templates fail. -static void check_instruction_variations(std::optional previous_template, std::optional next_template) { +static void check_instruction_variations(std::optional previous_template, std::optional next_template) { if (previous_template) { check_instruction_dst_variations(*previous_template, next_template); check_instruction_src_variations(*previous_template, next_template); @@ -628,8 +661,8 @@ static void check_instruction_variations(std::optional previous } // Check any invalid opcodes in between the previous and next templates. - int previous_opcode = previous_template ? previous_template->opcode : -1; - int next_opcode = next_template ? next_template->opcode : 0x100; + int previous_opcode = previous_template ? previous_template->inst.opcode : -1; + int next_opcode = next_template ? next_template->inst.opcode : 0x100; for (int opcode = previous_opcode + 1; opcode < next_opcode; opcode++) { ebpf_inst inst{.opcode = (uint8_t)opcode}; std::ostringstream oss; diff --git a/src/test/test_yaml.cpp b/src/test/test_yaml.cpp index 8e95638ad..f94b598d0 100644 --- a/src/test/test_yaml.cpp +++ b/src/test/test_yaml.cpp @@ -23,6 +23,7 @@ YAML_CASE("test-data/add.yaml") YAML_CASE("test-data/assign.yaml") YAML_CASE("test-data/bitop.yaml") YAML_CASE("test-data/call.yaml") +YAML_CASE("test-data/callx.yaml") YAML_CASE("test-data/udivmod.yaml") YAML_CASE("test-data/sdivmod.yaml") YAML_CASE("test-data/full64.yaml") diff --git a/test-data/call.yaml b/test-data/call.yaml index 49b856236..47beb799f 100644 --- a/test-data/call.yaml +++ b/test-data/call.yaml @@ -76,7 +76,7 @@ post: - s[504...511].type=number messages: - - "0: (r3.type in {stack, packet})" + - "0: Invalid type (r3.type in {stack, packet})" - "0: Only stack or packet can be used as a parameter (within stack(r3:value_size(r1)))" --- test-case: bpf_ringbuf_output with numeric stack value @@ -129,7 +129,7 @@ post: - s[504...511].type=number messages: - - "0: (r2.type in {stack, packet, shared})" + - "0: Invalid type (r2.type in {stack, packet, shared})" --- test-case: bpf_get_stack @@ -161,7 +161,7 @@ post: - r0.type=number messages: - - "0: (r2.type in {stack, packet, shared})" + - "0: Invalid type (r2.type in {stack, packet, shared})" --- test-case: bpf_get_stack writing into shared memory diff --git a/test-data/callx.yaml b/test-data/callx.yaml new file mode 100644 index 000000000..d262aafde --- /dev/null +++ b/test-data/callx.yaml @@ -0,0 +1,266 @@ +# Copyright (c) Prevail Verifier contributors. +# SPDX-License-Identifier: MIT +--- +test-case: callx bpf_map_lookup_elem + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=stack", "r2.stack_offset=504", + "r8.type=number", "r8.svalue=1", "r8.uvalue=1", + "s[504...511].type=number"] + +code: + : | + callx r8; bpf_map_lookup_elem + +post: + - r0.type=shared + - r0.shared_offset=0 + - r0.shared_region_size=4 + - r0.svalue=[0, 2147418112] + - r0.uvalue=[0, 2147418112] + - r0.svalue=r0.uvalue + - r8.type=number + - r8.svalue=1 + - r8.uvalue=1 + - s[504...511].type=number +--- +test-case: callx 0 + +pre: ["r8.type=number", "r8.svalue=1000", "r8.uvalue=1000"] + +code: + : | + callx r8 + +post: + - r8.type=number + - r8.svalue=1000 + - r8.uvalue=1000 + +messages: + - "0: invalid helper function id 1000 (r8.type is helper)" +--- +test-case: callx with invalid id + +pre: ["r8.type=number", "r8.svalue=1000", "r8.uvalue=1000"] + +code: + : | + callx r8 + +post: + - r8.type=number + - r8.svalue=1000 + - r8.uvalue=1000 + +messages: + - "0: invalid helper function id 1000 (r8.type is helper)" +--- +test-case: callx with interval + +pre: ["r8.type=number", "r8.svalue=[1, 2]", "r8.uvalue=[1, 2]"] + +code: + : | + callx r8; bpf_map_lookup_elem + +post: + - r8.type=number + - r8.svalue=[1, 2] + - r8.uvalue=[1, 2] + +messages: + - "0: callx helper function id is not a valid singleton (r8.type is helper)" +--- +test-case: callx bpf_map_lookup_elem with non-numeric stack key + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=stack", + "r8.type=number", "r8.svalue=1", "r8.uvalue=1"] + +code: + : | + callx r8; bpf_map_lookup_elem + +post: + - r0.type=shared + - r0.shared_offset=0 + - r0.shared_region_size=4 + - r0.svalue=[0, 2147418112] + - r0.uvalue=[0, 2147418112] + - r0.svalue=r0.uvalue + - r8.type=number + - r8.svalue=1 + - r8.uvalue=1 + +messages: + - "0: Illegal map update with a non-numerical value [-oo-oo) (within stack(r2:key_size(r1)))" +--- +test-case: callx bpf_map_update_elem with non-numeric stack value + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=stack", "r2.stack_offset=504", + "r3.type=stack", "r3.stack_offset=496", + "r4.type=number", + "r8.type=number", "r8.svalue=2", "r8.uvalue=2", + "s[504...511].type=number"] + +code: + : | + callx r8; bpf_map_update_elem + +post: + - r0.type=number + - s[504...511].type=number + - r8.type=number + - r8.svalue=2 + - r8.uvalue=2 + +messages: + - "0: Illegal map update with a non-numerical value [496-500) (within stack(r3:value_size(r1)))" +--- +test-case: callx bpf_map_update_elem with context value + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=stack", "r2.stack_offset=504", + "r3.type=ctx", "r3.ctx_offset=0", + "r4.type=number", + "r8.type=number", "r8.svalue=2", "r8.uvalue=2", + "s[504...511].type=number"] + +code: + : | + callx r8; bpf_map_update_elem + +post: + - r0.type=number + - s[504...511].type=number + - r8.type=number + - r8.svalue=2 + - r8.uvalue=2 + +messages: + - "0: Invalid type (r3.type in {stack, packet})" + - "0: Only stack or packet can be used as a parameter (within stack(r3:value_size(r1)))" +--- +test-case: callx bpf_ringbuf_output with numeric stack value + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=stack", "r2.stack_offset=504", + "r3.type=number", "r3.svalue=8", "r3.uvalue=8", + "r4.type=number", + "r8.type=number", "r8.svalue=130", "r8.uvalue=130", + "s[504...511].type=number"] + +code: + : | + callx r8; bpf_ringbuf_output + +post: + - r0.type=number + - r8.type=number + - r8.svalue=130 + - r8.uvalue=130 + - s[504...511].type=number +--- +test-case: callx bpf_ringbuf_output with non-numeric stack value + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=stack", "r2.stack_offset=504", + "r3.type=number", "r3.svalue=8", "r3.uvalue=8", + "r4.type=number", + "r8.type=number", "r8.svalue=130", "r8.uvalue=130"] + +code: + : | + callx r8; bpf_ringbuf_output + +post: + - r0.type=number + - r8.type=number + - r8.svalue=130 + - r8.uvalue=130 + +messages: + - "0: Stack content is not numeric (valid_access(r2.offset, width=r3) for read)" +--- +test-case: callx bpf_ringbuf_output with context value + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=ctx", "r2.ctx_offset=0", + "r3.type=number", "r3.svalue=8", "r3.uvalue=8", + "r4.type=number", + "r8.type=number", "r8.svalue=130", "r8.uvalue=130", + "s[504...511].type=number"] + +code: + : | + callx r8; bpf_ringbuf_output + +post: + - r0.type=number + - r8.type=number + - r8.svalue=130 + - r8.uvalue=130 + - s[504...511].type=number + +messages: + - "0: Invalid type (r2.type in {stack, packet, shared})" +--- +test-case: callx bpf_get_stack + +pre: ["r1.type=ctx", "r1.ctx_offset=0", + "r2.type=stack", "r2.stack_offset=504", + "r3.type=number", "r3.svalue=8", "r3.uvalue=8", + "r4.type=number", + "r8.type=number", "r8.svalue=67", "r8.uvalue=67"] + +code: + : | + callx r8; bpf_get_stack + +post: + - r0.type=number + - r8.type=number + - r8.svalue=67 + - r8.uvalue=67 + - s[504...511].type=number +--- +test-case: callx bpf_get_stack overwriting ctx + +pre: ["r1.type=ctx", "r1.ctx_offset=0", + "r2.type=ctx", "r2.ctx_offset=0", + "r3.type=number", "r3.svalue=8", "r3.uvalue=8", + "r4.type=number", + "r8.type=number", "r8.svalue=67", "r8.uvalue=67"] + +code: + : | + callx r8; bpf_get_stack + +post: + - r0.type=number + - r8.type=number + - r8.svalue=67 + - r8.uvalue=67 + +messages: + - "0: Invalid type (r2.type in {stack, packet, shared})" +--- +test-case: callx bpf_get_stack writing into shared memory + +pre: ["r1.type=ctx", "r1.ctx_offset=0", + "r2.type=shared", "r2.shared_offset=0", "r2.shared_region_size=64", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", + "r3.type=number", "r3.svalue=64", "r3.uvalue=64", + "r4.type=number", + "r8.type=number", "r8.svalue=67", "r8.uvalue=67"] + +code: + : | + callx r8; bpf_get_stack, doesn't this leak pointers into the shared memory? + +post: + - r0.type=number + - r8.type=number + - r8.svalue=67 + - r8.uvalue=67 diff --git a/test-data/jump.yaml b/test-data/jump.yaml index 2595b703a..940b70179 100644 --- a/test-data/jump.yaml +++ b/test-data/jump.yaml @@ -714,4 +714,4 @@ post: - r1.map_fd-r2.map_fd<=-1 messages: - - "0: (r1.type == non_map_fd)" + - "0: Invalid type (r1.type == non_map_fd)" diff --git a/test-data/movsx.yaml b/test-data/movsx.yaml index 2645dddd8..6a3d3c835 100644 --- a/test-data/movsx.yaml +++ b/test-data/movsx.yaml @@ -97,7 +97,7 @@ code: post: [] messages: - - "0: (r1.type == number)" + - "0: Invalid type (r1.type == number)" --- test-case: movsx8 register range to 32 bits without wrap From f1ce7589a87b341f9169acb1f0f9a0950c64a798 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Mar 2024 16:14:03 +0000 Subject: [PATCH 049/373] Bump external/libbtf from `d0938e0` to `604e8bc` Bumps [external/libbtf](https://github.com/Alan-Jowett/libbtf) from `d0938e0` to `604e8bc`. - [Release notes](https://github.com/Alan-Jowett/libbtf/releases) - [Commits](https://github.com/Alan-Jowett/libbtf/compare/d0938e01fe143b632a13bce712605eba33e2169d...604e8bc8889060a4ab985afc7310cb701540124e) --- updated-dependencies: - dependency-name: external/libbtf dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/libbtf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/libbtf b/external/libbtf index d0938e01f..604e8bc88 160000 --- a/external/libbtf +++ b/external/libbtf @@ -1 +1 @@ -Subproject commit d0938e01fe143b632a13bce712605eba33e2169d +Subproject commit 604e8bc8889060a4ab985afc7310cb701540124e From 800101de8422499e490190059af56e2a9782f9fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Mar 2024 16:14:07 +0000 Subject: [PATCH 050/373] Bump external/bpf_conformance from `cfec46b` to `68a9fda` Bumps [external/bpf_conformance](https://github.com/Alan-Jowett/bpf_conformance) from `cfec46b` to `68a9fda`. - [Release notes](https://github.com/Alan-Jowett/bpf_conformance/releases) - [Commits](https://github.com/Alan-Jowett/bpf_conformance/compare/cfec46b36fae802c2534285229927bef351ad7a7...68a9fdab3a3093c9911840d78b71de24e1b66fd9) --- updated-dependencies: - dependency-name: external/bpf_conformance dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/bpf_conformance | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/bpf_conformance b/external/bpf_conformance index cfec46b36..68a9fdab3 160000 --- a/external/bpf_conformance +++ b/external/bpf_conformance @@ -1 +1 @@ -Subproject commit cfec46b36fae802c2534285229927bef351ad7a7 +Subproject commit 68a9fdab3a3093c9911840d78b71de24e1b66fd9 From c1b6cc3a245c45e7b978acc113d6dd753670603f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Mar 2024 16:38:40 +0000 Subject: [PATCH 051/373] Bump external/libbtf from `604e8bc` to `07e30bd` Bumps [external/libbtf](https://github.com/Alan-Jowett/libbtf) from `604e8bc` to `07e30bd`. - [Release notes](https://github.com/Alan-Jowett/libbtf/releases) - [Commits](https://github.com/Alan-Jowett/libbtf/compare/604e8bc8889060a4ab985afc7310cb701540124e...07e30bde8e12857694061dc2c6aea247d640615d) --- updated-dependencies: - dependency-name: external/libbtf dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/libbtf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/libbtf b/external/libbtf index 604e8bc88..07e30bde8 160000 --- a/external/libbtf +++ b/external/libbtf @@ -1 +1 @@ -Subproject commit 604e8bc8889060a4ab985afc7310cb701540124e +Subproject commit 07e30bde8e12857694061dc2c6aea247d640615d From 6d092f4a9b02967ecd1b3c0da5d826c75385bc2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Mar 2024 16:38:43 +0000 Subject: [PATCH 052/373] Bump external/bpf_conformance from `68a9fda` to `6356e3e` Bumps [external/bpf_conformance](https://github.com/Alan-Jowett/bpf_conformance) from `68a9fda` to `6356e3e`. - [Release notes](https://github.com/Alan-Jowett/bpf_conformance/releases) - [Commits](https://github.com/Alan-Jowett/bpf_conformance/compare/68a9fdab3a3093c9911840d78b71de24e1b66fd9...6356e3ee03b2c1a70fd6839a85713974bab999b3) --- updated-dependencies: - dependency-name: external/bpf_conformance dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/bpf_conformance | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/bpf_conformance b/external/bpf_conformance index 68a9fdab3..6356e3ee0 160000 --- a/external/bpf_conformance +++ b/external/bpf_conformance @@ -1 +1 @@ -Subproject commit 68a9fdab3a3093c9911840d78b71de24e1b66fd9 +Subproject commit 6356e3ee03b2c1a70fd6839a85713974bab999b3 From fe59c86c51091f5449409c2326eae7d257d36277 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 14 Mar 2024 19:28:20 +0200 Subject: [PATCH 053/373] Update .clang-format --- .clang-format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clang-format b/.clang-format index ad34e5d80..09976dd24 100644 --- a/.clang-format +++ b/.clang-format @@ -4,6 +4,6 @@ AlwaysBreakTemplateDeclarations: Yes AllowShortCaseLabelsOnASingleLine: true PointerAlignment: Left AlignEscapedNewlines: Left -#BreakAfterAttributes: Always +BreakAfterAttributes: Always PenaltyReturnTypeOnItsOwnLine: 60 AlwaysBreakAfterReturnType: None From a7c4cfc09be30bd6573e285c6573622752f84fd5 Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Thu, 14 Mar 2024 15:37:39 -0700 Subject: [PATCH 054/373] add other atomic instructions (#558) Fixes #483 * Fast path for atomic operations on shared memory --------- Signed-off-by: Dave Thaler --- src/asm_cfg.cpp | 2 +- src/asm_marshal.cpp | 15 +- src/asm_ostream.cpp | 18 +- src/asm_ostream.hpp | 2 +- src/asm_parse.cpp | 18 +- src/asm_syntax.hpp | 21 +- src/asm_unmarshal.cpp | 34 +- src/assertions.cpp | 10 +- src/crab/ebpf_domain.cpp | 65 +++- src/crab/ebpf_domain.hpp | 2 +- src/ebpf_vm_isa.hpp | 20 +- src/test/test_conformance.cpp | 40 +-- src/test/test_marshal.cpp | 25 +- src/test/test_yaml.cpp | 1 + test-data/atomic.yaml | 578 ++++++++++++++++++++++++++++++++++ 15 files changed, 788 insertions(+), 63 deletions(-) create mode 100644 test-data/atomic.yaml diff --git a/src/asm_cfg.cpp b/src/asm_cfg.cpp index 1b37a4328..b67e87921 100644 --- a/src/asm_cfg.cpp +++ b/src/asm_cfg.cpp @@ -178,7 +178,7 @@ static std::string instype(Instruction ins) { return "callx"; } else if (std::holds_alternative(ins)) { return std::get(ins).is_load ? "load" : "store"; - } else if (std::holds_alternative(ins)) { + } else if (std::holds_alternative(ins)) { return "load_store"; } else if (std::holds_alternative(ins)) { return "packet_access"; diff --git a/src/asm_marshal.cpp b/src/asm_marshal.cpp index a80f20345..5e7f015e7 100644 --- a/src/asm_marshal.cpp +++ b/src/asm_marshal.cpp @@ -219,7 +219,7 @@ struct MarshalVisitor { vector operator()(Mem const& b) { Deref access = b.access; ebpf_inst res{ - .opcode = static_cast((INST_MEM << 5) | width_to_opcode(access.width)), + .opcode = static_cast(INST_MODE_MEM | width_to_opcode(access.width)), .dst = 0, .src = 0, .offset = static_cast(access.offset), @@ -253,21 +253,24 @@ struct MarshalVisitor { .imm = static_cast(b.offset), }; if (b.regoffset) { - res.opcode |= (INST_IND << 5); + res.opcode |= INST_MODE_IND; res.src = b.regoffset->v; } else { - res.opcode |= (INST_ABS << 5); + res.opcode |= INST_MODE_ABS; } return {res}; } - vector operator()(LockAdd const& b) { + vector operator()(Atomic const& b) { + int32_t imm = (int32_t)b.op; + if (b.fetch) + imm |= INST_FETCH; return {ebpf_inst{ - .opcode = static_cast(INST_CLS_ST | 0x1 | (INST_XADD << 5) | width_to_opcode(b.access.width)), + .opcode = static_cast(INST_CLS_STX | INST_MODE_ATOMIC | width_to_opcode(b.access.width)), .dst = b.access.basereg.v, .src = b.valreg.v, .offset = static_cast(b.access.offset), - .imm = 0}}; + .imm = imm}}; } vector operator()(IncrementLoopCounter const& ins) { diff --git a/src/asm_ostream.cpp b/src/asm_ostream.cpp index 66e8f1991..283bece2d 100644 --- a/src/asm_ostream.cpp +++ b/src/asm_ostream.cpp @@ -328,10 +328,24 @@ struct InstructionPrinterVisitor { } } - void operator()(LockAdd const& b) { + void operator()(Atomic const& b) { os_ << "lock "; print(b.access); - os_ << " += " << b.valreg; + os_ << " "; + bool showfetch = true; + switch (b.op) { + case Atomic::Op::ADD: os_ << "+"; break; + case Atomic::Op::OR : os_ << "|"; break; + case Atomic::Op::AND: os_ << "&"; break; + case Atomic::Op::XOR: os_ << "^"; break; + case Atomic::Op::XCHG: os_ << "x"; showfetch = false; break; + case Atomic::Op::CMPXCHG: os_ << "cx"; showfetch = false; break; + } + os_ << "= " << b.valreg; + + if (showfetch && b.fetch) { + os_ << " fetch"; + } } void operator()(Assume const& b) { diff --git a/src/asm_ostream.hpp b/src/asm_ostream.hpp index 31af5d63c..e51842cad 100644 --- a/src/asm_ostream.hpp +++ b/src/asm_ostream.hpp @@ -59,7 +59,7 @@ inline std::ostream& operator<<(std::ostream& os, Exit const& a) { return os << inline std::ostream& operator<<(std::ostream& os, Jmp const& a) { return os << (Instruction)a; } inline std::ostream& operator<<(std::ostream& os, Packet const& a) { return os << (Instruction)a; } inline std::ostream& operator<<(std::ostream& os, Mem const& a) { return os << (Instruction)a; } -inline std::ostream& operator<<(std::ostream& os, LockAdd const& a) { return os << (Instruction)a; } +inline std::ostream& operator<<(std::ostream& os, Atomic const& a) { return os << (Instruction)a; } inline std::ostream& operator<<(std::ostream& os, Assume const& a) { return os << (Instruction)a; } inline std::ostream& operator<<(std::ostream& os, Assert const& a) { return os << (Instruction)a; } inline std::ostream& operator<<(std::ostream& os, IncrementLoopCounter const& a) { return os << (Instruction)a; } diff --git a/src/asm_parse.cpp b/src/asm_parse.cpp index a6e00d89b..474446fbd 100644 --- a/src/asm_parse.cpp +++ b/src/asm_parse.cpp @@ -34,6 +34,7 @@ using crab::linear_expression_t; #define ASSIGN R"_(\s*=\s*)_" #define LONGLONG R"_(\s*(ll|)\s*)_" #define UNOP R"_((-|be16|be32|be64|le16|le32|le64|swap16|swap32|swap64))_" +#define ATOMICOP R"_((\+|\||&|\^|x|cx)=)_" #define PLUSMINUS R"_((\s*[+-])\s*)_" #define LPAREN R"_(\s*\(\s*)_" @@ -81,6 +82,14 @@ static const std::map str_to_cmpop = { {"s<", Condition::Op::SLT}, {"s<=", Condition::Op::SLE}, {"s>", Condition::Op::SGT}, {"s>=", Condition::Op::SGE}, }; +static const std::map str_to_atomicop = { + {"+", Atomic::Op::ADD}, + {"|", Atomic::Op::OR}, + {"&", Atomic::Op::AND}, + {"^", Atomic::Op::XOR}, + {"x", Atomic::Op::XCHG}, + {"cx", Atomic::Op::CMPXCHG}}; + static const std::map str_to_width = { {"8", 1}, {"16", 2}, @@ -182,8 +191,13 @@ Instruction parse_instruction(const std::string& line, const std::map; +using Instruction = std::variant; using LabeledInstruction = std::tuple>; using InstructionSeq = std::vector; diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index b6ad9b670..71232c936 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -177,6 +177,21 @@ struct Unmarshaller { return {}; } + auto getAtomicOp(size_t pc, ebpf_inst inst) -> Atomic::Op { + Atomic::Op op = (Atomic::Op)(inst.imm & ~INST_FETCH); + switch (op) { + case Atomic::Op::XCHG: + case Atomic::Op::CMPXCHG: + if ((inst.imm & INST_FETCH) == 0) + throw InvalidInstruction(pc, "unsupported immediate"); + case Atomic::Op::ADD: + case Atomic::Op::OR: + case Atomic::Op::AND: + case Atomic::Op::XOR: return op; + } + throw InvalidInstruction(pc, "unsupported immediate"); + } + uint64_t sign_extend(int32_t imm) { return (uint64_t)(int64_t)imm; } uint64_t zero_extend(int32_t imm) { return (uint64_t)(uint32_t)imm; } @@ -223,9 +238,10 @@ struct Unmarshaller { int width = getMemWidth(inst.opcode); bool isLD = (inst.opcode & INST_CLS_MASK) == INST_CLS_LD; - switch ((inst.opcode & INST_MODE_MASK) >> 5) { - case 0: throw InvalidInstruction(pc, inst.opcode); - case INST_ABS: + switch (inst.opcode & INST_MODE_MASK) { + case INST_MODE_IMM: + throw InvalidInstruction(pc, inst.opcode); + case INST_MODE_ABS: if (!info.platform->legacy || !isLD || (width == 8)) throw InvalidInstruction(pc, inst.opcode); if (inst.dst != 0) @@ -236,7 +252,7 @@ struct Unmarshaller { throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode)); return Packet{.width = width, .offset = inst.imm, .regoffset = {}}; - case INST_IND: + case INST_MODE_IND: if (!info.platform->legacy || !isLD || (width == 8)) throw InvalidInstruction(pc, inst.opcode); if (inst.dst != 0) @@ -247,7 +263,7 @@ struct Unmarshaller { throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode)); return Packet{.width = width, .offset = inst.imm, .regoffset = Reg{inst.src}}; - case INST_MEM: { + case INST_MODE_MEM: { if (isLD) throw InvalidInstruction(pc, inst.opcode); bool isLoad = getMemIsLoad(inst.opcode); @@ -279,14 +295,14 @@ struct Unmarshaller { return res; } - case INST_XADD: + case INST_MODE_ATOMIC: if (((inst.opcode & INST_CLS_MASK) != INST_CLS_STX) || ((inst.opcode & INST_SIZE_MASK) != INST_SIZE_W && (inst.opcode & INST_SIZE_MASK) != INST_SIZE_DW)) throw InvalidInstruction(pc, inst.opcode); - if (inst.imm != 0) - throw InvalidInstruction(pc, "unsupported immediate"); - return LockAdd{ + return Atomic{ + .op = getAtomicOp(pc, inst), + .fetch = (inst.imm & INST_FETCH) == INST_FETCH, .access = Deref{ .width = width, diff --git a/src/assertions.cpp b/src/assertions.cpp index d760a5420..c3e090134 100644 --- a/src/assertions.cpp +++ b/src/assertions.cpp @@ -170,11 +170,17 @@ class AssertExtractor { return res; } - vector operator()(LockAdd ins) const { + vector operator()(Atomic ins) const { vector res; - res.emplace_back(TypeConstraint{ins.access.basereg, TypeGroup::shared}); + res.emplace_back(TypeConstraint{ins.access.basereg, TypeGroup::pointer}); res.emplace_back(ValidAccess{ins.access.basereg, ins.access.offset, Imm{static_cast(ins.access.width)}, false}); + if (ins.op == Atomic::Op::CMPXCHG) { + // The memory contents pointed to by ins.access will be compared + // against the value of the ins.valreg register. Only numbers are + // supported. + res.emplace_back(TypeConstraint{ins.valreg, TypeGroup::number}); + } return res; } diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index f0b06bcc1..a8c9e1f01 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -2154,8 +2154,69 @@ void ebpf_domain_t::do_mem_store(const Mem& b, Type val_type, SValue val_svalue, }); } -void ebpf_domain_t::operator()(const LockAdd& a) { - // nothing to do here +// Construct a Bin operation that does the main operation that a given Atomic operation does atomically. +static Bin atomic_to_bin(const Atomic& a) { + Bin bin{ + .dst = Reg{R11_ATOMIC_SCRATCH}, .v = a.valreg, .is64 = (a.access.width == sizeof(uint64_t)), .lddw = false}; + switch (a.op) { + case Atomic::Op::ADD: bin.op = Bin::Op::ADD; break; + case Atomic::Op::OR: bin.op = Bin::Op::OR; break; + case Atomic::Op::AND: bin.op = Bin::Op::AND; break; + case Atomic::Op::XOR: bin.op = Bin::Op::XOR; break; + case Atomic::Op::XCHG: + case Atomic::Op::CMPXCHG: bin.op = Bin::Op::MOV; break; + default: throw std::exception(); + } + return bin; +} + +void ebpf_domain_t::operator()(const Atomic& a) { + if (m_inv.is_bottom()) + return; + if (!m_inv.entail(type_is_pointer(reg_pack(a.access.basereg))) || + !m_inv.entail(type_is_number(reg_pack(a.valreg)))) { + return; + } + if (m_inv.entail(type_is_not_stack(reg_pack(a.access.basereg)))) { + // Shared memory regions are volatile so we can just havoc + // any register that will be updated. + if (a.op == Atomic::Op::CMPXCHG) + havoc_register(m_inv, Reg{R0_RETURN_VALUE}); + else if (a.fetch) + havoc_register(m_inv, a.valreg); + return; + } + + // Fetch the current value into the R11 pseudo-register. + const Reg r11{R11_ATOMIC_SCRATCH}; + (*this)(Mem{.access = a.access, .value = r11, .is_load = true}); + + // Compute the new value in R11. + (*this)(atomic_to_bin(a)); + + if (a.op == Atomic::Op::CMPXCHG) { + // For CMPXCHG, store the original value in r0. + (*this)(Mem{.access = a.access, .value = Reg{R0_RETURN_VALUE}, .is_load = true}); + + // For the destination, there are 3 possibilities: + // 1) dst.value == r0.value : set R11 to valreg + // 2) dst.value != r0.value : don't modify R11 + // 3) dst.value may or may not == r0.value : set R11 to the union of R11 and valreg + // For now we just havoc the value of R11. + havoc_register(m_inv, r11); + } else if (a.fetch) { + // For other FETCH operations, store the original value in the src register. + (*this)(Mem{.access = a.access, .value = a.valreg, .is_load = true}); + } + + // Store the new value back in the original shared memory location. + // Note that do_mem_store() currently doesn't track shared memory values, + // but stack memory values are tracked and are legal here. + (*this)(Mem{.access = a.access, .value = r11, .is_load = false}); + + // Clear the R11 pseudo-register. + havoc_register(m_inv, r11); + type_inv.havoc_type(m_inv, r11); } void ebpf_domain_t::operator()(const Call& call) { diff --git a/src/crab/ebpf_domain.hpp b/src/crab/ebpf_domain.hpp index efc2dabc8..39f11e73b 100644 --- a/src/crab/ebpf_domain.hpp +++ b/src/crab/ebpf_domain.hpp @@ -67,7 +67,7 @@ class ebpf_domain_t final { void operator()(const FuncConstraint&); void operator()(const Jmp&); void operator()(const LoadMapFd&); - void operator()(const LockAdd&); + void operator()(const Atomic&); void operator()(const Mem&); void operator()(const ValidDivisor&); void operator()(const Packet&); diff --git a/src/ebpf_vm_isa.hpp b/src/ebpf_vm_isa.hpp index 1b60c8478..8af2b2503 100644 --- a/src/ebpf_vm_isa.hpp +++ b/src/ebpf_vm_isa.hpp @@ -45,16 +45,19 @@ enum { INST_MODE_MASK = 0xe0, - INST_ABS = 1, // Deprecated - INST_IND = 2, // Deprecated - INST_MEM = 3, - INST_LEN = 4, - INST_MSH = 5, - INST_XADD = 6, - INST_MEM_UNUSED = 7, + INST_MODE_IMM = 0x00, // 64-bit immediate instructions + INST_MODE_ABS = 0x20, // legacy BPF packet access (absolute) + INST_MODE_IND = 0x40, // legacy BPF packet access (indirect) + INST_MODE_MEM = 0x60, // regular load and store operations + INST_MODE_MEMSX = 0x80, // sign-extension load operations + INST_MODE_UNUSED1 = 0xa0, + INST_MODE_ATOMIC = 0xc0, // atomic operations + INST_MODE_UNUSED2 = 0xe0, INST_OP_LDDW_IMM = (INST_CLS_LD | INST_SRC_IMM | INST_SIZE_DW), // Special + INST_FETCH = 0x1, + INST_JA = 0x0, INST_CALL = 0x8, INST_EXIT = 0x9, @@ -91,7 +94,8 @@ enum { R7 = 7, R8 = 8, R9 = 9, - R10_STACK_POINTER = 10 + R10_STACK_POINTER = 10, + R11_ATOMIC_SCRATCH = 11, // Pseudo-register used internally for atomic instructions. }; int opcode_to_width(uint8_t opcode); diff --git a/src/test/test_conformance.cpp b/src/test/test_conformance.cpp index 7474d7257..b1f2dce57 100644 --- a/src/test/test_conformance.cpp +++ b/src/test/test_conformance.cpp @@ -149,26 +149,26 @@ TEST_CONFORMANCE("ldxw.data") TEST_CONFORMANCE("le16.data") TEST_CONFORMANCE("le32.data") TEST_CONFORMANCE("le64.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_add.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_add32.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_and.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_and32.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_cmpxchg.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_cmpxchg32.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_fetch_add.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_fetch_add32.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_fetch_and.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_fetch_and32.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_fetch_or.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_fetch_or32.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_fetch_xor.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_fetch_xor32.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_or.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_or32.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_xchg.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_xchg32.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_xor.data") -TEST_CONFORMANCE_VERIFICATION_FAILED("lock_xor32.data") +TEST_CONFORMANCE("lock_add.data") +TEST_CONFORMANCE_RANGE("lock_add32.data", "[0, 1311768467463790321]") +TEST_CONFORMANCE("lock_and.data") +TEST_CONFORMANCE_TOP("lock_and32.data") +TEST_CONFORMANCE_TOP("lock_cmpxchg.data") +TEST_CONFORMANCE_TOP("lock_cmpxchg32.data") +TEST_CONFORMANCE("lock_fetch_add.data") +TEST_CONFORMANCE_RANGE("lock_fetch_add32.data", "[0, 1311768467463790321]") +TEST_CONFORMANCE("lock_fetch_and.data") +TEST_CONFORMANCE_TOP("lock_fetch_and32.data") +TEST_CONFORMANCE("lock_fetch_or.data") +TEST_CONFORMANCE_TOP("lock_fetch_or32.data") +TEST_CONFORMANCE("lock_fetch_xor.data") +TEST_CONFORMANCE_TOP("lock_fetch_xor32.data") +TEST_CONFORMANCE("lock_or.data") +TEST_CONFORMANCE_TOP("lock_or32.data") +TEST_CONFORMANCE("lock_xchg.data") +TEST_CONFORMANCE("lock_xchg32.data") +TEST_CONFORMANCE("lock_xor.data") +TEST_CONFORMANCE_TOP("lock_xor32.data") TEST_CONFORMANCE("lsh32-imm.data") TEST_CONFORMANCE("lsh32-imm-high.data") TEST_CONFORMANCE("lsh32-imm-neg.data") diff --git a/src/test/test_marshal.cpp b/src/test/test_marshal.cpp index c3ff87c30..02ee4c4ff 100644 --- a/src/test/test_marshal.cpp +++ b/src/test/test_marshal.cpp @@ -434,11 +434,26 @@ TEST_CASE("disasm_marshal", "[disasm][marshal]") { } } - SECTION("LockAdd") { + SECTION("Atomic") { for (int w : ws) { if (w == 4 || w == 8) { Deref access{.width = w, .basereg = Reg{2}, .offset = 17}; - compare_marshal_unmarshal(LockAdd{.access = access, .valreg = Reg{1}}); + compare_marshal_unmarshal(Atomic{.op = Atomic::Op::ADD, .fetch = false, .access = access, .valreg = Reg{1}}); + compare_marshal_unmarshal(Atomic{.op = Atomic::Op::ADD, .fetch = true, .access = access, .valreg = Reg{1}}); + compare_marshal_unmarshal(Atomic{.op = Atomic::Op::OR, .fetch = false, .access = access, .valreg = Reg{1}}); + compare_marshal_unmarshal(Atomic{.op = Atomic::Op::OR, .fetch = true, .access = access, .valreg = Reg{1}}); + compare_marshal_unmarshal(Atomic{.op = Atomic::Op::AND, .fetch = false, .access = access, .valreg = Reg{1}}); + compare_marshal_unmarshal(Atomic{.op = Atomic::Op::AND, .fetch = true, .access = access, .valreg = Reg{1}}); + compare_marshal_unmarshal(Atomic{.op = Atomic::Op::XOR, .fetch = false, .access = access, .valreg = Reg{1}}); + compare_marshal_unmarshal(Atomic{.op = Atomic::Op::XOR, .fetch = true, .access = access, .valreg = Reg{1}}); + check_marshal_unmarshal_fail( + Atomic{.op = Atomic::Op::XCHG, .fetch = false, .access = access, .valreg = Reg{1}}, + "0: unsupported immediate\n"); + compare_marshal_unmarshal(Atomic{.op = Atomic::Op::XCHG, .fetch = true, .access = access, .valreg = Reg{1}}); + check_marshal_unmarshal_fail( + Atomic{.op = Atomic::Op::CMPXCHG, .fetch = false, .access = access, .valreg = Reg{1}}, + "0: unsupported immediate\n"); + compare_marshal_unmarshal(Atomic{.op = Atomic::Op::CMPXCHG, .fetch = true, .access = access, .valreg = Reg{1}}); } } } @@ -450,7 +465,7 @@ TEST_CASE("marshal", "[disasm][marshal]") { Mem m{.access = access, .value = Reg{3}, .is_load = true}; auto ins = marshal(m, 0).at(0); ebpf_inst expect{ - .opcode = (uint8_t)(INST_CLS_LD | (INST_MEM << 5) | width_to_opcode(1) | 0x1), + .opcode = (uint8_t)(INST_CLS_LD | INST_MODE_MEM | width_to_opcode(1) | 0x1), .dst = 3, .src = 4, .offset = 6, @@ -473,7 +488,7 @@ TEST_CASE("marshal", "[disasm][marshal]") { REQUIRE(ins.dst == 4); REQUIRE(ins.offset == 6); REQUIRE(ins.imm == 0); - REQUIRE(ins.opcode == (uint8_t)(INST_CLS_ST | (INST_MEM << 5) | width_to_opcode(1) | 0x1)); + REQUIRE(ins.opcode == (uint8_t)(INST_CLS_ST | INST_MODE_MEM | width_to_opcode(1) | 0x1)); } SECTION("StoreImm") { Deref access{.width = 1, .basereg = Reg{4}, .offset = 6}; @@ -482,7 +497,7 @@ TEST_CASE("marshal", "[disasm][marshal]") { REQUIRE(ins.dst == 4); REQUIRE(ins.offset == 6); REQUIRE(ins.imm == 3); - REQUIRE(ins.opcode == (uint8_t)(INST_CLS_ST | (INST_MEM << 5) | width_to_opcode(1) | 0x0)); + REQUIRE(ins.opcode == (uint8_t)(INST_CLS_ST | INST_MODE_MEM | width_to_opcode(1) | 0x0)); } } diff --git a/src/test/test_yaml.cpp b/src/test/test_yaml.cpp index f94b598d0..da124a8bf 100644 --- a/src/test/test_yaml.cpp +++ b/src/test/test_yaml.cpp @@ -21,6 +21,7 @@ YAML_CASE("test-data/add.yaml") YAML_CASE("test-data/assign.yaml") +YAML_CASE("test-data/atomic.yaml") YAML_CASE("test-data/bitop.yaml") YAML_CASE("test-data/call.yaml") YAML_CASE("test-data/callx.yaml") diff --git a/test-data/atomic.yaml b/test-data/atomic.yaml new file mode 100644 index 000000000..2e4787ef3 --- /dev/null +++ b/test-data/atomic.yaml @@ -0,0 +1,578 @@ +# Copyright (c) Prevail Verifier contributors. +# SPDX-License-Identifier: MIT +--- +test-case: atomic 32-bit ADD shared + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u32 *)(r2 + 4) += r1 + +post: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 32-bit ADD and fetch shared + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u32 *)(r2 + 4) += r1 fetch + +post: ["r1.type=number", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 64-bit ADD shared + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u64 *)(r2 + 4) += r1 + +post: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 64-bit ADD and fetch shared + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u64 *)(r2 + 4) += r1 fetch + +post: ["r1.type=number", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 32-bit AND shared + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u32 *)(r2 + 4) &= r1 + +post: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 32-bit AND and fetch shared + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u32 *)(r2 + 4) &= r1 fetch + +post: ["r1.type=number", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 64-bit AND shared + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u64 *)(r2 + 4) &= r1 + +post: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 64-bit AND and fetch shared + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u64 *)(r2 + 4) &= r1 fetch + +post: ["r1.type=number", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 32-bit OR shared + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u32 *)(r2 + 4) |= r1 + +post: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 32-bit OR and fetch shared + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u32 *)(r2 + 4) |= r1 fetch + +post: ["r1.type=number", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 64-bit OR shared + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u64 *)(r2 + 4) |= r1 + +post: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 64-bit OR and fetch shared + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u64 *)(r2 + 4) |= r1 fetch + +post: ["r1.type=number", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 32-bit XOR shared + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u32 *)(r2 + 4) ^= r1 + +post: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 32-bit XOR and fetch shared + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u32 *)(r2 + 4) ^= r1 fetch + +post: ["r1.type=number", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 64-bit XOR shared + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u64 *)(r2 + 4) ^= r1 + +post: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 64-bit XOR and fetch shared + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u64 *)(r2 + 4) ^= r1 fetch + +post: ["r1.type=number", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 32-bit XCHG shared + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u32 *)(r2 + 4) x= r1 + +post: ["r1.type=number", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 64-bit XCHG shared + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u64 *)(r2 + 4) x= r1 + +post: ["r1.type=number", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 32-bit CMPXCHG shared + +pre: ["r0.type=number", "r0.svalue=1", "r0.uvalue=1", + "r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u32 *)(r2 + 4) cx= r1 + +post: ["r0.type=number", + "r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=12", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 64-bit CMPXCHG shared + +pre: ["r0.type=number", "r0.svalue=2", "r0.uvalue=2", + "r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u64 *)(r2 + 4) cx= r1 + +post: ["r0.type=number", + "r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] +--- +test-case: atomic 32-bit ADD past end of shared region + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=10", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u32 *)(r2 + 4) += r1 + +post: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=shared", "r2.shared_region_size=10", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +messages: + - "0: Upper bound must be at most r2.shared_region_size (valid_access(r2.offset+4, width=4) for write)" +--- +test-case: atomic 32-bit ADD stack + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=1", "s[4...7].uvalue=1"] + +code: + : | + lock *(u32 *)(r2 + 4) += r1 + +post: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=3", "s[4...7].uvalue=3"] +--- +test-case: atomic 32-bit ADD and fetch stack + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=1", "s[4...7].uvalue=1"] + +code: + : | + lock *(u32 *)(r2 + 4) += r1 fetch + +post: ["r1.type=number", "r1.svalue=1", "r1.uvalue=1", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=3", "s[4...7].uvalue=3"] +--- +test-case: atomic 64-bit ADD stack + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=1", "s[4...11].uvalue=1"] + +code: + : | + lock *(u64 *)(r2 + 4) += r1 + +post: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=3", "s[4...11].uvalue=3"] +--- +test-case: atomic 64-bit ADD and fetch stack + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=1", "s[4...11].uvalue=1"] + +code: + : | + lock *(u64 *)(r2 + 4) += r1 fetch + +post: ["r1.type=number", "r1.svalue=1", "r1.uvalue=1", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=3", "s[4...11].uvalue=3"] +--- +test-case: atomic 32-bit AND stack + +pre: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=5", "s[4...7].uvalue=5"] + +code: + : | + lock *(u32 *)(r2 + 4) &= r1 + +post: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=1", "s[4...7].uvalue=1"] +--- +test-case: atomic 32-bit AND and fetch stack + +pre: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=5", "s[4...7].uvalue=5"] + +code: + : | + lock *(u32 *)(r2 + 4) &= r1 fetch + +post: ["r1.type=number", "r1.svalue=5", "r1.uvalue=5", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=1", "s[4...7].uvalue=1"] +--- +test-case: atomic 64-bit AND stack + +pre: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=5", "s[4...11].uvalue=5"] + +code: + : | + lock *(u64 *)(r2 + 4) &= r1 + +post: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=1", "s[4...11].uvalue=1"] +--- +test-case: atomic 64-bit AND and fetch stack + +pre: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=5", "s[4...11].uvalue=5"] + +code: + : | + lock *(u64 *)(r2 + 4) &= r1 fetch + +post: ["r1.type=number", "r1.svalue=5", "r1.uvalue=5", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=1", "s[4...11].uvalue=1"] +--- +test-case: atomic 32-bit OR stack + +pre: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=5", "s[4...7].uvalue=5"] + +code: + : | + lock *(u32 *)(r2 + 4) |= r1 + +post: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=7", "s[4...7].uvalue=7"] +--- +test-case: atomic 32-bit OR and fetch stack + +pre: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=5", "s[4...7].uvalue=5"] + +code: + : | + lock *(u32 *)(r2 + 4) |= r1 fetch + +post: ["r1.type=number", "r1.svalue=5", "r1.uvalue=5", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=7", "s[4...7].uvalue=7"] +--- +test-case: atomic 64-bit OR stack + +pre: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=5", "s[4...11].uvalue=5"] + +code: + : | + lock *(u64 *)(r2 + 4) |= r1 + +post: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=7", "s[4...11].uvalue=7"] +--- +test-case: atomic 64-bit OR and fetch stack + +pre: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=5", "s[4...11].uvalue=5"] + +code: + : | + lock *(u64 *)(r2 + 4) |= r1 fetch + +post: ["r1.type=number", "r1.svalue=5", "r1.uvalue=5", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=7", "s[4...11].uvalue=7"] +--- +test-case: atomic 32-bit XOR stack + +pre: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=5", "s[4...7].uvalue=5"] + +code: + : | + lock *(u32 *)(r2 + 4) ^= r1 + +post: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=6", "s[4...7].uvalue=6"] +--- +test-case: atomic 32-bit XOR and fetch stack + +pre: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=5", "s[4...7].uvalue=5"] + +code: + : | + lock *(u32 *)(r2 + 4) ^= r1 fetch + +post: ["r1.type=number", "r1.svalue=5", "r1.uvalue=5", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=6", "s[4...7].uvalue=6"] +--- +test-case: atomic 64-bit XOR stack + +pre: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=5", "s[4...11].uvalue=5"] + +code: + : | + lock *(u64 *)(r2 + 4) ^= r1 + +post: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=6", "s[4...11].uvalue=6"] +--- +test-case: atomic 64-bit XOR and fetch stack + +pre: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=5", "s[4...11].uvalue=5"] + +code: + : | + lock *(u64 *)(r2 + 4) ^= r1 fetch + +post: ["r1.type=number", "r1.svalue=5", "r1.uvalue=5", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=6", "s[4...11].uvalue=6"] +--- +test-case: atomic 32-bit XCHG stack + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=5", "s[4...7].uvalue=5"] + +code: + : | + lock *(u32 *)(r2 + 4) x= r1 + +post: ["r1.type=number", "r1.svalue=5", "r1.uvalue=5", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=2", "s[4...7].uvalue=2"] +--- +test-case: atomic 64-bit XCHG stack + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=5", "s[4...11].uvalue=5"] + +code: + : | + lock *(u64 *)(r2 + 4) x= r1 + +post: ["r1.type=number", "r1.svalue=5", "r1.uvalue=5", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=2", "s[4...11].uvalue=2"] +--- +test-case: atomic 32-bit CMPXCHG stack + +pre: ["r0.type=number", "r0.svalue=1", "r0.uvalue=1", + "r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=1", "s[4...7].uvalue=1"] + +code: + : | + lock *(u32 *)(r2 + 4) cx= r1 + +post: ["r0.type=number", "r0.svalue=1", "r0.uvalue=1", + "r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number"] +--- +test-case: atomic 64-bit CMPXCHG stack + +pre: ["r0.type=number", "r0.svalue=1", "r0.uvalue=1", + "r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=1", "s[4...11].uvalue=1"] + +code: + : | + lock *(u64 *)(r2 + 4) cx= r1 + +post: ["r0.type=number", "r0.svalue=1", "r0.uvalue=1", + "r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number"] +--- +test-case: atomic 32-bit ADD to non-pointer space + +pre: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=number", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u32 *)(r2 + 4) += r1 + +post: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", + "r2.type=number", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +messages: + - "0: Invalid type (r2.type in {ctx, stack, packet, shared})" + - "0: Only pointers can be dereferenced (valid_access(r2.offset+4, width=4) for write)" +--- +test-case: atomic 64-bit CMPXCHG comparing against non-number + +pre: ["r0.type=number", "r0.svalue=2", "r0.uvalue=2", + "r1.type=shared", "r1.shared_region_size=16", "r1.shared_offset=4", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +code: + : | + lock *(u64 *)(r2 + 4) cx= r1 + +post: ["r0.type=number", "r0.svalue=2", "r0.uvalue=2", + "r1.type=shared", "r1.shared_region_size=16", "r1.shared_offset=4", + "r2.type=shared", "r2.shared_region_size=16", "r2.shared_offset=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + +messages: + - "0: Invalid type (r1.type == number)" From 856817629ba7d719bfb25ed11afd1654bec40284 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Mar 2024 16:40:29 +0000 Subject: [PATCH 055/373] Bump external/libbtf from `07e30bd` to `fdd2121` Bumps [external/libbtf](https://github.com/Alan-Jowett/libbtf) from `07e30bd` to `fdd2121`. - [Release notes](https://github.com/Alan-Jowett/libbtf/releases) - [Commits](https://github.com/Alan-Jowett/libbtf/compare/07e30bde8e12857694061dc2c6aea247d640615d...fdd21218051415d7a974ae4bc449aa5da6f7b998) --- updated-dependencies: - dependency-name: external/libbtf dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/libbtf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/libbtf b/external/libbtf index 07e30bde8..fdd212180 160000 --- a/external/libbtf +++ b/external/libbtf @@ -1 +1 @@ -Subproject commit 07e30bde8e12857694061dc2c6aea247d640615d +Subproject commit fdd21218051415d7a974ae4bc449aa5da6f7b998 From fbd98b0ea33e8537eddc8cd8c667bbde31ba25f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Mar 2024 16:40:27 +0000 Subject: [PATCH 056/373] Bump external/bpf_conformance from `6356e3e` to `22b6df4` Bumps [external/bpf_conformance](https://github.com/Alan-Jowett/bpf_conformance) from `6356e3e` to `22b6df4`. - [Release notes](https://github.com/Alan-Jowett/bpf_conformance/releases) - [Commits](https://github.com/Alan-Jowett/bpf_conformance/compare/6356e3ee03b2c1a70fd6839a85713974bab999b3...22b6df461372f4b736c6b042c836ef73323c8a3a) --- updated-dependencies: - dependency-name: external/bpf_conformance dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/bpf_conformance | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/bpf_conformance b/external/bpf_conformance index 6356e3ee0..22b6df461 160000 --- a/external/bpf_conformance +++ b/external/bpf_conformance @@ -1 +1 @@ -Subproject commit 6356e3ee03b2c1a70fd6839a85713974bab999b3 +Subproject commit 22b6df461372f4b736c6b042c836ef73323c8a3a From e7b9e90dff34b5c4d055c206341635fc2f0cf199 Mon Sep 17 00:00:00 2001 From: Jorge Navas Date: Fri, 7 Jan 2022 19:27:09 +0100 Subject: [PATCH 057/373] refactor: added stubs for a proof generator Author: Jorge Navas Date: Fri Jan 7 19:27:09 2022 +0100 --- src/crab_verifier.cpp | 59 +++++++++++++--------------------------- src/crab_verifier.hpp | 55 ++++++++++++++++++++++++++++++++++++- src/ebpf_proof.cpp | 25 +++++++++++++++++ src/ebpf_proof.hpp | 19 +++++++++++++ src/main/check.cpp | 17 +++++++++--- src/main/utils.hpp | 4 +-- src/test/test_verify.cpp | 6 ++-- 7 files changed, 135 insertions(+), 50 deletions(-) create mode 100644 src/ebpf_proof.cpp create mode 100644 src/ebpf_proof.hpp diff --git a/src/crab_verifier.cpp b/src/crab_verifier.cpp index 3d5d33f31..cdee1e9cf 100644 --- a/src/crab_verifier.cpp +++ b/src/crab_verifier.cpp @@ -26,36 +26,8 @@ using std::string; thread_local crab::lazy_allocator global_program_info; thread_local ebpf_verifier_options_t thread_local_options; -// Toy database to store invariants. -struct checks_db final { - std::map> m_db{}; - int total_warnings{}; - int total_unreachable{}; - crab::bound_t max_loop_count{crab::number_t{0}}; - - void add(const label_t& label, const std::string& msg) { m_db[label].emplace_back(msg); } - - void add_warning(const label_t& label, const std::string& msg) { - add(label, msg); - total_warnings++; - } - - void add_unreachable(const label_t& label, const std::string& msg) { - add(label, msg); - total_unreachable++; - } - - [[nodiscard]] int get_max_loop_count() const { - auto m = this->max_loop_count.number(); - if (m && m->fits_sint32()) - return m->cast_to_sint32(); - else - return std::numeric_limits::max(); - } - checks_db() = default; -}; - -static checks_db generate_report(cfg_t& cfg, crab::invariant_table_t& pre_invariants, +static checks_db generate_report(cfg_t& cfg, + crab::invariant_table_t& pre_invariants, crab::invariant_table_t& post_invariants) { checks_db m_db; for (const label_t& label : cfg.sorted_labels()) { @@ -142,8 +114,7 @@ static checks_db get_analysis_report(std::ostream& s, cfg_t& cfg, crab::invarian return db; } -static checks_db get_ebpf_report(std::ostream& s, cfg_t& cfg, program_info info, - const ebpf_verifier_options_t* options) { +crab_results get_ebpf_report(std::ostream& s, cfg_t& cfg, program_info info, const ebpf_verifier_options_t* options) { global_program_info = std::move(info); crab::domains::clear_global_state(); crab::variable_t::clear_thread_local_state(); @@ -152,13 +123,19 @@ static checks_db get_ebpf_report(std::ostream& s, cfg_t& cfg, program_info info, try { // Get dictionaries of pre-invariants and post-invariants for each basic block. ebpf_domain_t entry_dom = ebpf_domain_t::setup_entry(true); - auto [pre_invariants, post_invariants] = crab::run_forward_analyzer(cfg, std::move(entry_dom)); - return get_analysis_report(s, cfg, pre_invariants, post_invariants); + auto [pre_invariants, post_invariants] = + crab::run_forward_analyzer(cfg, std::move(entry_dom)); + return crab_results(std::move(cfg), + std::move(pre_invariants), std::move(post_invariants), + std::move(get_analysis_report(s, cfg, pre_invariants, post_invariants))); } catch (std::runtime_error& e) { // Convert verifier runtime_error exceptions to failure. checks_db db; db.add_warning(label_t::exit, e.what()); - return db; + crab::invariant_table_t pre_invariants, post_invariants; + return crab_results(std::move(cfg), + std::move(pre_invariants), std::move(post_invariants), + std::move(db)); } } @@ -167,7 +144,7 @@ bool run_ebpf_analysis(std::ostream& s, cfg_t& cfg, const program_info& info, co ebpf_verifier_stats_t* stats) { if (options == nullptr) options = &ebpf_verifier_default_options; - checks_db report = get_ebpf_report(s, cfg, info, options); + checks_db report = get_ebpf_report(s, cfg, info, options).db; if (stats) { stats->total_unreachable = report.total_unreachable; stats->total_warnings = report.total_warnings; @@ -208,8 +185,8 @@ std::tuple 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) { +crab_results 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; @@ -217,7 +194,8 @@ bool ebpf_verify_program(std::ostream& os, const InstructionSeq& prog, const pro // in a "passive", non-deterministic form. cfg_t cfg = prepare_cfg(prog, info, !options->no_simplify); - checks_db report = get_ebpf_report(os, cfg, info, options); + crab_results results = get_ebpf_report(os, cfg, info, options); + checks_db &report = results.db; if (options->print_failures) { print_report(os, report, prog, options->print_line_info); } @@ -226,7 +204,8 @@ bool ebpf_verify_program(std::ostream& os, const InstructionSeq& prog, const pro stats->total_warnings = report.total_warnings; stats->max_loop_count = report.get_max_loop_count(); } - return (report.total_warnings == 0); + //return (report.total_warnings == 0); + return results; } void ebpf_verifier_clear_thread_local_state() { diff --git a/src/crab_verifier.hpp b/src/crab_verifier.hpp index 1709104ae..1e64f8e79 100644 --- a/src/crab_verifier.hpp +++ b/src/crab_verifier.hpp @@ -4,13 +4,66 @@ #include "config.hpp" #include "crab/cfg.hpp" +#include "crab/fwd_analyzer.hpp" #include "spec_type_descriptors.hpp" #include "string_constraints.hpp" +#include +#include + +// Toy database to store invariants. +// Temporary moved here but it's better to hide it again in crab_verifier.cpp +struct checks_db final { + std::map> m_db{}; + int total_warnings{}; + int total_unreachable{}; + crab::bound_t max_loop_count{crab::number_t{0}}; + + void add(const label_t& label, const std::string& msg) { m_db[label].emplace_back(msg); } + + void add_warning(const label_t& label, const std::string& msg) { + add(label, msg); + total_warnings++; + } + + void add_unreachable(const label_t& label, const std::string& msg) { + add(label, msg); + total_unreachable++; + } + + [[nodiscard]] int get_max_loop_count() const { + auto m = this->max_loop_count.number(); + if (m && m->fits_sint32()) + return m->cast_to_sint32(); + else + return std::numeric_limits::max(); + } + checks_db() = default; +}; + +struct crab_results { + crab::cfg_t cfg; + crab::invariant_table_t pre_invariants; + crab::invariant_table_t post_invariants; + checks_db db; + + crab_results(cfg_t &&_cfg, + crab::invariant_table_t &&pre, crab::invariant_table_t &&post, + checks_db &&_db) + : cfg(std::move(_cfg)), + pre_invariants(std::move(pre)), + post_invariants(std::move(post)), + db(std::move(_db)) {} + + bool pass_verify() const { + return db.total_warnings == 0; + } +}; + bool run_ebpf_analysis(std::ostream& s, cfg_t& cfg, const program_info& info, const ebpf_verifier_options_t* options, ebpf_verifier_stats_t* stats); -bool ebpf_verify_program( +crab_results ebpf_verify_program( std::ostream& s, const InstructionSeq& prog, const program_info& info, diff --git a/src/ebpf_proof.cpp b/src/ebpf_proof.cpp new file mode 100644 index 000000000..f6f6ba2d0 --- /dev/null +++ b/src/ebpf_proof.cpp @@ -0,0 +1,25 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT +#include "ebpf_proof.hpp" + +bool ebpf_generate_proof(std::ostream& s, const InstructionSeq& prog, const program_info& info, + const ebpf_verifier_options_t* options, const crab_results& results) { + if (!results.pass_verify()) { + // If the program is not correct then we cannot generate a proof + return false; + } + + /* + The goal is to translate results.pre_invariants and + results.post_invariants into types from our type system. For that, + we will need first to define all the C++ types/classes needed for + defining our type system (for pointer, checked_pointer, etc), and + then the conversion. + + A type checker is not part of the proof but we should also + implement it so that we can be sure that our types are correct. + */ + + s << "TODO: proof\n"; + return false; +} diff --git a/src/ebpf_proof.hpp b/src/ebpf_proof.hpp new file mode 100644 index 000000000..65e479758 --- /dev/null +++ b/src/ebpf_proof.hpp @@ -0,0 +1,19 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT +#pragma once + +#include "config.hpp" +#include "crab/cfg.hpp" +#include "crab_verifier.hpp" + +// - prog is a prevail representation of the eBPF program +// - results contains the Crab CFG together with the inferred invariants +// +// Return true if proof is generated. +// +// TODO: we need to return also a proof object. We could take prog is +// a non-const reference and add the types (i.e., our proof) as debug +// symbols or metadata. However, note that InstructionSeq is a prevail +// thing. Ultimately, we want to annotate eBPF bytecode with types. +bool ebpf_generate_proof(std::ostream& s, const InstructionSeq& prog, const program_info& info, + const ebpf_verifier_options_t* options, const crab_results& results); diff --git a/src/main/check.cpp b/src/main/check.cpp index 887177c92..72ee1b2b8 100644 --- a/src/main/check.cpp +++ b/src/main/check.cpp @@ -8,6 +8,7 @@ #include "CLI11.hpp" #include "ebpf_verifier.hpp" +#include "ebpf_proof.hpp" #ifdef _WIN32 #include "memsize_windows.hpp" #else @@ -76,6 +77,9 @@ int main(int argc, char** argv) { app.add_flag("--line-info", ebpf_verifier_options.print_line_info, "Print line information"); app.add_flag("--print-btf-types", ebpf_verifier_options.dump_btf_types_json, "Print BTF types"); + bool gen_proof = false; + app.add_flag("--gen-proof", gen_proof, "Generate a proof if program is proven correct"); + std::string asmfile; app.add_option("--asm", asmfile, "Print disassembly to FILE")->type_name("FILE"); std::string dotfile; @@ -164,14 +168,19 @@ int main(int argc, char** argv) { if (domain == "zoneCrab") { ebpf_verifier_stats_t verifier_stats; - const auto [res, seconds] = timed_execution([&] { + auto [res, seconds] = timed_execution([&] { return ebpf_verify_program(std::cout, prog, raw_prog.info, &ebpf_verifier_options, &verifier_stats); }); - if (res && ebpf_verifier_options.check_termination && (ebpf_verifier_options.print_failures || ebpf_verifier_options.print_invariants)) { + if (res.pass_verify() && ebpf_verifier_options.check_termination && (ebpf_verifier_options.print_failures || ebpf_verifier_options.print_invariants)) { std::cout << "Program terminates within " << verifier_stats.max_loop_count << " loop iterations\n"; } - std::cout << res << "," << seconds << "," << resident_set_size_kb() << "\n"; - return !res; + std::cout << res.pass_verify() << "," << seconds << "," << resident_set_size_kb() << "\n"; + + if (gen_proof) { + ebpf_generate_proof(std::cout, prog, raw_prog.info, &ebpf_verifier_options, res); + } + + return !res.pass_verify(); } else if (domain == "linux") { // Pass the instruction sequence to the Linux kernel verifier. const auto [res, seconds] = bpf_verify_program(raw_prog.info.type, raw_prog.prog, &ebpf_verifier_options); diff --git a/src/main/utils.hpp b/src/main/utils.hpp index 97c05330e..4cefecb5b 100644 --- a/src/main/utils.hpp +++ b/src/main/utils.hpp @@ -5,10 +5,10 @@ template auto timed_execution(F f) { clock_t begin = clock(); - const auto& res = f(); + auto res = f(); clock_t end = clock(); double elapsed_secs = double(end - begin) / CLOCKS_PER_SEC; - return std::make_tuple(res, elapsed_secs); + return std::make_tuple(std::move(res), elapsed_secs); } diff --git a/src/test/test_verify.cpp b/src/test/test_verify.cpp index bca93d05b..32f90054e 100644 --- a/src/test/test_verify.cpp +++ b/src/test/test_verify.cpp @@ -40,11 +40,11 @@ FAIL_UNMARSHAL("invalid", "invalid-lddw.o", ".text") std::variant prog_or_error = unmarshal(raw_prog); \ REQUIRE(std::holds_alternative(prog_or_error)); \ auto& prog = std::get(prog_or_error); \ - bool res = ebpf_verify_program(std::cout, prog, raw_prog.info, options, nullptr); \ + crab_results res = ebpf_verify_program(std::cout, prog, raw_prog.info, options, nullptr); \ if (pass) \ - REQUIRE(res); \ + REQUIRE(res.pass_verify()); \ else \ - REQUIRE(!res); \ + REQUIRE(!res.pass_verify()); \ } while (0) #define TEST_SECTION(project, filename, section) \ From 2db3f30a4d016852968a6fc4d33e0bcfd74d4ca0 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 27 Jun 2023 20:08:28 -0400 Subject: [PATCH 058/373] refactor: remove unused ebpf_domain_t::operator== --- src/crab/ebpf_domain.cpp | 6 +++--- src/crab/ebpf_domain.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index a8c9e1f01..6d3cd51b7 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -814,9 +814,9 @@ bool ebpf_domain_t::is_top() const { return m_inv.is_top() && stack.is_top(); } bool ebpf_domain_t::operator<=(const ebpf_domain_t& other) { return m_inv <= other.m_inv && stack <= other.stack; } -bool ebpf_domain_t::operator==(const ebpf_domain_t& other) const { - return stack == other.stack && m_inv <= other.m_inv && other.m_inv <= m_inv; -} +// bool ebpf_domain_t::operator==(const ebpf_domain_t& other) const { +// return stack == other.stack && m_inv <= other.m_inv && other.m_inv <= m_inv; +// } void ebpf_domain_t::TypeDomain::add_extra_invariant(NumAbsDomain& dst, std::map& extra_invariants, diff --git a/src/crab/ebpf_domain.hpp b/src/crab/ebpf_domain.hpp index 39f11e73b..85f49b762 100644 --- a/src/crab/ebpf_domain.hpp +++ b/src/crab/ebpf_domain.hpp @@ -34,7 +34,7 @@ class ebpf_domain_t final { [[nodiscard]] bool is_bottom() const; [[nodiscard]] bool is_top() const; bool operator<=(const ebpf_domain_t& other); - bool operator==(const ebpf_domain_t& other) const; + //bool operator==(const ebpf_domain_t& other) const; void operator|=(ebpf_domain_t&& other); void operator|=(const ebpf_domain_t& other); ebpf_domain_t operator|(ebpf_domain_t&& other) const; From 82f48af9e35cada3f2778490a9482be0e5fc872c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 30 Mar 2024 16:19:14 +0000 Subject: [PATCH 059/373] Bump external/libbtf from `fdd2121` to `6e28ce2` Bumps [external/libbtf](https://github.com/Alan-Jowett/libbtf) from `fdd2121` to `6e28ce2`. - [Release notes](https://github.com/Alan-Jowett/libbtf/releases) - [Commits](https://github.com/Alan-Jowett/libbtf/compare/fdd21218051415d7a974ae4bc449aa5da6f7b998...6e28ce2813e6a9fe7d1448298ba058531dd81adb) --- updated-dependencies: - dependency-name: external/libbtf dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/libbtf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/libbtf b/external/libbtf index fdd212180..6e28ce281 160000 --- a/external/libbtf +++ b/external/libbtf @@ -1 +1 @@ -Subproject commit fdd21218051415d7a974ae4bc449aa5da6f7b998 +Subproject commit 6e28ce2813e6a9fe7d1448298ba058531dd81adb From ede97b4c803792f95bb664d0eae8870753b0256c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 30 Mar 2024 16:19:10 +0000 Subject: [PATCH 060/373] Bump external/bpf_conformance from `22b6df4` to `73b6ea3` Bumps [external/bpf_conformance](https://github.com/Alan-Jowett/bpf_conformance) from `22b6df4` to `73b6ea3`. - [Release notes](https://github.com/Alan-Jowett/bpf_conformance/releases) - [Commits](https://github.com/Alan-Jowett/bpf_conformance/compare/22b6df461372f4b736c6b042c836ef73323c8a3a...73b6ea325b8b48ebdb17f2382c25ec38183154f8) --- updated-dependencies: - dependency-name: external/bpf_conformance dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/bpf_conformance | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/bpf_conformance b/external/bpf_conformance index 22b6df461..73b6ea325 160000 --- a/external/bpf_conformance +++ b/external/bpf_conformance @@ -1 +1 @@ -Subproject commit 22b6df461372f4b736c6b042c836ef73323c8a3a +Subproject commit 73b6ea325b8b48ebdb17f2382c25ec38183154f8 From affae64922c7536a7f301740d47a2952e2f488e5 Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Sun, 31 Mar 2024 03:00:37 -0700 Subject: [PATCH 061/373] add support for conformance groups (#598) Per https://datatracker.ietf.org/doc/draft-ietf-bpf-isa/ --------- Signed-off-by: Dave Thaler --- src/asm_unmarshal.cpp | 75 +++++-- src/ebpf_yaml.cpp | 6 +- src/linux/linux_platform.cpp | 3 +- src/main/check.cpp | 70 ++++++- src/platform.hpp | 8 +- src/test/test_marshal.cpp | 391 +++++++++++++++++++---------------- src/test/test_verify.cpp | 2 +- 7 files changed, 350 insertions(+), 205 deletions(-) diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index 71232c936..d331b40d0 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -100,14 +100,21 @@ struct Unmarshaller { auto getAluOp(size_t pc, ebpf_inst inst) -> std::variant { // First handle instructions that support a non-zero offset. + bool is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64; switch (inst.opcode & INST_ALU_OP_MASK) { case INST_ALU_OP_DIV: + if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::divmul64 + : bpf_conformance_groups_t::divmul32)) + throw InvalidInstruction(pc, inst.opcode); switch (inst.offset) { case 0: return Bin::Op::UDIV; case 1: return Bin::Op::SDIV; default: throw InvalidInstruction(pc, make_opcode_message("invalid offset for", inst.opcode)); } case INST_ALU_OP_MOD: + if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::divmul64 + : bpf_conformance_groups_t::divmul32)) + throw InvalidInstruction(pc, inst.opcode); switch (inst.offset) { case 0: return Bin::Op::UMOD; case 1: return Bin::Op::SMOD; @@ -132,7 +139,11 @@ struct Unmarshaller { switch (inst.opcode & INST_ALU_OP_MASK) { case INST_ALU_OP_ADD: return Bin::Op::ADD; case INST_ALU_OP_SUB: return Bin::Op::SUB; - case INST_ALU_OP_MUL: return Bin::Op::MUL; + case INST_ALU_OP_MUL: + if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::divmul64 + : bpf_conformance_groups_t::divmul32)) + throw InvalidInstruction(pc, inst.opcode); + return Bin::Op::MUL; case INST_ALU_OP_OR: return Bin::Op::OR; case INST_ALU_OP_AND: return Bin::Op::AND; case INST_ALU_OP_LSH: return Bin::Op::LSH; @@ -158,16 +169,34 @@ struct Unmarshaller { if (inst.opcode & INST_END_BE) throw InvalidInstruction(pc, inst.opcode); switch (inst.imm) { - case 16: return Un::Op::SWAP16; - case 32: return Un::Op::SWAP32; - case 64: return Un::Op::SWAP64; + case 16: + if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) + throw InvalidInstruction(pc, inst.opcode); + return Un::Op::SWAP16; + case 32: + if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) + throw InvalidInstruction(pc, inst.opcode); + return Un::Op::SWAP32; + case 64: + if (!info.platform->supports_group(bpf_conformance_groups_t::base64)) + throw InvalidInstruction(pc, inst.opcode); + return Un::Op::SWAP64; default: throw InvalidInstruction(pc, "unsupported immediate"); } } switch (inst.imm) { - case 16: return (inst.opcode & INST_END_BE) ? Un::Op::BE16 : Un::Op::LE16; - case 32: return (inst.opcode & INST_END_BE) ? Un::Op::BE32 : Un::Op::LE32; - case 64: return (inst.opcode & INST_END_BE) ? Un::Op::BE64 : Un::Op::LE64; + case 16: + if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) + throw InvalidInstruction(pc, inst.opcode); + return (inst.opcode & INST_END_BE) ? Un::Op::BE16 : Un::Op::LE16; + case 32: + if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) + throw InvalidInstruction(pc, inst.opcode); + return (inst.opcode & INST_END_BE) ? Un::Op::BE32 : Un::Op::LE32; + case 64: + if (!info.platform->supports_group(bpf_conformance_groups_t::base64)) + throw InvalidInstruction(pc, inst.opcode); + return (inst.opcode & INST_END_BE) ? Un::Op::BE64 : Un::Op::LE64; default: throw InvalidInstruction(pc, "unsupported immediate"); } @@ -237,12 +266,16 @@ struct Unmarshaller { throw InvalidInstruction(pc, "bad register"); int width = getMemWidth(inst.opcode); + if (!info.platform->supports_group((width == sizeof(uint64_t)) ? bpf_conformance_groups_t::base64 : bpf_conformance_groups_t::base32)) + throw InvalidInstruction(pc, inst.opcode); bool isLD = (inst.opcode & INST_CLS_MASK) == INST_CLS_LD; switch (inst.opcode & INST_MODE_MASK) { case INST_MODE_IMM: throw InvalidInstruction(pc, inst.opcode); + case INST_MODE_ABS: - if (!info.platform->legacy || !isLD || (width == 8)) + if (!info.platform->supports_group(bpf_conformance_groups_t::packet) || + !isLD || (width == 8)) throw InvalidInstruction(pc, inst.opcode); if (inst.dst != 0) throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode)); @@ -253,7 +286,8 @@ struct Unmarshaller { return Packet{.width = width, .offset = inst.imm, .regoffset = {}}; case INST_MODE_IND: - if (!info.platform->legacy || !isLD || (width == 8)) + if (!info.platform->supports_group(bpf_conformance_groups_t::packet) || + !isLD || (width == 8)) throw InvalidInstruction(pc, inst.opcode); if (inst.dst != 0) throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode)); @@ -300,6 +334,8 @@ struct Unmarshaller { ((inst.opcode & INST_SIZE_MASK) != INST_SIZE_W && (inst.opcode & INST_SIZE_MASK) != INST_SIZE_DW)) throw InvalidInstruction(pc, inst.opcode); + if (!info.platform->supports_group(((inst.opcode & INST_SIZE_MASK) == INST_SIZE_DW) ? bpf_conformance_groups_t::atomic64 : bpf_conformance_groups_t::atomic32)) + throw InvalidInstruction(pc, inst.opcode); return Atomic{ .op = getAtomicOp(pc, inst), .fetch = (inst.imm & INST_FETCH) == INST_FETCH, @@ -317,11 +353,13 @@ struct Unmarshaller { } auto makeAluOp(size_t pc, ebpf_inst inst) -> Instruction { + bool is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64; + if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::base64 : bpf_conformance_groups_t::base32)) + throw InvalidInstruction(pc, inst.opcode); if (inst.dst == R10_STACK_POINTER) throw InvalidInstruction(pc, "invalid target r10"); if (inst.dst > R10_STACK_POINTER || inst.src > R10_STACK_POINTER) throw InvalidInstruction(pc, "bad register"); - bool is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_ALU64; return std::visit(overloaded{[&](Un::Op op) -> Instruction { return Un{.op = op, .dst = Reg{inst.dst}, .is64 = is64}; }, [&](Bin::Op op) -> Instruction { Bin res{ @@ -339,6 +377,8 @@ struct Unmarshaller { } auto makeLddw(ebpf_inst inst, int32_t next_imm, const vector& insts, pc_t pc) -> Instruction { + if (!info.platform->supports_group(bpf_conformance_groups_t::base64)) + throw InvalidInstruction{pc, inst.opcode}; if (pc >= insts.size() - 1) throw InvalidInstruction(pc, "incomplete lddw"); ebpf_inst next = insts[pc + 1]; @@ -458,7 +498,10 @@ struct Unmarshaller { case INST_CALL: if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP) throw InvalidInstruction(pc, inst.opcode); - if (!info.platform->callx && (inst.opcode & INST_SRC_REG)) + if (!info.platform->supports_group(bpf_conformance_groups_t::callx) && + (inst.opcode & INST_SRC_REG)) + throw InvalidInstruction(pc, inst.opcode); + if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) throw InvalidInstruction(pc, inst.opcode); if (inst.src > 0) throw InvalidInstruction(pc, inst.opcode); @@ -472,6 +515,8 @@ struct Unmarshaller { throw InvalidInstruction(pc, "invalid helper function id " + std::to_string(inst.imm)); return makeCall(inst.imm); case INST_EXIT: + if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) + throw InvalidInstruction(pc, inst.opcode); if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP || (inst.opcode & INST_SRC_REG)) throw InvalidInstruction(pc, inst.opcode); if (inst.src != 0) @@ -484,8 +529,9 @@ struct Unmarshaller { throw InvalidInstruction(pc, make_opcode_message("nonzero offset for", inst.opcode)); return Exit{}; case INST_JA: - if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP && - (inst.opcode & INST_CLS_MASK) != INST_CLS_JMP32) + if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP && (inst.opcode & INST_CLS_MASK) != INST_CLS_JMP32) + throw InvalidInstruction(pc, inst.opcode); + if (!info.platform->supports_group(bpf_conformance_groups_t::base32)) throw InvalidInstruction(pc, inst.opcode); if (inst.opcode & INST_SRC_REG) throw InvalidInstruction(pc, inst.opcode); @@ -497,6 +543,9 @@ struct Unmarshaller { throw InvalidInstruction(pc, make_opcode_message("nonzero dst for register", inst.opcode)); default: { // First validate the opcode, src, and imm. + auto is64 = (inst.opcode & INST_CLS_MASK) == INST_CLS_JMP; + if (!info.platform->supports_group(is64 ? bpf_conformance_groups_t::base64 : bpf_conformance_groups_t::base32)) + throw InvalidInstruction(pc, inst.opcode); auto op = getJmpOp(pc, inst.opcode); if (!(inst.opcode & INST_SRC_REG) && (inst.src != 0)) throw InvalidInstruction(pc, inst.opcode); diff --git a/src/ebpf_yaml.cpp b/src/ebpf_yaml.cpp index dee8d9e3b..50378f5a8 100644 --- a/src/ebpf_yaml.cpp +++ b/src/ebpf_yaml.cpp @@ -63,8 +63,8 @@ ebpf_platform_t g_platform_test = { .parse_maps_section = ebpf_parse_maps_section, .get_map_descriptor = ebpf_get_map_descriptor, .get_map_type = ebpf_get_map_type, - .legacy = true, - .callx = true + .supported_conformance_groups = + bpf_conformance_groups_t::default_groups | bpf_conformance_groups_t::packet | bpf_conformance_groups_t::callx }; static EbpfProgramType make_program_type(const string& name, ebpf_context_descriptor_t* context_descriptor) { @@ -328,7 +328,7 @@ ConformanceTestResult run_conformance_test_case(const std::vector& memo } raw_program raw_prog{.prog = insts}; ebpf_platform_t platform = g_ebpf_platform_linux; - platform.callx = true; + platform.supported_conformance_groups |= bpf_conformance_groups_t::callx; raw_prog.info.platform = &platform; // Convert the raw program section to a set of instructions. diff --git a/src/linux/linux_platform.cpp b/src/linux/linux_platform.cpp index a57ccc16b..01cd9477c 100644 --- a/src/linux/linux_platform.cpp +++ b/src/linux/linux_platform.cpp @@ -255,6 +255,5 @@ const ebpf_platform_t g_ebpf_platform_linux = { get_map_descriptor_linux, get_map_type_linux, resolve_inner_map_references_linux, - true, // Legacy packet access instructions - false // No callx instructions + bpf_conformance_groups_t::default_groups | bpf_conformance_groups_t::packet }; diff --git a/src/main/check.cpp b/src/main/check.cpp index 887177c92..902eb3b96 100644 --- a/src/main/check.cpp +++ b/src/main/check.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include "CLI11.hpp" @@ -32,6 +33,42 @@ struct at_scope_exit ~at_scope_exit() { on_exit(); } }; +static const std::map _conformance_groups = { + {"atomic32", bpf_conformance_groups_t::atomic32}, + {"atomic64", bpf_conformance_groups_t::atomic64}, + {"base32", bpf_conformance_groups_t::base32}, + {"base64", bpf_conformance_groups_t::base64}, + {"callx", bpf_conformance_groups_t::callx}, + {"divmul32", bpf_conformance_groups_t::divmul32}, + {"divmul64", bpf_conformance_groups_t::divmul64}, + {"packet", bpf_conformance_groups_t::packet}}; + +static std::optional _get_conformance_group_by_name(std::string group) { + if (!_conformance_groups.contains(group)) { + return {}; + } + return _conformance_groups.find(group)->second; +} + +static std::string _get_conformance_group_names() { + std::string result; + for (const auto& entry : _conformance_groups) { + if (!result.empty()) { + result += ", "; + } + result += entry.first; + } + return result; +} + +// Given a string containing comma-separated tokens, split them into a list of strings. +static std::vector get_string_vector(std::string list) { + std::vector string_vector; + if (!list.empty()) + boost::split(string_vector, list, boost::is_any_of(",")); + return string_vector; +} + int main(int argc, char** argv) { // Always call ebpf_verifier_clear_thread_local_state on scope exit. at_scope_exit clear_thread_local_state; @@ -66,10 +103,12 @@ int main(int argc, char** argv) { app.add_flag("-f", ebpf_verifier_options.print_failures, "Print verifier's failure logs"); app.add_flag("-s", ebpf_verifier_options.strict, "Apply additional checks that would cause runtime failures"); app.add_flag("-v", verbose, "Print both invariants and failures"); - bool legacy = false; - app.add_flag("--legacy", legacy, "Allow deprecated packet access instructions"); - bool callx = false; - app.add_flag("--callx", callx, "Allow callx instructions"); + std::string include_groups; + app.add_option("include_groups", include_groups, + "Include conformance groups (valid group names: " + _get_conformance_group_names() + ")"); + std::string exclude_groups; + app.add_option("exclude_groups", exclude_groups, + "Exclude conformance groups (valid group names: " + _get_conformance_group_names() + ")"); bool no_division_by_zero = false; app.add_flag("--no-division-by-zero", no_division_by_zero, "Do not allow division by zero"); app.add_flag("--no-simplify", ebpf_verifier_options.no_simplify, "Do not simplify"); @@ -88,6 +127,26 @@ int main(int argc, char** argv) { ebpf_verifier_options.print_invariants = ebpf_verifier_options.print_failures = true; ebpf_verifier_options.allow_division_by_zero = !no_division_by_zero; + // Enable default conformance groups, which don't include callx or packet. + ebpf_platform_t platform = g_ebpf_platform_linux; + platform.supported_conformance_groups = bpf_conformance_groups_t::default_groups; + for (auto group_name : get_string_vector(include_groups)) { + if (auto group = _get_conformance_group_by_name(group_name)) { + platform.supported_conformance_groups |= *group; + } else { + std::cerr << "Invalid group: " << group_name << std::endl; + return 1; + } + } + for (auto group_name : get_string_vector(exclude_groups)) { + if (auto group = _get_conformance_group_by_name(group_name)) { + platform.supported_conformance_groups &= ~(*group); + } else { + std::cerr << "Invalid group: " << group_name << std::endl; + return 1; + } + } + // Main program if (filename == "@headers") { @@ -115,9 +174,6 @@ int main(int argc, char** argv) { if (domain == "linux") ebpf_verifier_options.mock_map_fds = false; - ebpf_platform_t platform = g_ebpf_platform_linux; - platform.legacy = legacy; - platform.callx = callx; // Read a set of raw program sections from an ELF file. vector raw_progs; diff --git a/src/platform.hpp b/src/platform.hpp index d3635bfcd..ab4ddcda5 100644 --- a/src/platform.hpp +++ b/src/platform.hpp @@ -6,6 +6,7 @@ // that supports eBPF can have an ebpf_platform_t struct that the verifier // can use to call platform-specific functions. +#include "../external/bpf_conformance/include/bpf_conformance.h" #include "config.hpp" #include "spec_type_descriptors.hpp" #include "helpers.hpp" @@ -42,10 +43,11 @@ struct ebpf_platform_t { ebpf_get_map_descriptor_fn get_map_descriptor; ebpf_get_map_type_fn get_map_type; ebpf_resolve_inner_map_references_fn resolve_inner_map_references; + bpf_conformance_groups_t supported_conformance_groups; - // Fields indicating support for various instruction types. - bool legacy; - bool callx; + bool supports_group(bpf_conformance_groups_t group) const { + return (supported_conformance_groups & group) == group; + } }; extern const ebpf_platform_t g_ebpf_platform_linux; diff --git a/src/test/test_marshal.cpp b/src/test/test_marshal.cpp index 02ee4c4ff..06508d25a 100644 --- a/src/test/test_marshal.cpp +++ b/src/test/test_marshal.cpp @@ -5,7 +5,6 @@ #include "asm_ostream.hpp" #include "asm_marshal.hpp" #include "asm_unmarshal.hpp" -#include "../external/bpf_conformance/include/bpf_conformance.h" // Below we define a tample of instruction templates that specify // what values each field are allowed to contain. We first define @@ -17,6 +16,7 @@ constexpr int MEM_OFFSET = 3; // Any valid memory offset value. constexpr int JMP_OFFSET = 5; // Any valid jump offset value. constexpr int DST = 7; // Any destination register number. +constexpr int HELPER_ID = 8; // Any helper ID. constexpr int SRC = 9; // Any source register number. constexpr int IMM = -1; // Any imm value. constexpr int INVALID_REGISTER = R10_STACK_POINTER + 1; // Not a valid register. @@ -29,180 +29,196 @@ struct ebpf_instruction_template_t { // The following table is derived from the table in the Appendix of the // BPF ISA specification (https://datatracker.ietf.org/doc/draft-ietf-bpf-isa/). static const ebpf_instruction_template_t instruction_template[] = { - // opcode, dst, src, offset, imm. - {0x04, DST, 0, 0, IMM}, - {0x05, 0, 0, JMP_OFFSET, 0}, - {0x06, 0, 0, 0, JMP_OFFSET}, - {0x07, DST, 0, 0, IMM}, - {0x0c, DST, SRC, 0, 0}, - {0x0f, DST, SRC, 0, 0}, - {0x14, DST, 0, 0, IMM}, - {0x15, DST, 0, JMP_OFFSET, IMM}, - {0x16, DST, 0, JMP_OFFSET, IMM}, - {0x17, DST, 0, 0, IMM}, - {0x18, DST, 0, 0, IMM}, - {0x18, DST, 1, 0, IMM}, + // {opcode, dst, src, offset, imm}, group + {{0x04, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32}, + {{0x05, 0, 0, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32}, + {{0x06, 0, 0, 0, JMP_OFFSET}, bpf_conformance_groups_t::base32}, + {{0x07, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64}, + {{0x0c, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32}, + {{0x0f, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64}, + {{0x14, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32}, + {{0x15, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64}, + {{0x16, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32}, + {{0x17, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64}, + {{0x18, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64}, + {{0x18, DST, 1, 0, IMM}, bpf_conformance_groups_t::base64}, // TODO(issue #533): add support for LDDW with src_reg > 1. - // {0x18, DST, 2, 0, IMM}, - // {0x18, DST, 3, 0, IMM}, - // {0x18, DST, 4, 0, IMM}, - // {0x18, DST, 5, 0, IMM}, - // {0x18, DST, 6, 0, IMM}, - {0x1c, DST, SRC, 0, 0}, - {0x1d, DST, SRC, JMP_OFFSET, 0}, - {0x1e, DST, SRC, JMP_OFFSET, 0}, - {0x1f, DST, SRC, 0, 0}, - {0x20, 0, 0, 0, IMM}, - {0x24, DST, 0, 0, IMM}, - {0x25, DST, 0, JMP_OFFSET, IMM}, - {0x26, DST, 0, JMP_OFFSET, IMM}, - {0x27, DST, 0, 0, IMM}, - {0x28, 0, 0, 0, IMM}, - {0x2c, DST, SRC, 0, 0}, - {0x2d, DST, SRC, JMP_OFFSET, 0}, - {0x2e, DST, SRC, JMP_OFFSET, 0}, - {0x2f, DST, SRC, 0, 0}, - {0x30, 0, 0, 0, IMM}, - {0x34, DST, 0, 0, IMM}, - {0x34, DST, 0, 1, IMM}, - {0x35, DST, 0, JMP_OFFSET, IMM}, - {0x36, DST, 0, JMP_OFFSET, IMM}, - {0x37, DST, 0, 0, IMM}, - {0x37, DST, 0, 1, IMM}, - {0x3c, DST, SRC, 0, 0}, - {0x3c, DST, SRC, 1, 0}, - {0x3d, DST, SRC, JMP_OFFSET, 0}, - {0x3e, DST, SRC, JMP_OFFSET, 0}, - {0x3f, DST, SRC, 0, 0}, - {0x3f, DST, SRC, 1, 0}, - {0x40, 0, SRC, 0, IMM}, - {0x44, DST, 0, 0, IMM}, - {0x45, DST, 0, JMP_OFFSET, IMM}, - {0x46, DST, 0, JMP_OFFSET, IMM}, - {0x47, DST, 0, 0, IMM}, - {0x48, 0, SRC, 0, IMM}, - {0x4c, DST, SRC, 0, 0}, - {0x4d, DST, SRC, JMP_OFFSET, 0}, - {0x4e, DST, SRC, JMP_OFFSET, 0}, - {0x4f, DST, SRC, 0, 0}, - {0x50, 0, SRC, 0, IMM}, - {0x54, DST, 0, 0, IMM}, - {0x55, DST, 0, JMP_OFFSET, IMM}, - {0x56, DST, 0, JMP_OFFSET, IMM}, - {0x57, DST, 0, 0, IMM}, - {0x5c, DST, SRC, 0, 0}, - {0x5d, DST, SRC, JMP_OFFSET, 0}, - {0x5e, DST, SRC, JMP_OFFSET, 0}, - {0x5f, DST, SRC, 0, 0}, - {0x61, DST, SRC, MEM_OFFSET, 0}, - {0x62, DST, 0, MEM_OFFSET, IMM}, - {0x63, DST, SRC, MEM_OFFSET, 0}, - {0x64, DST, 0, 0, IMM}, - {0x65, DST, 0, JMP_OFFSET, IMM}, - {0x66, DST, 0, JMP_OFFSET, IMM}, - {0x67, DST, 0, 0, IMM}, - {0x69, DST, SRC, MEM_OFFSET, 0}, - {0x6a, DST, 0, MEM_OFFSET, IMM}, - {0x6b, DST, SRC, MEM_OFFSET, 0}, - {0x6c, DST, SRC, 0, 0}, - {0x6d, DST, SRC, JMP_OFFSET, 0}, - {0x6e, DST, SRC, JMP_OFFSET, 0}, - {0x6f, DST, SRC, 0, 0}, - {0x71, DST, SRC, MEM_OFFSET, 0}, - {0x72, DST, 0, MEM_OFFSET, IMM}, - {0x73, DST, SRC, MEM_OFFSET, 0}, - {0x74, DST, 0, 0, IMM}, - {0x75, DST, 0, JMP_OFFSET, IMM}, - {0x76, DST, 0, JMP_OFFSET, IMM}, - {0x77, DST, 0, 0, IMM}, - {0x79, DST, SRC, MEM_OFFSET, 0}, - {0x7a, DST, 0, MEM_OFFSET, IMM}, - {0x7b, DST, SRC, MEM_OFFSET, 0}, - {0x7c, DST, SRC, 0, 0}, - {0x7d, DST, SRC, JMP_OFFSET, 0}, - {0x7e, DST, SRC, JMP_OFFSET, 0}, - {0x7f, DST, SRC, 0, 0}, - {0x84, DST, 0, 0, 0}, - {0x85, 0, 0, 0, IMM}, + // {{0x18, DST, 2, 0, IMM}, bpf_conformance_groups_t::base64}, + // {{0x18, DST, 3, 0, IMM}, bpf_conformance_groups_t::base64}, + // {{0x18, DST, 4, 0, IMM}, bpf_conformance_groups_t::base64}, + // {{0x18, DST, 5, 0, IMM}, bpf_conformance_groups_t::base64}, + // {{0x18, DST, 6, 0, IMM}, bpf_conformance_groups_t::base64}, + {{0x1c, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32}, + {{0x1d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64}, + {{0x1e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32}, + {{0x1f, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64}, + {{0x20, 0, 0, 0, IMM}, bpf_conformance_groups_t::packet}, + {{0x24, DST, 0, 0, IMM}, bpf_conformance_groups_t::divmul32}, + {{0x25, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64}, + {{0x26, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32}, + {{0x27, DST, 0, 0, IMM}, bpf_conformance_groups_t::divmul64}, + {{0x28, 0, 0, 0, IMM}, bpf_conformance_groups_t::packet}, + {{0x2c, DST, SRC, 0, 0}, bpf_conformance_groups_t::divmul32}, + {{0x2d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64}, + {{0x2e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32}, + {{0x2f, DST, SRC, 0, 0}, bpf_conformance_groups_t::divmul64}, + {{0x30, 0, 0, 0, IMM}, bpf_conformance_groups_t::packet}, + {{0x34, DST, 0, 0, IMM}, bpf_conformance_groups_t::divmul32}, + {{0x34, DST, 0, 1, IMM}, bpf_conformance_groups_t::divmul32}, + {{0x35, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64}, + {{0x36, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32}, + {{0x37, DST, 0, 0, IMM}, bpf_conformance_groups_t::divmul64}, + {{0x37, DST, 0, 1, IMM}, bpf_conformance_groups_t::divmul64}, + {{0x3c, DST, SRC, 0, 0}, bpf_conformance_groups_t::divmul32}, + {{0x3c, DST, SRC, 1, 0}, bpf_conformance_groups_t::divmul32}, + {{0x3d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64}, + {{0x3e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32}, + {{0x3f, DST, SRC, 0, 0}, bpf_conformance_groups_t::divmul64}, + {{0x3f, DST, SRC, 1, 0}, bpf_conformance_groups_t::divmul64}, + {{0x40, 0, SRC, 0, IMM}, bpf_conformance_groups_t::packet}, + {{0x44, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32}, + {{0x45, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64}, + {{0x46, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32}, + {{0x47, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64}, + {{0x48, 0, SRC, 0, IMM}, bpf_conformance_groups_t::packet}, + {{0x4c, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32}, + {{0x4d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64}, + {{0x4e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32}, + {{0x4f, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64}, + {{0x50, 0, SRC, 0, IMM}, bpf_conformance_groups_t::packet}, + {{0x54, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32}, + {{0x55, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64}, + {{0x56, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32}, + {{0x57, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64}, + {{0x5c, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32}, + {{0x5d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64}, + {{0x5e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32}, + {{0x5f, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64}, + {{0x61, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base32}, + {{0x62, DST, 0, MEM_OFFSET, IMM}, bpf_conformance_groups_t::base32}, + {{0x63, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base32}, + {{0x64, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32}, + {{0x65, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64}, + {{0x66, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32}, + {{0x67, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64}, + {{0x69, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base32}, + {{0x6a, DST, 0, MEM_OFFSET, IMM}, bpf_conformance_groups_t::base32}, + {{0x6b, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base32}, + {{0x6c, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32}, + {{0x6d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64}, + {{0x6e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32}, + {{0x6f, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64}, + {{0x71, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base32}, + {{0x72, DST, 0, MEM_OFFSET, IMM}, bpf_conformance_groups_t::base32}, + {{0x73, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base32}, + {{0x74, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32}, + {{0x75, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64}, + {{0x76, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32}, + {{0x77, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64}, + {{0x79, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base64}, + {{0x7a, DST, 0, MEM_OFFSET, IMM}, bpf_conformance_groups_t::base64}, + {{0x7b, DST, SRC, MEM_OFFSET, 0}, bpf_conformance_groups_t::base64}, + {{0x7c, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32}, + {{0x7d, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64}, + {{0x7e, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32}, + {{0x7f, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64}, + {{0x84, DST, 0, 0, 0}, bpf_conformance_groups_t::base32}, + {{0x85, 0, 0, 0, HELPER_ID}, bpf_conformance_groups_t::base32}, // TODO(issue #582): Add support for subprograms (call_local). - // {0x85, 0, 1, 0, IMM}, + // {{0x85, 0, 1, 0, IMM}, bpf_conformance_groups_t::base32}, // TODO(issue #590): Add support for calling a helper function by BTF ID. - // {0x85, 0, 2, 0, IMM}, - {0x87, DST, 0, 0, 0}, + // {{0x85, 0, 2, 0, IMM}, bpf_conformance_groups_t::base32}, + {{0x87, DST, 0, 0, 0}, bpf_conformance_groups_t::base64}, {{0x8d, DST, 0, 0, 0}, bpf_conformance_groups_t::callx}, - {0x94, DST, 0, 0, IMM}, - {0x94, DST, 0, 1, IMM}, - {0x95, 0, 0, 0, 0}, - {0x97, DST, 0, 0, IMM}, - {0x97, DST, 0, 1, IMM}, - {0x9c, DST, SRC, 0, 0}, - {0x9c, DST, SRC, 1, 0}, - {0x9f, DST, SRC, 0, 0}, - {0x9f, DST, SRC, 1, 0}, - {0xa4, DST, 0, 0, IMM}, - {0xa5, DST, 0, JMP_OFFSET, IMM}, - {0xa6, DST, 0, JMP_OFFSET, IMM}, - {0xa7, DST, 0, 0, IMM}, - {0xac, DST, SRC, 0, 0}, - {0xad, DST, SRC, JMP_OFFSET, 0}, - {0xae, DST, SRC, JMP_OFFSET, 0}, - {0xaf, DST, SRC, 0, 0}, - {0xb4, DST, 0, 0, IMM}, - {0xb5, DST, 0, JMP_OFFSET, IMM}, - {0xb6, DST, 0, JMP_OFFSET, IMM}, - {0xb7, DST, 0, 0, IMM}, - {0xbc, DST, SRC, 0, 0}, - {0xbc, DST, SRC, 8, 0}, - {0xbc, DST, SRC, 16, 0}, - {0xbd, DST, SRC, JMP_OFFSET, 0}, - {0xbe, DST, SRC, JMP_OFFSET, 0}, - {0xbf, DST, SRC, 0, 0}, - {0xbf, DST, SRC, 8, 0}, - {0xbf, DST, SRC, 16, 0}, - {0xbf, DST, SRC, 32, 0}, - {0xc3, DST, SRC, MEM_OFFSET, 0x00}, - {0xc3, DST, SRC, MEM_OFFSET, 0x01}, - {0xc3, DST, SRC, MEM_OFFSET, 0x40}, - {0xc3, DST, SRC, MEM_OFFSET, 0x41}, - {0xc3, DST, SRC, MEM_OFFSET, 0x50}, - {0xc3, DST, SRC, MEM_OFFSET, 0x51}, - {0xc3, DST, SRC, MEM_OFFSET, 0xa0}, - {0xc3, DST, SRC, MEM_OFFSET, 0xa1}, - {0xc3, DST, SRC, MEM_OFFSET, 0xe1}, - {0xc3, DST, SRC, MEM_OFFSET, 0xf1}, - {0xc4, DST, 0, 0, IMM}, - {0xc5, DST, 0, JMP_OFFSET, IMM}, - {0xc6, DST, 0, JMP_OFFSET, IMM}, - {0xc7, DST, 0, 0, IMM}, - {0xcc, DST, SRC, 0, 0}, - {0xcd, DST, SRC, JMP_OFFSET, 0}, - {0xce, DST, SRC, JMP_OFFSET, 0}, - {0xcf, DST, SRC, 0, 0}, - {0xd4, DST, 0, 0, 0x10}, - {0xd4, DST, 0, 0, 0x20}, - {0xd4, DST, 0, 0, 0x40}, - {0xd5, DST, 0, JMP_OFFSET, IMM}, - {0xd6, DST, 0, JMP_OFFSET, IMM}, - {0xd7, DST, 0, 0, 0x10}, - {0xd7, DST, 0, 0, 0x20}, - {0xd7, DST, 0, 0, 0x40}, - {0xdb, DST, SRC, MEM_OFFSET, 0x00}, - {0xdb, DST, SRC, MEM_OFFSET, 0x01}, - {0xdb, DST, SRC, MEM_OFFSET, 0x40}, - {0xdb, DST, SRC, MEM_OFFSET, 0x41}, - {0xdb, DST, SRC, MEM_OFFSET, 0x50}, - {0xdb, DST, SRC, MEM_OFFSET, 0x51}, - {0xdb, DST, SRC, MEM_OFFSET, 0xa0}, - {0xdb, DST, SRC, MEM_OFFSET, 0xa1}, - {0xdb, DST, SRC, MEM_OFFSET, 0xe1}, - {0xdb, DST, SRC, MEM_OFFSET, 0xf1}, - {0xdc, DST, 0, 0, 0x10}, - {0xdc, DST, 0, 0, 0x20}, - {0xdc, DST, 0, 0, 0x40}, - {0xdd, DST, SRC, JMP_OFFSET, 0}, - {0xde, DST, SRC, JMP_OFFSET, 0}, + {{0x94, DST, 0, 0, IMM}, bpf_conformance_groups_t::divmul32}, + {{0x94, DST, 0, 1, IMM}, bpf_conformance_groups_t::divmul32}, + {{0x95, 0, 0, 0, 0}, bpf_conformance_groups_t::base32}, + {{0x97, DST, 0, 0, IMM}, bpf_conformance_groups_t::divmul64}, + {{0x97, DST, 0, 1, IMM}, bpf_conformance_groups_t::divmul64}, + {{0x9c, DST, SRC, 0, 0}, bpf_conformance_groups_t::divmul32}, + {{0x9c, DST, SRC, 1, 0}, bpf_conformance_groups_t::divmul32}, + {{0x9f, DST, SRC, 0, 0}, bpf_conformance_groups_t::divmul64}, + {{0x9f, DST, SRC, 1, 0}, bpf_conformance_groups_t::divmul64}, + {{0xa4, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32}, + {{0xa5, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64}, + {{0xa6, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32}, + {{0xa7, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64}, + {{0xac, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32}, + {{0xad, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64}, + {{0xae, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32}, + {{0xaf, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64}, + {{0xb4, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32}, + {{0xb5, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64}, + {{0xb6, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32}, + {{0xb7, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64}, + {{0xbc, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32}, + {{0xbc, DST, SRC, 8, 0}, bpf_conformance_groups_t::base32}, + {{0xbc, DST, SRC, 16, 0}, bpf_conformance_groups_t::base32}, + {{0xbd, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64}, + {{0xbe, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32}, + {{0xbf, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64}, + {{0xbf, DST, SRC, 8, 0}, bpf_conformance_groups_t::base64}, + {{0xbf, DST, SRC, 16, 0}, bpf_conformance_groups_t::base64}, + {{0xbf, DST, SRC, 32, 0}, bpf_conformance_groups_t::base64}, + {{0xc3, DST, SRC, MEM_OFFSET, 0x00}, bpf_conformance_groups_t::atomic32}, + {{0xc3, DST, SRC, MEM_OFFSET, 0x01}, bpf_conformance_groups_t::atomic32}, + {{0xc3, DST, SRC, MEM_OFFSET, 0x40}, bpf_conformance_groups_t::atomic32}, + {{0xc3, DST, SRC, MEM_OFFSET, 0x41}, bpf_conformance_groups_t::atomic32}, + {{0xc3, DST, SRC, MEM_OFFSET, 0x50}, bpf_conformance_groups_t::atomic32}, + {{0xc3, DST, SRC, MEM_OFFSET, 0x51}, bpf_conformance_groups_t::atomic32}, + {{0xc3, DST, SRC, MEM_OFFSET, 0xa0}, bpf_conformance_groups_t::atomic32}, + {{0xc3, DST, SRC, MEM_OFFSET, 0xa1}, bpf_conformance_groups_t::atomic32}, + {{0xc3, DST, SRC, MEM_OFFSET, 0xe1}, bpf_conformance_groups_t::atomic32}, + {{0xc3, DST, SRC, MEM_OFFSET, 0xf1}, bpf_conformance_groups_t::atomic32}, + {{0xc4, DST, 0, 0, IMM}, bpf_conformance_groups_t::base32}, + {{0xc5, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64}, + {{0xc6, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32}, + {{0xc7, DST, 0, 0, IMM}, bpf_conformance_groups_t::base64}, + {{0xcc, DST, SRC, 0, 0}, bpf_conformance_groups_t::base32}, + {{0xcd, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64}, + {{0xce, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32}, + {{0xcf, DST, SRC, 0, 0}, bpf_conformance_groups_t::base64}, + {{0xd4, DST, 0, 0, 0x10}, bpf_conformance_groups_t::base32}, + {{0xd4, DST, 0, 0, 0x20}, bpf_conformance_groups_t::base32}, + {{0xd4, DST, 0, 0, 0x40}, bpf_conformance_groups_t::base64}, + {{0xd5, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base64}, + {{0xd6, DST, 0, JMP_OFFSET, IMM}, bpf_conformance_groups_t::base32}, + {{0xd7, DST, 0, 0, 0x10}, bpf_conformance_groups_t::base32}, + {{0xd7, DST, 0, 0, 0x20}, bpf_conformance_groups_t::base32}, + {{0xd7, DST, 0, 0, 0x40}, bpf_conformance_groups_t::base64}, + {{0xdb, DST, SRC, MEM_OFFSET, 0x00}, bpf_conformance_groups_t::atomic64}, + {{0xdb, DST, SRC, MEM_OFFSET, 0x01}, bpf_conformance_groups_t::atomic64}, + {{0xdb, DST, SRC, MEM_OFFSET, 0x40}, bpf_conformance_groups_t::atomic64}, + {{0xdb, DST, SRC, MEM_OFFSET, 0x41}, bpf_conformance_groups_t::atomic64}, + {{0xdb, DST, SRC, MEM_OFFSET, 0x50}, bpf_conformance_groups_t::atomic64}, + {{0xdb, DST, SRC, MEM_OFFSET, 0x51}, bpf_conformance_groups_t::atomic64}, + {{0xdb, DST, SRC, MEM_OFFSET, 0xa0}, bpf_conformance_groups_t::atomic64}, + {{0xdb, DST, SRC, MEM_OFFSET, 0xa1}, bpf_conformance_groups_t::atomic64}, + {{0xdb, DST, SRC, MEM_OFFSET, 0xe1}, bpf_conformance_groups_t::atomic64}, + {{0xdb, DST, SRC, MEM_OFFSET, 0xf1}, bpf_conformance_groups_t::atomic64}, + {{0xdc, DST, 0, 0, 0x10}, bpf_conformance_groups_t::base32}, + {{0xdc, DST, 0, 0, 0x20}, bpf_conformance_groups_t::base32}, + {{0xdc, DST, 0, 0, 0x40}, bpf_conformance_groups_t::base64}, + {{0xdd, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base64}, + {{0xde, DST, SRC, JMP_OFFSET, 0}, bpf_conformance_groups_t::base32}, }; +// Verify that we can successfully unmarshal an instruction. +static void check_unmarshal_succeed(const ebpf_inst& ins, const ebpf_platform_t& platform = g_ebpf_platform_linux) { + program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; + const ebpf_inst exit{.opcode = INST_OP_EXIT}; + InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", {ins, exit, exit}, info})); + REQUIRE(parsed.size() == 3); +} + +// Verify that we can successfully unmarshal a 64-bit immediate instruction. +static void check_unmarshal_succeed(ebpf_inst inst1, ebpf_inst inst2, const ebpf_platform_t& platform = g_ebpf_platform_linux) { + program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; + const ebpf_inst exit{.opcode = INST_OP_EXIT}; + InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", {inst1, inst2, exit, exit}, info})); + REQUIRE(parsed.size() == 3); +} + // Verify that if we unmarshal an instruction and then re-marshal it, // we get what we expect. static void compare_unmarshal_marshal(const ebpf_inst& ins, const ebpf_inst& expected_result, const ebpf_platform_t& platform = g_ebpf_platform_linux) { @@ -410,7 +426,7 @@ TEST_CASE("disasm_marshal", "[disasm][marshal]") { // Test callx with support. Note that callx puts the register number in 'dst' not 'src'. ebpf_platform_t platform = g_ebpf_platform_linux; - platform.callx = true; + platform.supported_conformance_groups |= bpf_conformance_groups_t::callx; compare_marshal_unmarshal(Callx{8}, false, platform); ebpf_inst callx{.opcode = INST_OP_CALLX, .dst = 8}; compare_unmarshal_marshal(callx, callx, platform); @@ -566,8 +582,7 @@ static void check_unmarshal_instruction_fail(ebpf_inst& inst, const std::string& static ebpf_platform_t get_template_platform(const ebpf_instruction_template_t& previous_template) { ebpf_platform_t platform = g_ebpf_platform_linux; - if ((previous_template.groups & bpf_conformance_groups_t::callx) != bpf_conformance_groups_t::none) - platform.callx = true; + platform.supported_conformance_groups |= previous_template.groups; return platform; } @@ -642,7 +657,7 @@ static void check_instruction_imm_variations(const ebpf_instruction_template_t& if (inst.imm == JMP_OFFSET) { inst.imm = 0; // Not a valid jump offset. check_unmarshal_instruction_fail(inst, "0: jump out of bounds\n", platform); - } else if (inst.imm != IMM) { + } else if (inst.imm != IMM && inst.imm != HELPER_ID) { // This instruction limits what can appear in the 'imm' field. // Just try the next value unless that's what the next template has. inst.imm++; @@ -659,7 +674,8 @@ static void check_instruction_imm_variations(const ebpf_instruction_template_t& // Some instructions only permit non-zero imm values. // If the next template is for one of those, check the zero value now. if (next_template && (previous_template.inst.opcode != next_template->inst.opcode) && - (next_template->inst.imm > 0) && (next_template->inst.imm != JMP_OFFSET)) { + (next_template->inst.imm > 0) && (next_template->inst.imm != HELPER_ID) && + (next_template->inst.imm != JMP_OFFSET)) { inst = next_template->inst; inst.imm = 0; check_unmarshal_instruction_fail(inst, "0: unsupported immediate\n"); @@ -699,6 +715,29 @@ TEST_CASE("fail unmarshal bad instructions", "[disasm][marshal]") { check_instruction_variations(instruction_template[template_count - 1], {}); } +TEST_CASE("check unmarshal conformance groups", "[disasm][marshal]") { + for (const auto& current : instruction_template) { + // Try unmarshaling without support. + ebpf_platform_t platform = g_ebpf_platform_linux; + platform.supported_conformance_groups &= ~current.groups; + std::ostringstream oss; + oss << "0: bad instruction op 0x" << std::hex << (int)current.inst.opcode << std::endl; + check_unmarshal_fail(current.inst, oss.str(), platform); + + // Try unmarshaling with support. + platform.supported_conformance_groups |= current.groups; + ebpf_inst inst = current.inst; + if (inst.offset == JMP_OFFSET) + inst.offset = 1; + if (inst.imm == JMP_OFFSET) + inst.imm = 1; + if (inst.opcode == INST_OP_LDDW_IMM) + check_unmarshal_succeed(inst, ebpf_inst{}, platform); + else + check_unmarshal_succeed(inst, platform); + } +} + TEST_CASE("check unmarshal legacy opcodes", "[disasm][marshal]") { // The following opcodes are deprecated and should no longer be used. static uint8_t supported_legacy_opcodes[] = {0x20, 0x28, 0x30, 0x40, 0x48, 0x50}; @@ -706,9 +745,9 @@ TEST_CASE("check unmarshal legacy opcodes", "[disasm][marshal]") { compare_unmarshal_marshal(ebpf_inst{.opcode = opcode}, ebpf_inst{.opcode = opcode}); } - // Disable legacy support. + // Disable legacy packet instruction support. ebpf_platform_t platform = g_ebpf_platform_linux; - platform.legacy = false; + platform.supported_conformance_groups &= ~bpf_conformance_groups_t::packet; for (uint8_t opcode : supported_legacy_opcodes) { std::ostringstream oss; oss << "0: bad instruction op 0x" << std::hex << (int)opcode << std::endl; diff --git a/src/test/test_verify.cpp b/src/test/test_verify.cpp index bca93d05b..c5bf67fb5 100644 --- a/src/test/test_verify.cpp +++ b/src/test/test_verify.cpp @@ -79,7 +79,7 @@ FAIL_UNMARSHAL("invalid", "invalid-lddw.o", ".text") TEST_SECTION(dirname, filename, sectionname) \ TEST_CASE("Try unmarshalling bad program: " dirname "/" filename " " sectionname, "[unmarshal]") { \ ebpf_platform_t platform = g_ebpf_platform_linux; \ - platform.legacy = false; \ + platform.supported_conformance_groups &= ~bpf_conformance_groups_t::packet; \ auto raw_progs = read_elf("ebpf-samples/" dirname "/" filename, sectionname, nullptr, &platform); \ REQUIRE(raw_progs.size() == 1); \ raw_program raw_prog = raw_progs.back(); \ From d9b8390b3dd7e3e057de6d3d9f46d8b0c0dfc93f Mon Sep 17 00:00:00 2001 From: Jorge Navas Date: Fri, 25 Mar 2022 14:03:46 -0600 Subject: [PATCH 062/373] refactor(crab): made Prevail parametric wrt abstract domain Prevail was designed to use ebpf_domain_t as the only abstract domain. We made changes in Prevail so that other abstract domains can be easily plugin. To add a new domain: 1) Implement the public interface of abstract_domain_t defined in crab/abstract_domain.hpp. 2) Modify the enum class abstract_domain_kind in config.hpp to assign a new id to the new abstract domain. 3) Extend make_initial in crab_verifier.cpp to create new instances of the new domain Author: Jorge Navas Date: Fri Mar 25 14:03:46 2022 -0600 --- src/config.cpp | 1 + src/config.hpp | 3 + src/crab/abstract_domain.cpp | 285 +++++++++++++++++++++++++++++++++++ src/crab/abstract_domain.hpp | 135 +++++++++++++++++ src/crab/ebpf_domain.cpp | 29 ++-- src/crab/ebpf_domain.hpp | 9 +- src/crab/fwd_analyzer.cpp | 61 +++++--- src/crab/fwd_analyzer.hpp | 6 +- src/crab_verifier.cpp | 56 ++++++- 9 files changed, 540 insertions(+), 45 deletions(-) create mode 100644 src/crab/abstract_domain.cpp create mode 100644 src/crab/abstract_domain.hpp diff --git a/src/config.cpp b/src/config.cpp index 3849162c5..138e3711a 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -8,6 +8,7 @@ const ebpf_verifier_options_t ebpf_verifier_default_options = { .print_invariants = false, .print_failures = false, .no_simplify = false, + .abstract_domain = abstract_domain_kind::EBPF_DOMAIN, .mock_map_fds = true, .strict = false, .print_line_info = false, diff --git a/src/config.hpp b/src/config.hpp index cf3065fd9..c425d8019 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -2,12 +2,15 @@ // SPDX-License-Identifier: MIT #pragma once +enum class abstract_domain_kind { EBPF_DOMAIN, TYPE_DOMAIN }; + struct ebpf_verifier_options_t { bool check_termination; bool assume_assertions; bool print_invariants; bool print_failures; bool no_simplify; + abstract_domain_kind abstract_domain; // False to use actual map fd's, true to use mock fd's. bool mock_map_fds; diff --git a/src/crab/abstract_domain.cpp b/src/crab/abstract_domain.cpp new file mode 100644 index 000000000..244f770ae --- /dev/null +++ b/src/crab/abstract_domain.cpp @@ -0,0 +1,285 @@ +#include "abstract_domain.hpp" +#include "ebpf_domain.hpp" + +template +abstract_domain_t::abstract_domain_model::abstract_domain_model(Domain abs_val) + : m_abs_val(std::move(abs_val)) {} + +template +std::unique_ptr +abstract_domain_t::abstract_domain_model::clone() const { + std::unique_ptr res = + std::make_unique>(m_abs_val); + return std::move(res); +} + +template +void abstract_domain_t::abstract_domain_model::set_to_top() { + m_abs_val.set_to_top(); +} + +template +void abstract_domain_t::abstract_domain_model::set_to_bottom() { + m_abs_val.set_to_bottom(); +} + +template +bool abstract_domain_t::abstract_domain_model::is_bottom() const { + return m_abs_val.is_bottom(); +} + +template +bool abstract_domain_t::abstract_domain_model::is_top() const { + return m_abs_val.is_top(); +} + +// unsafe: if the underlying domain in abs is not Domain then it will crash +template +bool abstract_domain_t::abstract_domain_model::operator<=( + const abstract_domain_t::abstract_domain_concept& abs) { + return m_abs_val.operator<=(static_cast*>(&abs)->m_abs_val); +} + +// unsafe: if the underlying domain in abs is not Domain then it will crash +template +void abstract_domain_t::abstract_domain_model::operator|=( + const abstract_domain_t::abstract_domain_concept& abs) { + m_abs_val |= static_cast*>(&abs)->m_abs_val; +} + +// unsafe: if the underlying domain in abs is not Domain then it will crash +template +void abstract_domain_t::abstract_domain_model::operator|=(abstract_domain_t::abstract_domain_concept&& abs) { + m_abs_val |= std::move(static_cast*>(&abs)->m_abs_val); +} + +// unsafe: if the underlying domain in abs is not Domain then it will crash +template +std::unique_ptr abstract_domain_t::abstract_domain_model::operator|( + const abstract_domain_t::abstract_domain_concept& abs) const { + std::unique_ptr res( + new abstract_domain_t::abstract_domain_model(m_abs_val.operator|( + static_cast*>(&abs)->m_abs_val))); + return std::move(res); +} + +// unsafe: if the underlying domain in abs is not Domain then it will crash +template +std::unique_ptr +abstract_domain_t::abstract_domain_model::operator|(abstract_domain_t::abstract_domain_concept&& abs) const { + std::unique_ptr res( + new abstract_domain_t::abstract_domain_model(m_abs_val.operator|( + std::move(static_cast*>(&abs)->m_abs_val)))); + return std::move(res); +} + +// unsafe: if the underlying domain in abs is not Domain then it will crash +template +std::unique_ptr abstract_domain_t::abstract_domain_model::operator&( + const abstract_domain_t::abstract_domain_concept& abs) const { + std::unique_ptr res( + new abstract_domain_t::abstract_domain_model(m_abs_val.operator&( + static_cast*>(&abs)->m_abs_val))); + return std::move(res); +} + +// unsafe: if the underlying domain in abs is not Domain then it will crash +template +std::unique_ptr +abstract_domain_t::abstract_domain_model::widen(const abstract_domain_t::abstract_domain_concept& abs, bool to_constants) { + std::unique_ptr res( + new abstract_domain_t::abstract_domain_model( + m_abs_val.widen(static_cast*>(&abs)->m_abs_val, to_constants))); + return std::move(res); +} + +// unsafe: if the underlying domain in abs is not Domain then it will crash +template +std::unique_ptr +abstract_domain_t::abstract_domain_model::narrow(const abstract_domain_t::abstract_domain_concept& abs) const { + std::unique_ptr res( + new abstract_domain_t::abstract_domain_model( + m_abs_val.narrow(static_cast*>(&abs)->m_abs_val))); + return std::move(res); +} + +template +void abstract_domain_t::abstract_domain_model::operator()(const basic_block_t& bb) { + m_abs_val.operator()(bb); +} + +template +void abstract_domain_t::abstract_domain_model::operator()(const Undefined& s) { + m_abs_val.operator()(s); +} + +template +void abstract_domain_t::abstract_domain_model::operator()(const Bin& s) { + m_abs_val.operator()(s); +} + +template +void abstract_domain_t::abstract_domain_model::operator()(const Un& s) { + m_abs_val.operator()(s); +} + +template +void abstract_domain_t::abstract_domain_model::operator()(const LoadMapFd& s) { + m_abs_val.operator()(s); +} + +template +void abstract_domain_t::abstract_domain_model::operator()(const Call& s) { + m_abs_val.operator()(s); +} + +template +void abstract_domain_t::abstract_domain_model::operator()(const Exit& s) { + m_abs_val.operator()(s); +} + +template +void abstract_domain_t::abstract_domain_model::operator()(const Jmp& s) { + m_abs_val.operator()(s); +} + +template +void abstract_domain_t::abstract_domain_model::operator()(const Mem& s) { + m_abs_val.operator()(s); +} + +template +void abstract_domain_t::abstract_domain_model::operator()(const Packet& s) { + m_abs_val.operator()(s); +} + +template +void abstract_domain_t::abstract_domain_model::operator()(const Assume& s) { + m_abs_val.operator()(s); +} + +template +void abstract_domain_t::abstract_domain_model::operator()(const Assert& s) { + m_abs_val.operator()(s); +} + +template +void abstract_domain_t::abstract_domain_model::write(std::ostream& os) const { + m_abs_val.write(os); +} + +template +crab::bound_t abstract_domain_t::abstract_domain_model::get_loop_count_upper_bound() { + return m_abs_val.get_loop_count_upper_bound(); +} + +template +void abstract_domain_t::abstract_domain_model::initialize_loop_counter(const label_t label) { + m_abs_val.initialize_loop_counter(label); +} + +template +string_invariant abstract_domain_t::abstract_domain_model::to_set() { + return m_abs_val.to_set(); +} + +template +void abstract_domain_t::abstract_domain_model::set_require_check(check_require_func_t f) { + m_abs_val.set_require_check(f); +} + +abstract_domain_t::abstract_domain_t(std::unique_ptr concept_) + : m_concept(std::move(concept_)) {} + +template +abstract_domain_t::abstract_domain_t(Domain abs_val) + : m_concept(new abstract_domain_t::abstract_domain_model(std::move(abs_val))) {} + +abstract_domain_t::abstract_domain_t(const abstract_domain_t& o) : m_concept(o.m_concept->clone()) {} + +abstract_domain_t& abstract_domain_t::operator=(const abstract_domain_t& o) { + if (this != &o) { + m_concept = o.m_concept->clone(); + } + return *this; +} + +void abstract_domain_t::set_to_top() { m_concept->set_to_top(); } + +void abstract_domain_t::set_to_bottom() { m_concept->set_to_bottom(); } + +bool abstract_domain_t::is_bottom() const { return m_concept->is_bottom(); } + +bool abstract_domain_t::is_top() const { return m_concept->is_top(); } + +bool abstract_domain_t::operator<=(const abstract_domain_t& abs) { + return m_concept->operator<=(*(abs.m_concept)); +} + +void abstract_domain_t::operator|=(const abstract_domain_t& abs) { m_concept->operator|=(*(abs.m_concept)); } + +void abstract_domain_t::operator|=(abstract_domain_t&& abs) { m_concept->operator|=(std::move(*(abs.m_concept))); } + +abstract_domain_t abstract_domain_t::operator|(const abstract_domain_t& abs) const { + return abstract_domain_t(std::move(m_concept->operator|(*(abs.m_concept)))); +} + +abstract_domain_t abstract_domain_t::operator|(abstract_domain_t&& abs) const { + return abstract_domain_t(std::move(m_concept->operator|(std::move(*(abs.m_concept))))); +} + +abstract_domain_t abstract_domain_t::operator&(const abstract_domain_t& abs) const { + return abstract_domain_t(std::move(m_concept->operator&(*(abs.m_concept)))); +} + +abstract_domain_t abstract_domain_t::widen(const abstract_domain_t& abs, bool to_constants) { + return abstract_domain_t(std::move(m_concept->widen(*(abs.m_concept), to_constants))); +} + +abstract_domain_t abstract_domain_t::narrow(const abstract_domain_t& abs) const { + return abstract_domain_t(std::move(m_concept->narrow(*(abs.m_concept)))); +} + +void abstract_domain_t::operator()(const basic_block_t& bb) { + m_concept->operator()(bb); +} + +void abstract_domain_t::operator()(const Undefined& s) { m_concept->operator()(s); } + +void abstract_domain_t::operator()(const Bin& s) { m_concept->operator()(s); } + +void abstract_domain_t::operator()(const Un& s) { m_concept->operator()(s); } + +void abstract_domain_t::operator()(const LoadMapFd& s) { m_concept->operator()(s); } + +void abstract_domain_t::operator()(const Call& s) { m_concept->operator()(s); } + +void abstract_domain_t::operator()(const Exit& s) { m_concept->operator()(s); } + +void abstract_domain_t::operator()(const Jmp& s) { m_concept->operator()(s); } + +void abstract_domain_t::operator()(const Mem& s) { m_concept->operator()(s); } + +void abstract_domain_t::operator()(const Packet& s) { m_concept->operator()(s); } + +void abstract_domain_t::operator()(const Assume& s) { m_concept->operator()(s); } + +void abstract_domain_t::operator()(const Assert& s) { m_concept->operator()(s); } + +void abstract_domain_t::write(std::ostream& os) const { m_concept->write(os); } + +crab::bound_t abstract_domain_t::get_loop_count_upper_bound() { return m_concept->get_loop_count_upper_bound(); } + +void abstract_domain_t::initialize_loop_counter(const label_t label) { m_concept->initialize_loop_counter(label); } + +string_invariant abstract_domain_t::to_set() { return m_concept->to_set(); } + +void abstract_domain_t::set_require_check(check_require_func_t f) { m_concept->set_require_check(f); } + +std::ostream& operator<<(std::ostream& o, const abstract_domain_t& dom) { + dom.write(o); + return o; +} + +// REQUIRED: instantiation for supported domains +template abstract_domain_t::abstract_domain_t(crab::ebpf_domain_t); diff --git a/src/crab/abstract_domain.hpp b/src/crab/abstract_domain.hpp new file mode 100644 index 000000000..ae03cb2a1 --- /dev/null +++ b/src/crab/abstract_domain.hpp @@ -0,0 +1,135 @@ +#pragma once + +#include "cfg.hpp" +#include "linear_constraint.hpp" +#include "string_constraints.hpp" + +#include "array_domain.hpp" +using check_require_func_t = std::function; +class abstract_domain_t { + private: + class abstract_domain_concept { + public: + abstract_domain_concept() = default; + virtual ~abstract_domain_concept() = default; + abstract_domain_concept(const abstract_domain_concept&) = delete; + abstract_domain_concept(abstract_domain_concept&&) = delete; + abstract_domain_concept& operator=(const abstract_domain_concept&) = delete; + abstract_domain_concept& operator=(abstract_domain_concept&&) = delete; + virtual std::unique_ptr clone() const = 0; + virtual void set_to_top() = 0; + virtual void set_to_bottom() = 0; + virtual bool is_bottom() const = 0; + virtual bool is_top() const = 0; + virtual bool operator<=(const abstract_domain_concept& abs) = 0; + virtual std::unique_ptr operator|(const abstract_domain_concept& abs) const = 0; + virtual std::unique_ptr operator|(abstract_domain_concept&& abs) const = 0; + virtual void operator|=(const abstract_domain_concept& abs) = 0; + virtual void operator|=(abstract_domain_concept&& abs) = 0; + virtual std::unique_ptr operator&(const abstract_domain_concept& abs) const = 0; + virtual std::unique_ptr widen(const abstract_domain_concept& abs, bool) = 0; + virtual std::unique_ptr narrow(const abstract_domain_concept& abs) const = 0; + virtual void operator()(const basic_block_t&) = 0; + virtual void operator()(const Undefined&) = 0; + virtual void operator()(const Bin&) = 0; + virtual void operator()(const Un&) = 0; + virtual void operator()(const LoadMapFd&) = 0; + virtual void operator()(const Call&) = 0; + virtual void operator()(const Exit&) = 0; + virtual void operator()(const Jmp&) = 0; + virtual void operator()(const Mem&) = 0; + virtual void operator()(const Packet&) = 0; + virtual void operator()(const Assume&) = 0; + virtual void operator()(const Assert&) = 0; + virtual void write(std::ostream& o) const = 0; + + /* These operations are not very conventional for an abstract + domain but it's convenient to have them */ + + virtual crab::bound_t get_loop_count_upper_bound() = 0; + virtual void initialize_loop_counter(const label_t) = 0; + virtual string_invariant to_set() = 0; + virtual void set_require_check(check_require_func_t f) = 0; + }; // end class abstract_domain_concept + + template + class abstract_domain_model final : public abstract_domain_concept { + Domain m_abs_val; + + public: + explicit abstract_domain_model(Domain abs_val); + std::unique_ptr clone() const override; + void set_to_top() override; + void set_to_bottom() override; + bool is_bottom() const override; + bool is_top() const override; + bool operator<=(const abstract_domain_concept& abs) override; + void operator|=(const abstract_domain_concept& abs) override; + void operator|=(abstract_domain_concept&& abs) override; + std::unique_ptr operator|(const abstract_domain_concept& abs) const override; + std::unique_ptr operator|(abstract_domain_concept&& abs) const override; + std::unique_ptr operator&(const abstract_domain_concept& abs) const override; + std::unique_ptr widen(const abstract_domain_concept& abs, bool) override; + std::unique_ptr narrow(const abstract_domain_concept& abs) const override; + void operator()(const basic_block_t& bb) override; + void operator()(const Undefined& s) override; + void operator()(const Bin& s) override; + void operator()(const Un& s) override; + void operator()(const LoadMapFd& s) override; + void operator()(const Call& s) override; + void operator()(const Exit& s) override; + void operator()(const Jmp& s) override; + void operator()(const Mem& s) override; + void operator()(const Packet& s) override; + void operator()(const Assume& s) override; + void operator()(const Assert& s) override; + void write(std::ostream& o) const override; + void initialize_loop_counter(const label_t) override; + crab::bound_t get_loop_count_upper_bound() override; + string_invariant to_set() override; + void set_require_check(check_require_func_t f) override; + }; // end class abstract_domain_model + + std::unique_ptr m_concept; + explicit abstract_domain_t(std::unique_ptr concept_); + + public: + template + abstract_domain_t(Domain abs_val); + ~abstract_domain_t() = default; + abstract_domain_t(const abstract_domain_t& o); + abstract_domain_t& operator=(const abstract_domain_t& o); + abstract_domain_t(abstract_domain_t&& o) = default; + abstract_domain_t& operator=(abstract_domain_t&& o) = default; + void set_to_top(); + void set_to_bottom(); + bool is_bottom() const; + bool is_top() const; + bool operator<=(const abstract_domain_t& abs); + void operator|=(const abstract_domain_t& abs); + void operator|=(abstract_domain_t&& abs); + abstract_domain_t operator|(const abstract_domain_t& abs) const; + abstract_domain_t operator|(abstract_domain_t&& abs) const; + abstract_domain_t operator&(const abstract_domain_t& abs) const; + abstract_domain_t widen(const abstract_domain_t& abs, bool); + abstract_domain_t narrow(const abstract_domain_t& abs) const; + void operator()(const basic_block_t& bb); + void operator()(const Undefined& s); + void operator()(const Bin& s); + void operator()(const Un& s); + void operator()(const LoadMapFd& s); + void operator()(const Call& s); + void operator()(const Exit& s); + void operator()(const Jmp& s); + void operator()(const Mem& s); + void operator()(const Packet& s); + void operator()(const Assume& s); + void operator()(const Assert& s); + void write(std::ostream& o) const; + crab::bound_t get_loop_count_upper_bound(); + void initialize_loop_counter(const label_t); + string_invariant to_set(); + void set_require_check(check_require_func_t f); + + friend std::ostream& operator<<(std::ostream& o, const abstract_domain_t& dom); +}; diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index 6d3cd51b7..9fb6d0811 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -23,6 +23,8 @@ using crab::domains::NumAbsDomain; namespace crab { +using crab::data_kind_t; + struct reg_pack_t { variable_t svalue; // int64_t value. variable_t uvalue; // uint64_t value. @@ -812,7 +814,9 @@ bool ebpf_domain_t::is_bottom() const { return m_inv.is_bottom(); } bool ebpf_domain_t::is_top() const { return m_inv.is_top() && stack.is_top(); } -bool ebpf_domain_t::operator<=(const ebpf_domain_t& other) { return m_inv <= other.m_inv && stack <= other.stack; } +bool ebpf_domain_t::operator<=(const ebpf_domain_t& other) { + return m_inv <= other.m_inv && stack <= other.stack; +} // bool ebpf_domain_t::operator==(const ebpf_domain_t& other) const { // return stack == other.stack && m_inv <= other.m_inv && other.m_inv <= m_inv; @@ -905,14 +909,10 @@ ebpf_domain_t ebpf_domain_t::operator|(ebpf_domain_t&& other) const { return ebpf_domain_t(m_inv | std::move(other.m_inv), stack | other.stack); } -ebpf_domain_t ebpf_domain_t::operator|(const ebpf_domain_t& other) const& { +ebpf_domain_t ebpf_domain_t::operator|(const ebpf_domain_t& other) const { return ebpf_domain_t(m_inv | other.m_inv, stack | other.stack); } -ebpf_domain_t ebpf_domain_t::operator|(const ebpf_domain_t& other) && { - return ebpf_domain_t(other.m_inv | std::move(m_inv), other.stack | stack); -} - ebpf_domain_t ebpf_domain_t::operator&(const ebpf_domain_t& other) const { return ebpf_domain_t(m_inv & other.m_inv, stack & other.stack); } @@ -952,7 +952,12 @@ ebpf_domain_t ebpf_domain_t::widen(const ebpf_domain_t& other, bool to_constants return res; } -ebpf_domain_t ebpf_domain_t::narrow(const ebpf_domain_t& other) { +// ebpf_domain_t ebpf_domain_t::widening_thresholds(const ebpf_domain_t& other, const crab::iterators::thresholds_t& ts) +// { +// return ebpf_domain_t(m_inv.widening_thresholds(other.m_inv, ts), stack | other.stack); +// } + +ebpf_domain_t ebpf_domain_t::narrow(const ebpf_domain_t& other) const { return ebpf_domain_t(m_inv.narrow(other.m_inv), stack & other.stack); } @@ -2920,12 +2925,16 @@ void ebpf_domain_t::operator()(const Bin& bin) { string_invariant ebpf_domain_t::to_set() { return this->m_inv.to_set() + this->stack.to_set(); } -std::ostream& operator<<(std::ostream& o, const ebpf_domain_t& dom) { - if (dom.is_bottom()) { +void ebpf_domain_t::write(std::ostream& o) const { + if (is_bottom()) { o << "_|_"; } else { - o << dom.m_inv << "\nStack: " << dom.stack; + o << m_inv << "\nStack: " << stack; } +} + +std::ostream& operator<<(std::ostream& o, ebpf_domain_t dom) { + dom.write(o); return o; } diff --git a/src/crab/ebpf_domain.hpp b/src/crab/ebpf_domain.hpp index 85f49b762..92b683d1c 100644 --- a/src/crab/ebpf_domain.hpp +++ b/src/crab/ebpf_domain.hpp @@ -8,6 +8,7 @@ #include #include +#include "crab/abstract_domain.hpp" #include "crab/array_domain.hpp" #include "crab/split_dbm.hpp" #include "crab/variable.hpp" @@ -38,12 +39,11 @@ class ebpf_domain_t final { void operator|=(ebpf_domain_t&& other); void operator|=(const ebpf_domain_t& other); ebpf_domain_t operator|(ebpf_domain_t&& other) const; - ebpf_domain_t operator|(const ebpf_domain_t& other) const&; - ebpf_domain_t operator|(const ebpf_domain_t& other) &&; + ebpf_domain_t operator|(const ebpf_domain_t& other) const; ebpf_domain_t operator&(const ebpf_domain_t& other) const; ebpf_domain_t widen(const ebpf_domain_t& other, bool to_constants); ebpf_domain_t widening_thresholds(const ebpf_domain_t& other, const crab::iterators::thresholds_t& ts); - ebpf_domain_t narrow(const ebpf_domain_t& other); + ebpf_domain_t narrow(const ebpf_domain_t& other) const; typedef bool check_require_func_t(NumAbsDomain&, const linear_constraint_t&, std::string); void set_require_check(std::function f); @@ -81,6 +81,9 @@ class ebpf_domain_t final { void operator()(const ZeroCtxOffset&); void operator()(const IncrementLoopCounter&); + // write operation is important to keep in ebpf_domain_t because of the parametric abstract domain + void write(std::ostream& o) const; + void initialize_loop_counter(label_t label); static ebpf_domain_t calculate_constant_limits(); private: diff --git a/src/crab/fwd_analyzer.cpp b/src/crab/fwd_analyzer.cpp index d08f515d3..047dc1b72 100644 --- a/src/crab/fwd_analyzer.cpp +++ b/src/crab/fwd_analyzer.cpp @@ -6,7 +6,6 @@ #include "crab/cfg.hpp" #include "crab/wto.hpp" -#include "crab/ebpf_domain.hpp" #include "crab/fwd_analyzer.hpp" namespace crab { @@ -45,6 +44,7 @@ class interleaved_fwd_fixpoint_iterator_t final { using iterator = typename invariant_table_t::iterator; cfg_t& _cfg; + abstract_domain_t _bottom_inv; wto_t _wto; invariant_table_t _pre, _post; @@ -59,16 +59,28 @@ class interleaved_fwd_fixpoint_iterator_t final { bool _skip{true}; private: - void set_pre(const label_t& label, const ebpf_domain_t& v) { _pre[label] = v; } + void set_pre(const label_t& label, const abstract_domain_t& v) { + auto res = _pre.insert({label, v}); + if (!res.second) { + // if the insertion failed then we just update value + res.first->second = v; + } + } - void transform_to_post(const label_t& label, ebpf_domain_t pre) { + inline void transform_to_post(const label_t& label, abstract_domain_t pre) { basic_block_t& bb = _cfg.get_node(label); pre(bb); - _post[label] = std::move(pre); + + auto it = _post.find(label); + if (it == _post.end()) { + _post.insert({label, std::move(pre)}); + } else { + it->second = std::move(pre); + } } - [[nodiscard]] static ebpf_domain_t extrapolate(ebpf_domain_t before, const ebpf_domain_t& after, - unsigned int iteration) { + [[nodiscard]] abstract_domain_t extrapolate(abstract_domain_t before, const abstract_domain_t& after, + unsigned int iteration) const { /// number of iterations until triggering widening constexpr auto _widening_delay = 2; @@ -78,7 +90,8 @@ class interleaved_fwd_fixpoint_iterator_t final { return before.widen(after, iteration == _widening_delay); } - static ebpf_domain_t refine(ebpf_domain_t before, const ebpf_domain_t& after, unsigned int iteration) { + static abstract_domain_t refine(abstract_domain_t before, const abstract_domain_t& after, + unsigned int iteration) { if (iteration == 1) { return before & after; } else { @@ -86,8 +99,8 @@ class interleaved_fwd_fixpoint_iterator_t final { } } - ebpf_domain_t join_all_prevs(const label_t& node) { - ebpf_domain_t res = ebpf_domain_t::bottom(); + abstract_domain_t join_all_prevs(const label_t& node) { + abstract_domain_t res(_bottom_inv); for (const label_t& prev : _cfg.prev_nodes(node)) { res |= get_post(prev); } @@ -95,27 +108,33 @@ class interleaved_fwd_fixpoint_iterator_t final { } public: - explicit interleaved_fwd_fixpoint_iterator_t(cfg_t& cfg) : _cfg(cfg), _wto(cfg) { + explicit interleaved_fwd_fixpoint_iterator_t(cfg_t& cfg, const abstract_domain_t& entry) + : _cfg(cfg), _bottom_inv(entry), _wto(cfg) { + + _bottom_inv.set_to_bottom(); + for (const auto& label : _cfg.labels()) { - _pre.emplace(label, ebpf_domain_t::bottom()); - _post.emplace(label, ebpf_domain_t::bottom()); + _pre.emplace(label, _bottom_inv); + _post.emplace(label, _bottom_inv); } + set_pre(_cfg.entry_label(), entry); } - ebpf_domain_t get_pre(const label_t& node) { return _pre.at(node); } + abstract_domain_t get_pre(const label_t& node) { return _pre.at(node); } - ebpf_domain_t get_post(const label_t& node) { return _post.at(node); } + abstract_domain_t get_post(const label_t& node) { return _post.at(node); } void operator()(const label_t& node); void operator()(std::shared_ptr& cycle); - friend std::pair run_forward_analyzer(cfg_t& cfg, ebpf_domain_t entry_inv); + friend std::pair + run_forward_analyzer(cfg_t& cfg, abstract_domain_t entry_inv); }; -std::pair run_forward_analyzer(cfg_t& cfg, ebpf_domain_t entry_inv) { +std::pair run_forward_analyzer(cfg_t& cfg, abstract_domain_t entry_inv) { // Go over the CFG in weak topological order (accounting for loops). - interleaved_fwd_fixpoint_iterator_t analyzer(cfg); + interleaved_fwd_fixpoint_iterator_t analyzer(cfg, entry_inv); if (thread_local_options.check_termination) { std::vector cycle_heads; for (auto& component : analyzer._wto) { @@ -144,7 +163,7 @@ void interleaved_fwd_fixpoint_iterator_t::operator()(const label_t& node) { return; } - ebpf_domain_t pre = node == _cfg.entry_label() ? get_pre(node) : join_all_prevs(node); + abstract_domain_t pre = node == _cfg.entry_label() ? get_pre(node) : join_all_prevs(node); set_pre(node, pre); transform_to_post(node, pre); @@ -167,7 +186,7 @@ void interleaved_fwd_fixpoint_iterator_t::operator()(std::shared_ptr(c) || (std::get(c) != head)) std::visit(*this, *component); } - ebpf_domain_t new_pre = join_all_prevs(head); + abstract_domain_t new_pre = join_all_prevs(head); if (new_pre <= invariant) { // Post-fixpoint reached set_pre(head, new_pre); @@ -208,7 +227,7 @@ void interleaved_fwd_fixpoint_iterator_t::operator()(std::shared_ptr(c) || (std::get(c) != head)) std::visit(*this, *component); } - ebpf_domain_t new_pre = join_all_prevs(head); + abstract_domain_t new_pre = join_all_prevs(head); if (invariant <= new_pre) { // No more refinement possible(pre == new_pre) break; diff --git a/src/crab/fwd_analyzer.hpp b/src/crab/fwd_analyzer.hpp index a6e35e476..632da744c 100644 --- a/src/crab/fwd_analyzer.hpp +++ b/src/crab/fwd_analyzer.hpp @@ -6,13 +6,13 @@ #include #include "config.hpp" +#include "crab/abstract_domain.hpp" #include "crab/cfg.hpp" -#include "crab/ebpf_domain.hpp" namespace crab { -using invariant_table_t = std::map; +using invariant_table_t = std::map; -std::pair run_forward_analyzer(cfg_t& cfg, ebpf_domain_t entry_inv); +std::pair run_forward_analyzer(cfg_t& cfg, abstract_domain_t entry_inv); } // namespace crab diff --git a/src/crab_verifier.cpp b/src/crab_verifier.cpp index cdee1e9cf..f7cd3265c 100644 --- a/src/crab_verifier.cpp +++ b/src/crab_verifier.cpp @@ -12,6 +12,7 @@ #include +#include "crab/abstract_domain.hpp" #include "crab/ebpf_domain.hpp" #include "crab/fwd_analyzer.hpp" #include "crab_utils/lazy_allocator.hpp" @@ -21,7 +22,7 @@ #include "string_constraints.hpp" using crab::ebpf_domain_t; -using std::string; +using crab::linear_constraint_t; thread_local crab::lazy_allocator global_program_info; thread_local ebpf_verifier_options_t thread_local_options; @@ -32,9 +33,10 @@ static checks_db generate_report(cfg_t& cfg, checks_db m_db; for (const label_t& label : cfg.sorted_labels()) { basic_block_t& bb = cfg.get_node(label); - ebpf_domain_t from_inv(pre_invariants.at(label)); + abstract_domain_t from_inv(pre_invariants.at(label)); + from_inv.set_require_check( - [&m_db, label](auto& inv, const crab::linear_constraint_t& cst, const std::string& s) { + [&m_db, label](auto& inv, const linear_constraint_t& cst, const std::string& s) { if (inv.is_bottom()) return true; if (cst.is_contradiction()) { @@ -114,6 +116,43 @@ static checks_db get_analysis_report(std::ostream& s, cfg_t& cfg, crab::invarian return db; } +/* EXTEND FOR NEW DOMAINS */ +static abstract_domain_t make_initial(const ebpf_verifier_options_t* options) { + switch (options->abstract_domain) { + case abstract_domain_kind::EBPF_DOMAIN: { + ebpf_domain_t entry_inv = ebpf_domain_t::setup_entry(true); + return abstract_domain_t(entry_inv); + } + case abstract_domain_kind::TYPE_DOMAIN: { + // TODO + } + default: + // FIXME: supported abstract domains should be checked in check.cpp + std::cerr << "error: unsupported abstract domain\n"; + std::exit(1); + } +} + +/* EXTEND FOR NEW DOMAINS */ +static abstract_domain_t make_initial(abstract_domain_kind abstract_domain, const string_invariant& entry_invariant, bool setup_constraints) { + + switch (abstract_domain) { + case abstract_domain_kind::EBPF_DOMAIN: { + ebpf_domain_t entry_inv = entry_invariant.is_bottom() + ? ebpf_domain_t::from_constraints({"false"}, setup_constraints) + : ebpf_domain_t::from_constraints(entry_invariant.value(), setup_constraints); + return abstract_domain_t(entry_inv); + } + case abstract_domain_kind::TYPE_DOMAIN: { + // TODO + } + default: + // FIXME: supported abstract domains should be checked in check.cpp + std::cerr << "error: unsupported abstract domain\n"; + std::exit(1); + } +} + crab_results get_ebpf_report(std::ostream& s, cfg_t& cfg, program_info info, const ebpf_verifier_options_t* options) { global_program_info = std::move(info); crab::domains::clear_global_state(); @@ -121,8 +160,9 @@ crab_results get_ebpf_report(std::ostream& s, cfg_t& cfg, program_info info, con thread_local_options = *options; try { + + abstract_domain_t entry_dom = make_initial(options); // Get dictionaries of pre-invariants and post-invariants for each basic block. - ebpf_domain_t entry_dom = ebpf_domain_t::setup_entry(true); auto [pre_invariants, post_invariants] = crab::run_forward_analyzer(cfg, std::move(entry_dom)); return crab_results(std::move(cfg), @@ -171,7 +211,7 @@ std::tuple ebpf_analyze_program_for_test(std::ostream& o thread_local_options = options; global_program_info = info; assert(!entry_invariant.is_bottom()); - ebpf_domain_t entry_inv = ebpf_domain_t::from_constraints(entry_invariant.value(), options.setup_constraints); + abstract_domain_t entry_inv = make_initial(abstract_domain_kind::EBPF_DOMAIN, entry_invariant, options.setup_constraints); if (entry_inv.is_bottom()) throw std::runtime_error("Entry invariant is inconsistent"); cfg_t cfg = prepare_cfg(prog, info, !options.no_simplify, false); @@ -186,7 +226,7 @@ std::tuple ebpf_analyze_program_for_test(std::ostream& o /// Returned value is true if the program passes verification. crab_results 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) { if (options == nullptr) options = &ebpf_verifier_default_options; @@ -195,7 +235,7 @@ crab_results ebpf_verify_program(std::ostream& os, const InstructionSeq& prog, c cfg_t cfg = prepare_cfg(prog, info, !options->no_simplify); crab_results results = get_ebpf_report(os, cfg, info, options); - checks_db &report = results.db; + checks_db& report = results.db; if (options->print_failures) { print_report(os, report, prog, options->print_line_info); } @@ -204,7 +244,7 @@ crab_results ebpf_verify_program(std::ostream& os, const InstructionSeq& prog, c stats->total_warnings = report.total_warnings; stats->max_loop_count = report.get_max_loop_count(); } - //return (report.total_warnings == 0); + // return (report.total_warnings == 0); return results; } From 05046ca559b0fbe7e4efa482012eb38d20926bd0 Mon Sep 17 00:00:00 2001 From: Jorge Navas Date: Fri, 25 Mar 2022 14:05:12 -0600 Subject: [PATCH 063/373] test: a script to run prevail on ebpf-samples --- scripts/runcheck.sh | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100755 scripts/runcheck.sh diff --git a/scripts/runcheck.sh b/scripts/runcheck.sh new file mode 100755 index 000000000..520d0e4cf --- /dev/null +++ b/scripts/runcheck.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +### Update this path to the root of your prevail repository. +PREVAIL_ROOT=/Users/jorge/Repos/prevail-type-proofs + + +EBPF_BENCHMARKS=${PREVAIL_ROOT}/ebpf-samples +PREVAIL_CHECK=${PREVAIL_ROOT}/check +DOMAINS="zoneCrab" +PREFIX=prevail_$(date +"%m%d%y%H%M") + +for dom in $DOMAINS +do + rm -f log_${dom}.txt + echo -n "Running Prevail with $dom ... " + echo "File,Result,Cpu,Mem" 1>> ${PREFIX}_${dom}.csv + for f in ${EBPF_BENCHMARKS}/*/*.o + do + sections=($(${PREVAIL_CHECK} $f -l 2> /dev/null)) + for s in "${sections[@]}" + do + echo "${PREVAIL_CHECK} ${f} ${s} --domain=${dom}" >> log_${dom}.txt + echo -n $f:$s 1>> ${PREFIX}_${dom}.csv + o=$(${PREVAIL_CHECK} ${f} ${s} --domain=${dom} 2>>log_${dom}.txt) + echo -n ",$o" 1>> ${PREFIX}_${dom}.csv + echo 1>> ${PREFIX}_${dom}.csv + done + done + echo "DONE" +done From 223306cb2a671910005eb77d63ab7299fe17ec91 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Fri, 11 Mar 2022 13:15:48 -0500 Subject: [PATCH 064/373] Initial implementation for type domain; Abstract transformers for assignment and memory instruction implemented; Join not implemented yet Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 204 +++++++++++++++++++++++++++++++++++++++ src/crab/type_domain.hpp | 115 ++++++++++++++++++++++ src/ebpf_proof.cpp | 40 +++++++- src/ebpf_proof.hpp | 17 ++++ 4 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 src/crab/type_domain.cpp create mode 100644 src/crab/type_domain.hpp diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp new file mode 100644 index 000000000..ec41d1d67 --- /dev/null +++ b/src/crab/type_domain.cpp @@ -0,0 +1,204 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT + +#include + +#include "crab/type_domain.hpp" + +using crab::ptr_t; +using crab::ptr_with_off_t; +using crab::ptr_no_off_t; +using crab::ctx_t; + +void type_domain_t::operator()(const Undefined & u) {} +void type_domain_t::operator()(const Un &u) {} +void type_domain_t::operator()(const LoadMapFd &u) {} +void type_domain_t::operator()(const Call &u) {} +void type_domain_t::operator()(const Exit &u) {} +void type_domain_t::operator()(const Jmp &u) {} +void type_domain_t::operator()(const Packet & u) {} +void type_domain_t::operator()(const LockAdd &u) {} +void type_domain_t::operator()(const Assume &u) {} +void type_domain_t::operator()(const Assert &u) {} + + +type_domain_t type_domain_t::setup_entry(const ctx_t& _ctx) { + + type_domain_t inv; + + inv.types.insert(std::make_pair(R1_ARG, ptr_with_off_t(crab::region::T_CTX, 0))); + inv.types.insert(std::make_pair(R10_STACK_POINTER, ptr_with_off_t(crab::region::T_STACK, 512))); + + for (auto& p : _ctx) { + inv.ctx.insert(p); + } + + return inv; +} + +void type_domain_t::operator()(const Bin& bin) { + + if (std::holds_alternative(bin.v)) { + Reg src = std::get(bin.v); + switch (bin.op) + { + case Bin::Op::MOV: { + + auto it = types.find(src.v); + if (it == types.end()) { + std::cout << "type error: assigning an unknown pointer or a number\n"; + exit(0); + } + + types.insert(std::make_pair(bin.dst.v, it->second)); + } + + default: + break; + } + } +} + +void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { + + int offset = b.access.offset; + Reg basereg = b.access.basereg; + + auto it = types.find(basereg.v); + if (it == types.end()) { + std::cout << "type_error: loading from an unknown pointer, or from number\n"; + exit(0); + } + + ptr_t type_basereg = it->second; + + if (std::holds_alternative(type_basereg)) { + std::cout << "type_error: loading from either packet or shared region not allowed\n"; + exit(0); + } + + ptr_with_off_t type_with_off = std::get(type_basereg); + int load_at = offset+type_with_off.offset; + + switch (type_with_off.r) { + case crab::region::T_STACK: { + + auto it = stack.find(load_at); + + if (it == stack.end()) { + std::cout << "type_error: no field at loaded offset in stack\n"; + exit(0); + } + ptr_t type_loaded = it->second; + + if (std::holds_alternative(type_loaded)) { + ptr_with_off_t type_loaded_with_off = std::get(type_loaded); + types.insert(std::make_pair(target_reg.v, type_loaded_with_off)); + } + else { + ptr_no_off_t type_loaded_no_off = std::get(type_loaded); + types.insert(std::make_pair(target_reg.v, type_loaded_no_off)); + } + + break; + } + case crab::region::T_CTX: { + + auto it = ctx.find(load_at); + + if (it == ctx.end()) { + std::cout << "type_error: no field at loaded offset in context\n"; + exit(0); + } + ptr_no_off_t type_loaded = it->second; + + types.insert(std::make_pair(target_reg.v, type_loaded)); + break; + } + + default: { + assert(false); + } + } +} + +void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { + + int offset = b.access.offset; + Reg basereg = b.access.basereg; + + auto it = types.find(basereg.v); + if (it == types.end()) { + std::cout << "type_error: storing at an unknown pointer, or from number\n"; + exit(0); + } + + ptr_t type_basereg = it->second; + + auto it2 = types.find(target_reg.v); + if (it2 == types.end()) { + std::cout << "type_error: storing either a number or an unknown pointer\n"; + exit(0); + } + + ptr_t type_stored = it2->second; + + if (std::holds_alternative(type_stored)) { + ptr_with_off_t type_stored_with_off = std::get(type_stored); + if (type_stored_with_off.r == crab::region::T_STACK) { + std::cout << "type_error: we do not store stack pointers into stack\n"; + exit(0); + } + } + + if (std::holds_alternative(type_basereg)) { + std::cout << "type_error: we cannot store pointers into packet or shared\n"; + exit(0); + } + + ptr_with_off_t type_basereg_with_off = std::get(type_basereg); + if (type_basereg_with_off.r == crab::region::T_CTX) { + std::cout << "type_error: we cannot store pointers into ctx\n"; + exit(0); + } + + int store_at = offset+type_basereg_with_off.offset; + + auto it3 = stack.find(store_at); + if (it3 == stack.end()) { + stack.insert(std::make_pair(store_at, type_stored)); + } + else { + auto type_in_stack = it3->second; + if (type_stored.index() != type_in_stack.index()) { + std::cout << "type_error: type being stored is not the same as stored already in stack\n"; + exit(0); + } + if (std::holds_alternative(type_stored)) { + if (std::get(type_stored) != std::get(type_in_stack)) { + std::cout << "type_error: type being stored is not the same as stored already in stack\n"; + exit(0); + } + } + else { + if (std::get(type_stored) != std::get(type_in_stack)) { + std::cout << "type_error: type being stored is not the same as stored already in stack\n"; + exit(0); + } + } + } +} + +void type_domain_t::operator()(const Mem& b) { + + if (std::holds_alternative(b.value)) { + if (b.is_load) { + do_load(b, std::get(b.value)); + } else { + do_mem_store(b, std::get(b.value)); + } + } else { + std::cout << "Either loading to a number (not allowed) or storing a number (not allowed yet)\n"; + exit(0); + } +} diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp new file mode 100644 index 000000000..3a2a1a013 --- /dev/null +++ b/src/crab/type_domain.hpp @@ -0,0 +1,115 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +#include "crab/cfg.hpp" + +namespace crab { + +enum class region { + T_CTX, + T_STACK, + T_PACKET, + T_SHARED, +}; + + +struct ptr_with_off_t; + +struct ptr_no_off_t { + region r; + + ptr_no_off_t(region _r) : r(_r) {} + ptr_no_off_t(const ptr_no_off_t& p) : r(p.r) {} + bool operator!=(const ptr_with_off_t& p) { + return false; + } + bool operator!=(const ptr_no_off_t& p) { + return r != p.r; + } +}; + +struct ptr_with_off_t { + region r; + int offset; + + ptr_with_off_t(region _r, int _off) : r(_r), offset(_off) {} + ptr_with_off_t(const ptr_with_off_t& p) : r(p.r), offset(p.offset) {} + bool operator!=(const ptr_no_off_t& p) { + return false; + } + bool operator!=(const ptr_with_off_t& p) { + return r != p.r; + } +}; + +struct reg_with_loc_t { + uint8_t r; + int loc; +}; + +using ptr_t = std::variant; + +using stack_t = std::unordered_map; +using types_t = std::unordered_map; +using ctx_t = std::unordered_map; +} + +class type_domain_t final { + + crab::stack_t stack; + crab::types_t types; + crab::ctx_t ctx; + + public: + + type_domain_t() {} + // eBPF initialization: R1 points to ctx, R10 to stack, etc. + static type_domain_t setup_entry(const crab::ctx_t&); + // bottom/top + void set_to_top(); + void set_to_bottom(); + bool is_bottom() const; + bool is_top() const; + // inclusion + bool operator<=(const type_domain_t& other) const; + // join + void operator|=(type_domain_t& other) const; + void operator|=(const type_domain_t& other) const; + type_domain_t operator|(type_domain_t& other) const; + type_domain_t operator|(const type_domain_t& other) const; + // meet + type_domain_t operator&(const type_domain_t& other) const; + // widening + type_domain_t widen(const type_domain_t& other) const; + // narrowing + type_domain_t narrow(const type_domain_t& other) const; + + //// abstract transformers + void operator()(const Undefined &); + void operator()(const Bin &); + void operator()(const Un &) ; + void operator()(const LoadMapFd &); + void operator()(const Call &); + void operator()(const Exit &); + void operator()(const Jmp &); + void operator()(const Mem &); + void operator()(const Packet &); + void operator()(const LockAdd &); + void operator()(const Assume &); + void operator()(const Assert &); + void operator()(const basic_block_t& bb) { + for (const Instruction& statement : bb) { + std::visit(*this, statement); + } + } + + private: + + void do_load(const Mem&, const Reg&); + void do_mem_store(const Mem&, const Reg&); + +}; // end type_domain_t diff --git a/src/ebpf_proof.cpp b/src/ebpf_proof.cpp index f6f6ba2d0..26a86d2d7 100644 --- a/src/ebpf_proof.cpp +++ b/src/ebpf_proof.cpp @@ -1,9 +1,34 @@ // Copyright (c) Prevail Verifier contributors. // SPDX-License-Identifier: MIT + #include "ebpf_proof.hpp" +type_domain_t get_post(const label_t& node, types_table_t& t) +{ + auto it = t.find(node); + if (it != t.end()) { + std::cout << "type information is not available for basic block\n"; + exit(0); + } + return it->second; +} + +type_domain_t join_all_prevs(const label_t& node, types_table_t& t, const cfg_t& cfg) { + type_domain_t type; + for (const label_t& prev : cfg.prev_nodes(node)) { + // rather than join, it just assigns the type domain of previous basic block; should work for no branch examples + type = get_post(prev, t); + } + return type; +} + bool ebpf_generate_proof(std::ostream& s, const InstructionSeq& prog, const program_info& info, const ebpf_verifier_options_t* options, const crab_results& results) { + + ctx_t ctx(info.type.context_descriptor); + + types_table_t types_table; + if (!results.pass_verify()) { // If the program is not correct then we cannot generate a proof return false; @@ -20,6 +45,19 @@ bool ebpf_generate_proof(std::ostream& s, const InstructionSeq& prog, const prog implement it so that we can be sure that our types are correct. */ - s << "TODO: proof\n"; + type_domain_t type = type_domain_t::setup_entry(ctx.packet_ptrs); + + auto labels = results.cfg.labels(); + for (auto& label : labels) + { + if (label != results.cfg.entry_label()) { + type = join_all_prevs(label, types_table, results.cfg); + } + auto& bb = results.cfg.get_node(label); + type(bb); + + types_table.insert(std::make_pair(label, type)); + } + return false; } diff --git a/src/ebpf_proof.hpp b/src/ebpf_proof.hpp index 65e479758..851d0280a 100644 --- a/src/ebpf_proof.hpp +++ b/src/ebpf_proof.hpp @@ -5,6 +5,23 @@ #include "config.hpp" #include "crab/cfg.hpp" #include "crab_verifier.hpp" +#include "crab/type_domain.hpp" + +using types_table_t = std::map; + +using offset_to_ptr_t = std::unordered_map; + +struct ctx_t { + offset_to_ptr_t packet_ptrs; + + ctx_t(const ebpf_context_descriptor_t* desc) + { + packet_ptrs.insert(std::make_pair(desc->data, crab::ptr_no_off_t(crab::region::T_PACKET))); + packet_ptrs.insert(std::make_pair(desc->end, crab::ptr_no_off_t(crab::region::T_PACKET))); + } +}; + +type_domain_t join_all_prevs(const label_t& label); // - prog is a prevail representation of the eBPF program // - results contains the Crab CFG together with the inferred invariants From a8644d9958d646317d85642b6d661e02ceadc864 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Sun, 2 Jul 2023 02:46:14 -0400 Subject: [PATCH 065/373] Fixed error prints; moved functionality from ebpf_proof to testcase/type_domain Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 51 +++++++++++++++------------------------- src/crab/type_domain.hpp | 33 ++++++++++++++++---------- src/ebpf_proof.cpp | 37 ----------------------------- src/ebpf_proof.hpp | 16 ------------- 4 files changed, 39 insertions(+), 98 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index ec41d1d67..f666b3054 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -5,6 +5,8 @@ #include "crab/type_domain.hpp" +using crab::___print___; + using crab::ptr_t; using crab::ptr_with_off_t; using crab::ptr_no_off_t; @@ -21,8 +23,7 @@ void type_domain_t::operator()(const LockAdd &u) {} void type_domain_t::operator()(const Assume &u) {} void type_domain_t::operator()(const Assert &u) {} - -type_domain_t type_domain_t::setup_entry(const ctx_t& _ctx) { +type_domain_t type_domain_t::setup_entry(const crab::offset_to_ptr_t& _ctx) { type_domain_t inv; @@ -46,8 +47,7 @@ void type_domain_t::operator()(const Bin& bin) { auto it = types.find(src.v); if (it == types.end()) { - std::cout << "type error: assigning an unknown pointer or a number\n"; - exit(0); + CRAB_ERROR("type error: assigning an unknown pointer or a number"); } types.insert(std::make_pair(bin.dst.v, it->second)); @@ -66,19 +66,17 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { auto it = types.find(basereg.v); if (it == types.end()) { - std::cout << "type_error: loading from an unknown pointer, or from number\n"; - exit(0); + CRAB_ERROR("type_error: loading from an unknown pointer, or from number"); } ptr_t type_basereg = it->second; if (std::holds_alternative(type_basereg)) { - std::cout << "type_error: loading from either packet or shared region not allowed\n"; - exit(0); + CRAB_ERROR("type_error: loading from either packet or shared region not allowed"); } ptr_with_off_t type_with_off = std::get(type_basereg); - int load_at = offset+type_with_off.offset; + uint64_t load_at = offset+type_with_off.offset; switch (type_with_off.r) { case crab::region::T_STACK: { @@ -86,8 +84,7 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { auto it = stack.find(load_at); if (it == stack.end()) { - std::cout << "type_error: no field at loaded offset in stack\n"; - exit(0); + CRAB_ERROR("type_error: no field at loaded offset in stack"); } ptr_t type_loaded = it->second; @@ -107,8 +104,7 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { auto it = ctx.find(load_at); if (it == ctx.end()) { - std::cout << "type_error: no field at loaded offset in context\n"; - exit(0); + CRAB_ERROR("type_error: no field at loaded offset in context"); } ptr_no_off_t type_loaded = it->second; @@ -129,16 +125,14 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { auto it = types.find(basereg.v); if (it == types.end()) { - std::cout << "type_error: storing at an unknown pointer, or from number\n"; - exit(0); + CRAB_ERROR("type_error: storing at an unknown pointer, or from number"); } ptr_t type_basereg = it->second; auto it2 = types.find(target_reg.v); if (it2 == types.end()) { - std::cout << "type_error: storing either a number or an unknown pointer\n"; - exit(0); + CRAB_ERROR("type_error: storing either a number or an unknown pointer"); } ptr_t type_stored = it2->second; @@ -146,23 +140,20 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { if (std::holds_alternative(type_stored)) { ptr_with_off_t type_stored_with_off = std::get(type_stored); if (type_stored_with_off.r == crab::region::T_STACK) { - std::cout << "type_error: we do not store stack pointers into stack\n"; - exit(0); + CRAB_ERROR("type_error: we do not store stack pointers into stack"); } } if (std::holds_alternative(type_basereg)) { - std::cout << "type_error: we cannot store pointers into packet or shared\n"; - exit(0); + CRAB_ERROR("type_error: we cannot store pointers into packet or shared"); } ptr_with_off_t type_basereg_with_off = std::get(type_basereg); if (type_basereg_with_off.r == crab::region::T_CTX) { - std::cout << "type_error: we cannot store pointers into ctx\n"; - exit(0); + CRAB_ERROR("type_error: we cannot store pointers into ctx"); } - int store_at = offset+type_basereg_with_off.offset; + uint64_t store_at = offset+type_basereg_with_off.offset; auto it3 = stack.find(store_at); if (it3 == stack.end()) { @@ -171,19 +162,16 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { else { auto type_in_stack = it3->second; if (type_stored.index() != type_in_stack.index()) { - std::cout << "type_error: type being stored is not the same as stored already in stack\n"; - exit(0); + CRAB_ERROR("type_error: type being stored is not the same as stored already in stack"); } if (std::holds_alternative(type_stored)) { if (std::get(type_stored) != std::get(type_in_stack)) { - std::cout << "type_error: type being stored is not the same as stored already in stack\n"; - exit(0); + CRAB_ERROR("type_error: type being stored is not the same as stored already in stack"); } } else { if (std::get(type_stored) != std::get(type_in_stack)) { - std::cout << "type_error: type being stored is not the same as stored already in stack\n"; - exit(0); + CRAB_ERROR("type_error: type being stored is not the same as stored already in stack"); } } } @@ -198,7 +186,6 @@ void type_domain_t::operator()(const Mem& b) { do_mem_store(b, std::get(b.value)); } } else { - std::cout << "Either loading to a number (not allowed) or storing a number (not allowed yet)\n"; - exit(0); + CRAB_ERROR("Either loading to a number (not allowed) or storing a number (not allowed yet)"); } } diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 3a2a1a013..e00799e37 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -16,17 +16,12 @@ enum class region { T_SHARED, }; - -struct ptr_with_off_t; - struct ptr_no_off_t { region r; ptr_no_off_t(region _r) : r(_r) {} ptr_no_off_t(const ptr_no_off_t& p) : r(p.r) {} - bool operator!=(const ptr_with_off_t& p) { - return false; - } + bool operator!=(const ptr_no_off_t& p) { return r != p.r; } @@ -38,9 +33,7 @@ struct ptr_with_off_t { ptr_with_off_t(region _r, int _off) : r(_r), offset(_off) {} ptr_with_off_t(const ptr_with_off_t& p) : r(p.r), offset(p.offset) {} - bool operator!=(const ptr_no_off_t& p) { - return false; - } + bool operator!=(const ptr_with_off_t& p) { return r != p.r; } @@ -53,22 +46,34 @@ struct reg_with_loc_t { using ptr_t = std::variant; -using stack_t = std::unordered_map; +using stack_t = std::unordered_map; using types_t = std::unordered_map; -using ctx_t = std::unordered_map; + + +using offset_to_ptr_t = std::unordered_map; + +struct ctx_t { + offset_to_ptr_t packet_ptrs; + + ctx_t(const ebpf_context_descriptor_t* desc) + { + packet_ptrs.insert(std::make_pair(desc->data, crab::ptr_no_off_t(crab::region::T_PACKET))); + packet_ptrs.insert(std::make_pair(desc->end, crab::ptr_no_off_t(crab::region::T_PACKET))); + } +}; } class type_domain_t final { crab::stack_t stack; crab::types_t types; - crab::ctx_t ctx; + crab::offset_to_ptr_t ctx; public: type_domain_t() {} // eBPF initialization: R1 points to ctx, R10 to stack, etc. - static type_domain_t setup_entry(const crab::ctx_t&); + static type_domain_t setup_entry(const crab::offset_to_ptr_t&); // bottom/top void set_to_top(); void set_to_bottom(); @@ -113,3 +118,5 @@ class type_domain_t final { void do_mem_store(const Mem&, const Reg&); }; // end type_domain_t + +using types_table_t = std::map; diff --git a/src/ebpf_proof.cpp b/src/ebpf_proof.cpp index 26a86d2d7..0ec47b746 100644 --- a/src/ebpf_proof.cpp +++ b/src/ebpf_proof.cpp @@ -3,32 +3,9 @@ #include "ebpf_proof.hpp" -type_domain_t get_post(const label_t& node, types_table_t& t) -{ - auto it = t.find(node); - if (it != t.end()) { - std::cout << "type information is not available for basic block\n"; - exit(0); - } - return it->second; -} - -type_domain_t join_all_prevs(const label_t& node, types_table_t& t, const cfg_t& cfg) { - type_domain_t type; - for (const label_t& prev : cfg.prev_nodes(node)) { - // rather than join, it just assigns the type domain of previous basic block; should work for no branch examples - type = get_post(prev, t); - } - return type; -} - bool ebpf_generate_proof(std::ostream& s, const InstructionSeq& prog, const program_info& info, const ebpf_verifier_options_t* options, const crab_results& results) { - ctx_t ctx(info.type.context_descriptor); - - types_table_t types_table; - if (!results.pass_verify()) { // If the program is not correct then we cannot generate a proof return false; @@ -45,19 +22,5 @@ bool ebpf_generate_proof(std::ostream& s, const InstructionSeq& prog, const prog implement it so that we can be sure that our types are correct. */ - type_domain_t type = type_domain_t::setup_entry(ctx.packet_ptrs); - - auto labels = results.cfg.labels(); - for (auto& label : labels) - { - if (label != results.cfg.entry_label()) { - type = join_all_prevs(label, types_table, results.cfg); - } - auto& bb = results.cfg.get_node(label); - type(bb); - - types_table.insert(std::make_pair(label, type)); - } - return false; } diff --git a/src/ebpf_proof.hpp b/src/ebpf_proof.hpp index 851d0280a..8137e019b 100644 --- a/src/ebpf_proof.hpp +++ b/src/ebpf_proof.hpp @@ -7,22 +7,6 @@ #include "crab_verifier.hpp" #include "crab/type_domain.hpp" -using types_table_t = std::map; - -using offset_to_ptr_t = std::unordered_map; - -struct ctx_t { - offset_to_ptr_t packet_ptrs; - - ctx_t(const ebpf_context_descriptor_t* desc) - { - packet_ptrs.insert(std::make_pair(desc->data, crab::ptr_no_off_t(crab::region::T_PACKET))); - packet_ptrs.insert(std::make_pair(desc->end, crab::ptr_no_off_t(crab::region::T_PACKET))); - } -}; - -type_domain_t join_all_prevs(const label_t& label); - // - prog is a prevail representation of the eBPF program // - results contains the Crab CFG together with the inferred invariants // From 6ea1e0ed12600049824e1d8b47f8faa84f9c7c46 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Sun, 2 Jul 2023 02:49:36 -0400 Subject: [PATCH 066/373] Added shared_ptrs for types and context Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 63 ++++++++++++++++++++++++++-------------- src/crab/type_domain.hpp | 48 +++++++++++++++++++++++++----- 2 files changed, 81 insertions(+), 30 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index f666b3054..30cfde9ac 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -6,11 +6,18 @@ #include "crab/type_domain.hpp" using crab::___print___; - using crab::ptr_t; using crab::ptr_with_off_t; using crab::ptr_no_off_t; using crab::ctx_t; +using crab::types_t; +using crab::reg_with_loc_t; + +void update(std::shared_ptr m, const reg_with_loc_t& key, const ptr_t& value) { + auto it = m->insert(std::make_pair(key, value)); + if (not it.second) it.first->second = value; +} + void type_domain_t::operator()(const Undefined & u) {} void type_domain_t::operator()(const Un &u) {} @@ -23,16 +30,15 @@ void type_domain_t::operator()(const LockAdd &u) {} void type_domain_t::operator()(const Assume &u) {} void type_domain_t::operator()(const Assert &u) {} -type_domain_t type_domain_t::setup_entry(const crab::offset_to_ptr_t& _ctx) { +type_domain_t type_domain_t::setup_entry(std::shared_ptr _ctx, std::shared_ptr _types) { - type_domain_t inv; + type_domain_t inv(label_t::entry); - inv.types.insert(std::make_pair(R1_ARG, ptr_with_off_t(crab::region::T_CTX, 0))); - inv.types.insert(std::make_pair(R10_STACK_POINTER, ptr_with_off_t(crab::region::T_STACK, 512))); + inv.types = _types; + inv.ctx = _ctx; - for (auto& p : _ctx) { - inv.ctx.insert(p); - } + inv.live_vars[R1_ARG] = crab::reg_with_loc_t(R1_ARG, label_t::entry, -1); + inv.live_vars[R10_STACK_POINTER] = crab::reg_with_loc_t(R10_STACK_POINTER, label_t::entry, -1); return inv; } @@ -45,12 +51,15 @@ void type_domain_t::operator()(const Bin& bin) { { case Bin::Op::MOV: { - auto it = types.find(src.v); - if (it == types.end()) { + auto reg_to_look = reg_with_loc_t(src.v, label, -1); + auto it = types->find(reg_to_look); + if (it == types->end()) { CRAB_ERROR("type error: assigning an unknown pointer or a number"); } - types.insert(std::make_pair(bin.dst.v, it->second)); + auto reg = reg_with_loc_t(bin.dst.v, label, -1); + update(types, reg, it->second); + live_vars[bin.dst.v] = reg; } default: @@ -64,8 +73,9 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { int offset = b.access.offset; Reg basereg = b.access.basereg; - auto it = types.find(basereg.v); - if (it == types.end()) { + auto reg_to_look = reg_with_loc_t(basereg.v, label, -1); + auto it = types->find(reg_to_look); + if (it == types->end()) { CRAB_ERROR("type_error: loading from an unknown pointer, or from number"); } @@ -90,25 +100,32 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { if (std::holds_alternative(type_loaded)) { ptr_with_off_t type_loaded_with_off = std::get(type_loaded); - types.insert(std::make_pair(target_reg.v, type_loaded_with_off)); + auto reg = reg_with_loc_t(target_reg.v, label, -1); + update(types, reg, type_loaded_with_off); + live_vars[target_reg.v] = reg; } else { ptr_no_off_t type_loaded_no_off = std::get(type_loaded); - types.insert(std::make_pair(target_reg.v, type_loaded_no_off)); + auto reg = reg_with_loc_t(target_reg.v, label, -1); + update(types, reg, type_loaded_no_off); + live_vars[target_reg.v] = reg; } break; } case crab::region::T_CTX: { - auto it = ctx.find(load_at); + auto ptrs = ctx->packet_ptrs; + auto it = ptrs.find(load_at); - if (it == ctx.end()) { + if (it == ptrs.end()) { CRAB_ERROR("type_error: no field at loaded offset in context"); } ptr_no_off_t type_loaded = it->second; - types.insert(std::make_pair(target_reg.v, type_loaded)); + auto reg = reg_with_loc_t(target_reg.v, label, -1); + update(types, reg, type_loaded); + live_vars[target_reg.v] = reg; break; } @@ -123,15 +140,17 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { int offset = b.access.offset; Reg basereg = b.access.basereg; - auto it = types.find(basereg.v); - if (it == types.end()) { + auto reg_to_look = reg_with_loc_t(basereg.v, label, -1); + auto it = types->find(reg_to_look); + if (it == types->end()) { CRAB_ERROR("type_error: storing at an unknown pointer, or from number"); } ptr_t type_basereg = it->second; - auto it2 = types.find(target_reg.v); - if (it2 == types.end()) { + reg_to_look = reg_with_loc_t(target_reg.v, label, -1); + auto it2 = types->find(reg_to_look); + if (it2 == types->end()) { CRAB_ERROR("type_error: storing either a number or an unknown pointer"); } diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index e00799e37..feed28c70 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -40,14 +40,23 @@ struct ptr_with_off_t { }; struct reg_with_loc_t { - uint8_t r; - int loc; + int r; + std::pair loc; + + reg_with_loc_t() : r(-1), loc(std::make_pair(label_t::entry, -1)) {} + reg_with_loc_t(int _r, const label_t& l, int loc_instr) : r(_r), loc(std::make_pair(l, loc_instr)) {} + + bool operator==(const reg_with_loc_t& other) const { + return (r == other.r && loc.first == other.loc.first && loc.second == other.loc.second); + } }; + using ptr_t = std::variant; using stack_t = std::unordered_map; -using types_t = std::unordered_map; +using types_t = std::unordered_map; +using live_vars_t = std::array; using offset_to_ptr_t = std::unordered_map; @@ -66,14 +75,16 @@ struct ctx_t { class type_domain_t final { crab::stack_t stack; - crab::types_t types; - crab::offset_to_ptr_t ctx; + std::shared_ptr types; + crab::live_vars_t live_vars; + std::shared_ptr ctx; + label_t label; public: - type_domain_t() {} + type_domain_t(const label_t& _l) : label(_l) {} // eBPF initialization: R1 points to ctx, R10 to stack, etc. - static type_domain_t setup_entry(const crab::offset_to_ptr_t&); + static type_domain_t setup_entry(std::shared_ptr, std::shared_ptr); // bottom/top void set_to_top(); void set_to_bottom(); @@ -107,6 +118,7 @@ class type_domain_t final { void operator()(const Assume &); void operator()(const Assert &); void operator()(const basic_block_t& bb) { + label = bb.label(); for (const Instruction& statement : bb) { std::visit(*this, statement); } @@ -119,4 +131,24 @@ class type_domain_t final { }; // end type_domain_t -using types_table_t = std::map; +// adapted from top answer in +// https://stackoverflow.com/questions/17016175/c-unordered-map-using-a-custom-class-type-as-the-key +// works for now but needs to be checked again +template <> +struct std::hash +{ + std::size_t operator()(const crab::reg_with_loc_t& reg) const + { + using std::size_t; + using std::hash; + using std::string; + + // Compute individual hash values for first, + // second and third and combine them using XOR + // and bit shifting: + + return ((hash()(reg.r) + ^ (hash()(reg.loc.first.from) << 1)) >> 1) + ^ (hash()(reg.loc.second) << 1); + } +}; From c304b2af7b78dde438b22efe82ccca8836e0b218 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 3 Jul 2023 16:14:33 -0400 Subject: [PATCH 067/373] Proper lookups using live_vars and global types Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 30cfde9ac..2ceb27026 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -37,8 +37,8 @@ type_domain_t type_domain_t::setup_entry(std::shared_ptr _ctx, std::share inv.types = _types; inv.ctx = _ctx; - inv.live_vars[R1_ARG] = crab::reg_with_loc_t(R1_ARG, label_t::entry, -1); - inv.live_vars[R10_STACK_POINTER] = crab::reg_with_loc_t(R10_STACK_POINTER, label_t::entry, -1); + inv.live_vars[R1_ARG] = reg_with_loc_t(R1_ARG, label_t::entry, -1); + inv.live_vars[R10_STACK_POINTER] = reg_with_loc_t(R10_STACK_POINTER, label_t::entry, -1); return inv; } @@ -51,10 +51,10 @@ void type_domain_t::operator()(const Bin& bin) { { case Bin::Op::MOV: { - auto reg_to_look = reg_with_loc_t(src.v, label, -1); + auto reg_to_look = live_vars[src.v]; // need checks that array actually contains an element, not default value auto it = types->find(reg_to_look); if (it == types->end()) { - CRAB_ERROR("type error: assigning an unknown pointer or a number"); + CRAB_ERROR("type error: assigning an unknown pointer or a number - R", src.v); } auto reg = reg_with_loc_t(bin.dst.v, label, -1); @@ -73,16 +73,16 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { int offset = b.access.offset; Reg basereg = b.access.basereg; - auto reg_to_look = reg_with_loc_t(basereg.v, label, -1); + auto reg_to_look = live_vars[basereg.v]; auto it = types->find(reg_to_look); if (it == types->end()) { - CRAB_ERROR("type_error: loading from an unknown pointer, or from number"); + CRAB_ERROR("type_error: loading from an unknown pointer, or from number - R", basereg.v); } ptr_t type_basereg = it->second; if (std::holds_alternative(type_basereg)) { - CRAB_ERROR("type_error: loading from either packet or shared region not allowed"); + CRAB_ERROR("type_error: loading from either packet or shared region not allowed - R", basereg.v); } ptr_with_off_t type_with_off = std::get(type_basereg); @@ -94,7 +94,7 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { auto it = stack.find(load_at); if (it == stack.end()) { - CRAB_ERROR("type_error: no field at loaded offset in stack"); + CRAB_ERROR("type_error: no field at loaded offset ", load_at, " in stack"); } ptr_t type_loaded = it->second; @@ -119,7 +119,7 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { auto it = ptrs.find(load_at); if (it == ptrs.end()) { - CRAB_ERROR("type_error: no field at loaded offset in context"); + CRAB_ERROR("type_error: no field at loaded offset ", load_at, " in context"); } ptr_no_off_t type_loaded = it->second; @@ -140,18 +140,18 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { int offset = b.access.offset; Reg basereg = b.access.basereg; - auto reg_to_look = reg_with_loc_t(basereg.v, label, -1); + auto reg_to_look = live_vars[basereg.v]; auto it = types->find(reg_to_look); if (it == types->end()) { - CRAB_ERROR("type_error: storing at an unknown pointer, or from number"); + CRAB_ERROR("type_error: storing at an unknown pointer, or from number - R", (int)basereg.v); } ptr_t type_basereg = it->second; - reg_to_look = reg_with_loc_t(target_reg.v, label, -1); + reg_to_look = live_vars[target_reg.v]; auto it2 = types->find(reg_to_look); if (it2 == types->end()) { - CRAB_ERROR("type_error: storing either a number or an unknown pointer"); + CRAB_ERROR("type_error: storing either a number or an unknown pointer - R", (int)target_reg.v); } ptr_t type_stored = it2->second; @@ -159,17 +159,17 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { if (std::holds_alternative(type_stored)) { ptr_with_off_t type_stored_with_off = std::get(type_stored); if (type_stored_with_off.r == crab::region::T_STACK) { - CRAB_ERROR("type_error: we do not store stack pointers into stack"); + CRAB_ERROR("type_error: we cannot store stack pointer, R", (int)target_reg.v, ", into stack"); } } if (std::holds_alternative(type_basereg)) { - CRAB_ERROR("type_error: we cannot store pointers into packet or shared"); + CRAB_ERROR("type_error: we cannot store pointer, R", (int)target_reg.v, ", into packet or shared"); } ptr_with_off_t type_basereg_with_off = std::get(type_basereg); if (type_basereg_with_off.r == crab::region::T_CTX) { - CRAB_ERROR("type_error: we cannot store pointers into ctx"); + CRAB_ERROR("type_error: we cannot store pointer, R", (int)target_reg.v, ", into ctx"); } uint64_t store_at = offset+type_basereg_with_off.offset; @@ -181,16 +181,16 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { else { auto type_in_stack = it3->second; if (type_stored.index() != type_in_stack.index()) { - CRAB_ERROR("type_error: type being stored is not the same as stored already in stack"); + CRAB_ERROR("type_error: type being stored at offset ", store_at, " is not the same as stored already in stack"); } if (std::holds_alternative(type_stored)) { if (std::get(type_stored) != std::get(type_in_stack)) { - CRAB_ERROR("type_error: type being stored is not the same as stored already in stack"); + CRAB_ERROR("type_error: type being stored at offset ", store_at, " is not the same as stored already in stack"); } } else { if (std::get(type_stored) != std::get(type_in_stack)) { - CRAB_ERROR("type_error: type being stored is not the same as stored already in stack"); + CRAB_ERROR("type_error: type being stored at offset ", store_at, " is not the same as stored already in stack"); } } } @@ -205,6 +205,6 @@ void type_domain_t::operator()(const Mem& b) { do_mem_store(b, std::get(b.value)); } } else { - CRAB_ERROR("Either loading to a number (not allowed) or storing a number (not allowed yet)"); + CRAB_ERROR("Either loading to a number (not allowed) or storing a number (not allowed yet) - ", std::get(b.value).v); } } From 76d4752485ae7c11d38446cbd0a3417d021bdbd4 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 3 Jul 2023 18:06:49 -0400 Subject: [PATCH 068/373] Support for join operations; better error printing; refactored; Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 40 +++++++++++---------- src/crab/type_domain.hpp | 77 +++++++++++++++++++++++++++++++++++----- 2 files changed, 91 insertions(+), 26 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 2ceb27026..87df83338 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -19,6 +19,10 @@ void update(std::shared_ptr m, const reg_with_loc_t& key, const ptr_t& } +type_domain_t type_domain_t::operator|(const type_domain_t& other) const& { + return type_domain_t(live_def | other.live_def, stack | other.stack, label, other.types, other.ctx); +} + void type_domain_t::operator()(const Undefined & u) {} void type_domain_t::operator()(const Un &u) {} void type_domain_t::operator()(const LoadMapFd &u) {} @@ -37,8 +41,8 @@ type_domain_t type_domain_t::setup_entry(std::shared_ptr _ctx, std::share inv.types = _types; inv.ctx = _ctx; - inv.live_vars[R1_ARG] = reg_with_loc_t(R1_ARG, label_t::entry, -1); - inv.live_vars[R10_STACK_POINTER] = reg_with_loc_t(R10_STACK_POINTER, label_t::entry, -1); + inv.live_def.vars[R1_ARG] = reg_with_loc_t(R1_ARG, label_t::entry, -1); + inv.live_def.vars[R10_STACK_POINTER] = reg_with_loc_t(R10_STACK_POINTER, label_t::entry, -1); return inv; } @@ -51,15 +55,15 @@ void type_domain_t::operator()(const Bin& bin) { { case Bin::Op::MOV: { - auto reg_to_look = live_vars[src.v]; // need checks that array actually contains an element, not default value + auto reg_to_look = live_def.vars[src.v]; // need checks that array actually contains an element, not default value auto it = types->find(reg_to_look); if (it == types->end()) { - CRAB_ERROR("type error: assigning an unknown pointer or a number - R", src.v); + CRAB_ERROR("type error: assigning an unknown pointer or a number - R", (int)src.v); } auto reg = reg_with_loc_t(bin.dst.v, label, -1); update(types, reg, it->second); - live_vars[bin.dst.v] = reg; + live_def.vars[bin.dst.v] = reg; } default: @@ -73,16 +77,16 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { int offset = b.access.offset; Reg basereg = b.access.basereg; - auto reg_to_look = live_vars[basereg.v]; + auto reg_to_look = live_def.vars[basereg.v]; auto it = types->find(reg_to_look); if (it == types->end()) { - CRAB_ERROR("type_error: loading from an unknown pointer, or from number - R", basereg.v); + CRAB_ERROR("type_error: loading from an unknown pointer, or from number - R", (int)basereg.v); } ptr_t type_basereg = it->second; if (std::holds_alternative(type_basereg)) { - CRAB_ERROR("type_error: loading from either packet or shared region not allowed - R", basereg.v); + CRAB_ERROR("type_error: loading from either packet or shared region not allowed - R", (int)basereg.v); } ptr_with_off_t type_with_off = std::get(type_basereg); @@ -91,9 +95,9 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { switch (type_with_off.r) { case crab::region::T_STACK: { - auto it = stack.find(load_at); + auto it = stack.ptrs.find(load_at); - if (it == stack.end()) { + if (it == stack.ptrs.end()) { CRAB_ERROR("type_error: no field at loaded offset ", load_at, " in stack"); } ptr_t type_loaded = it->second; @@ -102,13 +106,13 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { ptr_with_off_t type_loaded_with_off = std::get(type_loaded); auto reg = reg_with_loc_t(target_reg.v, label, -1); update(types, reg, type_loaded_with_off); - live_vars[target_reg.v] = reg; + live_def.vars[target_reg.v] = reg; } else { ptr_no_off_t type_loaded_no_off = std::get(type_loaded); auto reg = reg_with_loc_t(target_reg.v, label, -1); update(types, reg, type_loaded_no_off); - live_vars[target_reg.v] = reg; + live_def.vars[target_reg.v] = reg; } break; @@ -125,7 +129,7 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { auto reg = reg_with_loc_t(target_reg.v, label, -1); update(types, reg, type_loaded); - live_vars[target_reg.v] = reg; + live_def.vars[target_reg.v] = reg; break; } @@ -140,7 +144,7 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { int offset = b.access.offset; Reg basereg = b.access.basereg; - auto reg_to_look = live_vars[basereg.v]; + auto reg_to_look = live_def.vars[basereg.v]; auto it = types->find(reg_to_look); if (it == types->end()) { CRAB_ERROR("type_error: storing at an unknown pointer, or from number - R", (int)basereg.v); @@ -148,7 +152,7 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { ptr_t type_basereg = it->second; - reg_to_look = live_vars[target_reg.v]; + reg_to_look = live_def.vars[target_reg.v]; auto it2 = types->find(reg_to_look); if (it2 == types->end()) { CRAB_ERROR("type_error: storing either a number or an unknown pointer - R", (int)target_reg.v); @@ -174,9 +178,9 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { uint64_t store_at = offset+type_basereg_with_off.offset; - auto it3 = stack.find(store_at); - if (it3 == stack.end()) { - stack.insert(std::make_pair(store_at, type_stored)); + auto it3 = stack.ptrs.find(store_at); + if (it3 == stack.ptrs.end()) { + stack.ptrs.insert(std::make_pair(store_at, type_stored)); } else { auto type_in_stack = it3->second; diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index feed28c70..656406b19 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -25,6 +25,10 @@ struct ptr_no_off_t { bool operator!=(const ptr_no_off_t& p) { return r != p.r; } + + bool operator==(const ptr_no_off_t& p) { + return r == p.r; + } }; struct ptr_with_off_t { @@ -37,6 +41,10 @@ struct ptr_with_off_t { bool operator!=(const ptr_with_off_t& p) { return r != p.r; } + + bool operator==(const ptr_with_off_t& p) { + return r == p.r; + } }; struct reg_with_loc_t { @@ -47,22 +55,20 @@ struct reg_with_loc_t { reg_with_loc_t(int _r, const label_t& l, int loc_instr) : r(_r), loc(std::make_pair(l, loc_instr)) {} bool operator==(const reg_with_loc_t& other) const { - return (r == other.r && loc.first == other.loc.first && loc.second == other.loc.second); + return (r != -1 && r == other.r && loc.first == other.loc.first && loc.second == other.loc.second); } }; using ptr_t = std::variant; -using stack_t = std::unordered_map; using types_t = std::unordered_map; -using live_vars_t = std::array; - -using offset_to_ptr_t = std::unordered_map; +using offset_to_ptr_no_off_t = std::unordered_map; +using offset_to_ptr_t = std::unordered_map; struct ctx_t { - offset_to_ptr_t packet_ptrs; + offset_to_ptr_no_off_t packet_ptrs; ctx_t(const ebpf_context_descriptor_t* desc) { @@ -70,19 +76,74 @@ struct ctx_t { packet_ptrs.insert(std::make_pair(desc->end, crab::ptr_no_off_t(crab::region::T_PACKET))); } }; + +struct stack_t { + offset_to_ptr_t ptrs; + + stack_t operator|(const stack_t& other) const { + stack_t st{}; + for (auto& e : ptrs) { + auto it = other.ptrs.find(e.first); + if (it == other.ptrs.end()) { + st.ptrs.insert(e); + } + else { + if (it->second.index() == e.second.index()) { + if (std::holds_alternative(it->second)) { + ptr_no_off_t t = std::get(it->second); + ptr_no_off_t t1 = std::get(e.second); + if (t == t1) st.ptrs.insert(e); + } + else { + ptr_with_off_t t = std::get(it->second); + ptr_with_off_t t1 = std::get(e.second); + if (t == t1) st.ptrs.insert(e); + } + } + } + } + + for (auto& e : other.ptrs) { + auto it = ptrs.find(e.first); + if (it == ptrs.end()) { + st.ptrs.insert(e); + } + } + return st; + } +}; + +struct live_def_t { + std::array vars; + + live_def_t operator|(const live_def_t& other) const { + live_def_t v{}; + for (int i = 0; i < vars.size(); i++) { + if (vars[i] == other.vars[i]) { + std::cout << "equal at: " << i << "\n"; + v.vars[i] = vars[i]; + } + } + return v; + } +}; + } class type_domain_t final { crab::stack_t stack; std::shared_ptr types; - crab::live_vars_t live_vars; + crab::live_def_t live_def; std::shared_ptr ctx; label_t label; public: type_domain_t(const label_t& _l) : label(_l) {} + type_domain_t(const crab::live_def_t& _live, const crab::stack_t& _st, const label_t& _l, + std::shared_ptr _types, std::shared_ptr _ctx) + : stack(_st), types(_types), live_def(_live), ctx(_ctx), label(_l) {} // eBPF initialization: R1 points to ctx, R10 to stack, etc. static type_domain_t setup_entry(std::shared_ptr, std::shared_ptr); // bottom/top @@ -96,7 +157,7 @@ class type_domain_t final { void operator|=(type_domain_t& other) const; void operator|=(const type_domain_t& other) const; type_domain_t operator|(type_domain_t& other) const; - type_domain_t operator|(const type_domain_t& other) const; + type_domain_t operator|(const type_domain_t& other) const&; // meet type_domain_t operator&(const type_domain_t& other) const; // widening From 9aecfb8177e5bb68f7ae844ddb35d666d135a073 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 3 Jul 2023 18:08:59 -0400 Subject: [PATCH 069/373] Added limited functionality related to bottom/top; fixed issues with join; refactored Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 71 ++++++++++++++---------- src/crab/type_domain.hpp | 113 ++++++++++++++++++++++++++------------- 2 files changed, 120 insertions(+), 64 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 87df83338..4ffad470d 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -10,17 +10,34 @@ using crab::ptr_t; using crab::ptr_with_off_t; using crab::ptr_no_off_t; using crab::ctx_t; -using crab::types_t; +using crab::all_types_t; using crab::reg_with_loc_t; -void update(std::shared_ptr m, const reg_with_loc_t& key, const ptr_t& value) { +void update(std::shared_ptr m, const reg_with_loc_t& key, const ptr_t& value) { auto it = m->insert(std::make_pair(key, value)); if (not it.second) it.first->second = value; } +type_domain_t type_domain_t::bottom() { + type_domain_t typ(label_t::entry); + typ.set_to_bottom(); + return typ; +} + +bool type_domain_t::is_bottom() const { + return (stack.is_bottom() && types.is_bottom()); +} + +void type_domain_t::set_to_bottom() { + stack.set_to_bottom(); + types.set_to_bottom(); +} type_domain_t type_domain_t::operator|(const type_domain_t& other) const& { - return type_domain_t(live_def | other.live_def, stack | other.stack, label, other.types, other.ctx); + if (is_bottom()) { + return other; + } + return type_domain_t(types | other.types, stack | other.stack, label, other.ctx); } void type_domain_t::operator()(const Undefined & u) {} @@ -34,15 +51,15 @@ void type_domain_t::operator()(const LockAdd &u) {} void type_domain_t::operator()(const Assume &u) {} void type_domain_t::operator()(const Assert &u) {} -type_domain_t type_domain_t::setup_entry(std::shared_ptr _ctx, std::shared_ptr _types) { +type_domain_t type_domain_t::setup_entry(std::shared_ptr _ctx, std::shared_ptr _types) { type_domain_t inv(label_t::entry); - inv.types = _types; + inv.types.all_types = _types; inv.ctx = _ctx; - inv.live_def.vars[R1_ARG] = reg_with_loc_t(R1_ARG, label_t::entry, -1); - inv.live_def.vars[R10_STACK_POINTER] = reg_with_loc_t(R10_STACK_POINTER, label_t::entry, -1); + inv.types.vars[R1_ARG] = reg_with_loc_t(R1_ARG, label_t::entry, -1); + inv.types.vars[R10_STACK_POINTER] = reg_with_loc_t(R10_STACK_POINTER, label_t::entry, -1); return inv; } @@ -55,15 +72,15 @@ void type_domain_t::operator()(const Bin& bin) { { case Bin::Op::MOV: { - auto reg_to_look = live_def.vars[src.v]; // need checks that array actually contains an element, not default value - auto it = types->find(reg_to_look); - if (it == types->end()) { + auto reg_to_look = types.vars[src.v]; // need checks that array actually contains an element, not default value + auto it = types.all_types->find(reg_to_look); + if (it == types.all_types->end()) { CRAB_ERROR("type error: assigning an unknown pointer or a number - R", (int)src.v); } auto reg = reg_with_loc_t(bin.dst.v, label, -1); - update(types, reg, it->second); - live_def.vars[bin.dst.v] = reg; + update(types.all_types, reg, it->second); + types.vars[bin.dst.v] = reg; } default: @@ -77,9 +94,9 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { int offset = b.access.offset; Reg basereg = b.access.basereg; - auto reg_to_look = live_def.vars[basereg.v]; - auto it = types->find(reg_to_look); - if (it == types->end()) { + auto reg_to_look = types.vars[basereg.v]; + auto it = types.all_types->find(reg_to_look); + if (it == types.all_types->end()) { CRAB_ERROR("type_error: loading from an unknown pointer, or from number - R", (int)basereg.v); } @@ -105,14 +122,14 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { if (std::holds_alternative(type_loaded)) { ptr_with_off_t type_loaded_with_off = std::get(type_loaded); auto reg = reg_with_loc_t(target_reg.v, label, -1); - update(types, reg, type_loaded_with_off); - live_def.vars[target_reg.v] = reg; + update(types.all_types, reg, type_loaded_with_off); + types.vars[target_reg.v] = reg; } else { ptr_no_off_t type_loaded_no_off = std::get(type_loaded); auto reg = reg_with_loc_t(target_reg.v, label, -1); - update(types, reg, type_loaded_no_off); - live_def.vars[target_reg.v] = reg; + update(types.all_types, reg, type_loaded_no_off); + types.vars[target_reg.v] = reg; } break; @@ -128,8 +145,8 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { ptr_no_off_t type_loaded = it->second; auto reg = reg_with_loc_t(target_reg.v, label, -1); - update(types, reg, type_loaded); - live_def.vars[target_reg.v] = reg; + update(types.all_types, reg, type_loaded); + types.vars[target_reg.v] = reg; break; } @@ -144,17 +161,17 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { int offset = b.access.offset; Reg basereg = b.access.basereg; - auto reg_to_look = live_def.vars[basereg.v]; - auto it = types->find(reg_to_look); - if (it == types->end()) { + auto reg_to_look = types.vars[basereg.v]; + auto it = types.all_types->find(reg_to_look); + if (it == types.all_types->end()) { CRAB_ERROR("type_error: storing at an unknown pointer, or from number - R", (int)basereg.v); } ptr_t type_basereg = it->second; - reg_to_look = live_def.vars[target_reg.v]; - auto it2 = types->find(reg_to_look); - if (it2 == types->end()) { + reg_to_look = types.vars[target_reg.v]; + auto it2 = types.all_types->find(reg_to_look); + if (it2 == types.all_types->end()) { CRAB_ERROR("type_error: storing either a number or an unknown pointer - R", (int)target_reg.v); } diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 656406b19..602d5ae5d 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -47,6 +47,9 @@ struct ptr_with_off_t { } }; +using ptr_t = std::variant; + + struct reg_with_loc_t { int r; std::pair loc; @@ -55,14 +58,35 @@ struct reg_with_loc_t { reg_with_loc_t(int _r, const label_t& l, int loc_instr) : r(_r), loc(std::make_pair(l, loc_instr)) {} bool operator==(const reg_with_loc_t& other) const { - return (r != -1 && r == other.r && loc.first == other.loc.first && loc.second == other.loc.second); + return (r != -1 && r == other.r); } }; +} +// adapted from top answer in +// https://stackoverflow.com/questions/17016175/c-unordered-map-using-a-custom-class-type-as-the-key +// works for now but needs to be checked again +template <> +struct std::hash +{ + std::size_t operator()(const crab::reg_with_loc_t& reg) const + { + using std::size_t; + using std::hash; + using std::string; -using ptr_t = std::variant; + // Compute individual hash values for first, + // second and third and combine them using XOR + // and bit shifting: + + return ((hash()(reg.r) + ^ (hash()(reg.loc.first.from) << 1)) >> 1) + ^ (hash()(reg.loc.second) << 1); + } +}; + +namespace crab { -using types_t = std::unordered_map; using offset_to_ptr_no_off_t = std::unordered_map; using offset_to_ptr_t = std::unordered_map; @@ -111,21 +135,59 @@ struct stack_t { } return st; } + + void set_to_bottom() { + ptrs.clear(); + } + + bool is_bottom() const { + return ptrs.empty(); + } }; -struct live_def_t { +using all_types_t = std::unordered_map; + +struct types_t { std::array vars; + std::shared_ptr all_types; - live_def_t operator|(const live_def_t& other) const { - live_def_t v{}; + types_t() {} + types_t(const types_t& other) : vars(other.vars), all_types(other.all_types) {} + + types_t operator|(const types_t& other) const { + types_t v{}; for (int i = 0; i < vars.size(); i++) { - if (vars[i] == other.vars[i]) { - std::cout << "equal at: " << i << "\n"; - v.vars[i] = vars[i]; + auto it1 = all_types->find(vars[i]); + auto it2 = other.all_types->find(other.vars[i]); + if (it1 != all_types->end() && it2 != other.all_types->end()) { + if (it1->second.index() == it2->second.index()) { + if (std::holds_alternative(it1->second)) { + if (std::get(it1->second) == std::get(it2->second)) { + v.vars[i] = vars[i]; + } + } + else { + if (std::get(it1->second) == std::get(it2->second)) { + v.vars[i] = vars[i]; + } + } + } } } + v.all_types = all_types; return v; } + + void set_to_bottom() { + vars = {}; + } + + bool is_bottom() const { + for (auto& it : vars) { + if (it.r != -1) return false; + } + return true; + } }; } @@ -133,20 +195,19 @@ struct live_def_t { class type_domain_t final { crab::stack_t stack; - std::shared_ptr types; - crab::live_def_t live_def; + crab::types_t types; std::shared_ptr ctx; label_t label; public: type_domain_t(const label_t& _l) : label(_l) {} - type_domain_t(const crab::live_def_t& _live, const crab::stack_t& _st, const label_t& _l, - std::shared_ptr _types, std::shared_ptr _ctx) - : stack(_st), types(_types), live_def(_live), ctx(_ctx), label(_l) {} + type_domain_t(const crab::types_t& _types, const crab::stack_t& _st, const label_t& _l, std::shared_ptr _ctx) + : stack(_st), types(_types), ctx(_ctx), label(_l) {} // eBPF initialization: R1 points to ctx, R10 to stack, etc. - static type_domain_t setup_entry(std::shared_ptr, std::shared_ptr); + static type_domain_t setup_entry(std::shared_ptr, std::shared_ptr); // bottom/top + static type_domain_t bottom(); void set_to_top(); void set_to_bottom(); bool is_bottom() const; @@ -191,25 +252,3 @@ class type_domain_t final { void do_mem_store(const Mem&, const Reg&); }; // end type_domain_t - -// adapted from top answer in -// https://stackoverflow.com/questions/17016175/c-unordered-map-using-a-custom-class-type-as-the-key -// works for now but needs to be checked again -template <> -struct std::hash -{ - std::size_t operator()(const crab::reg_with_loc_t& reg) const - { - using std::size_t; - using std::hash; - using std::string; - - // Compute individual hash values for first, - // second and third and combine them using XOR - // and bit shifting: - - return ((hash()(reg.r) - ^ (hash()(reg.loc.first.from) << 1)) >> 1) - ^ (hash()(reg.loc.second) << 1); - } -}; From f0bdd5d10221f5258c429e1ec75b7cca3b7e3fc0 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 3 Jul 2023 18:13:26 -0400 Subject: [PATCH 070/373] Refactored Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 12 +---------- src/crab/type_domain.hpp | 45 ++++++++++------------------------------ 2 files changed, 12 insertions(+), 45 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 4ffad470d..9ea81b299 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -201,19 +201,9 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { } else { auto type_in_stack = it3->second; - if (type_stored.index() != type_in_stack.index()) { + if (type_stored != type_in_stack) { CRAB_ERROR("type_error: type being stored at offset ", store_at, " is not the same as stored already in stack"); } - if (std::holds_alternative(type_stored)) { - if (std::get(type_stored) != std::get(type_in_stack)) { - CRAB_ERROR("type_error: type being stored at offset ", store_at, " is not the same as stored already in stack"); - } - } - else { - if (std::get(type_stored) != std::get(type_in_stack)) { - CRAB_ERROR("type_error: type being stored at offset ", store_at, " is not the same as stored already in stack"); - } - } } } diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 602d5ae5d..614882708 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -22,12 +22,12 @@ struct ptr_no_off_t { ptr_no_off_t(region _r) : r(_r) {} ptr_no_off_t(const ptr_no_off_t& p) : r(p.r) {} - bool operator!=(const ptr_no_off_t& p) { - return r != p.r; + friend bool operator==(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { + return (p1.r == p2.r); } - bool operator==(const ptr_no_off_t& p) { - return r == p.r; + friend bool operator!=(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { + return !(p2 == p1); } }; @@ -38,12 +38,12 @@ struct ptr_with_off_t { ptr_with_off_t(region _r, int _off) : r(_r), offset(_off) {} ptr_with_off_t(const ptr_with_off_t& p) : r(p.r), offset(p.offset) {} - bool operator!=(const ptr_with_off_t& p) { - return r != p.r; + friend bool operator==(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { + return (p1.r == p2.r); } - bool operator==(const ptr_with_off_t& p) { - return r == p.r; + friend bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { + return !(p1 == p2); } }; @@ -108,23 +108,9 @@ struct stack_t { stack_t st{}; for (auto& e : ptrs) { auto it = other.ptrs.find(e.first); - if (it == other.ptrs.end()) { + if (it == other.ptrs.end() || it->second == e.second) { st.ptrs.insert(e); } - else { - if (it->second.index() == e.second.index()) { - if (std::holds_alternative(it->second)) { - ptr_no_off_t t = std::get(it->second); - ptr_no_off_t t1 = std::get(e.second); - if (t == t1) st.ptrs.insert(e); - } - else { - ptr_with_off_t t = std::get(it->second); - ptr_with_off_t t1 = std::get(e.second); - if (t == t1) st.ptrs.insert(e); - } - } - } } for (auto& e : other.ptrs) { @@ -160,17 +146,8 @@ struct types_t { auto it1 = all_types->find(vars[i]); auto it2 = other.all_types->find(other.vars[i]); if (it1 != all_types->end() && it2 != other.all_types->end()) { - if (it1->second.index() == it2->second.index()) { - if (std::holds_alternative(it1->second)) { - if (std::get(it1->second) == std::get(it2->second)) { - v.vars[i] = vars[i]; - } - } - else { - if (std::get(it1->second) == std::get(it2->second)) { - v.vars[i] = vars[i]; - } - } + if (it1->second == it2->second) { + v.vars[i] = vars[i]; } } } From 8b0c5548d537e0688bd601573a8611658c34a7b9 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 3 Jul 2023 18:15:10 -0400 Subject: [PATCH 071/373] Support for recognizing types at instruction level Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 12 ++++++------ src/crab/type_domain.hpp | 9 ++++++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 9ea81b299..c87105de5 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -58,8 +58,8 @@ type_domain_t type_domain_t::setup_entry(std::shared_ptr _ctx, std::share inv.types.all_types = _types; inv.ctx = _ctx; - inv.types.vars[R1_ARG] = reg_with_loc_t(R1_ARG, label_t::entry, -1); - inv.types.vars[R10_STACK_POINTER] = reg_with_loc_t(R10_STACK_POINTER, label_t::entry, -1); + inv.types.vars[R1_ARG] = reg_with_loc_t(R1_ARG, label_t::entry, 0); + inv.types.vars[R10_STACK_POINTER] = reg_with_loc_t(R10_STACK_POINTER, label_t::entry, 0); return inv; } @@ -78,7 +78,7 @@ void type_domain_t::operator()(const Bin& bin) { CRAB_ERROR("type error: assigning an unknown pointer or a number - R", (int)src.v); } - auto reg = reg_with_loc_t(bin.dst.v, label, -1); + auto reg = reg_with_loc_t(bin.dst.v, label, m_curr_pos); update(types.all_types, reg, it->second); types.vars[bin.dst.v] = reg; } @@ -121,13 +121,13 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { if (std::holds_alternative(type_loaded)) { ptr_with_off_t type_loaded_with_off = std::get(type_loaded); - auto reg = reg_with_loc_t(target_reg.v, label, -1); + auto reg = reg_with_loc_t(target_reg.v, label, m_curr_pos); update(types.all_types, reg, type_loaded_with_off); types.vars[target_reg.v] = reg; } else { ptr_no_off_t type_loaded_no_off = std::get(type_loaded); - auto reg = reg_with_loc_t(target_reg.v, label, -1); + auto reg = reg_with_loc_t(target_reg.v, label, m_curr_pos); update(types.all_types, reg, type_loaded_no_off); types.vars[target_reg.v] = reg; } @@ -144,7 +144,7 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { } ptr_no_off_t type_loaded = it->second; - auto reg = reg_with_loc_t(target_reg.v, label, -1); + auto reg = reg_with_loc_t(target_reg.v, label, m_curr_pos); update(types.all_types, reg, type_loaded); types.vars[target_reg.v] = reg; break; diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 614882708..59a336472 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -52,10 +52,10 @@ using ptr_t = std::variant; struct reg_with_loc_t { int r; - std::pair loc; + std::pair loc; - reg_with_loc_t() : r(-1), loc(std::make_pair(label_t::entry, -1)) {} - reg_with_loc_t(int _r, const label_t& l, int loc_instr) : r(_r), loc(std::make_pair(l, loc_instr)) {} + reg_with_loc_t() : r(-1), loc(std::make_pair(label_t::entry, 0)) {} + reg_with_loc_t(int _r, const label_t& l, uint32_t loc_instr) : r(_r), loc(std::make_pair(l, loc_instr)) {} bool operator==(const reg_with_loc_t& other) const { return (r != -1 && r == other.r); @@ -175,6 +175,7 @@ class type_domain_t final { crab::types_t types; std::shared_ptr ctx; label_t label; + uint32_t m_curr_pos = 0; public: @@ -217,8 +218,10 @@ class type_domain_t final { void operator()(const Assume &); void operator()(const Assert &); void operator()(const basic_block_t& bb) { + m_curr_pos = 0; label = bb.label(); for (const Instruction& statement : bb) { + m_curr_pos++; std::visit(*this, statement); } } From 6b93ef15045922e1c8c9b7cd858b0781143f2cb6 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Thu, 17 Mar 2022 23:25:03 -0400 Subject: [PATCH 072/373] More structural functionality for stack and context Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 25 +++++++++++------------ src/crab/type_domain.hpp | 43 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index c87105de5..3ba051160 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -112,12 +112,12 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { switch (type_with_off.r) { case crab::region::T_STACK: { - auto it = stack.ptrs.find(load_at); + auto it = stack.find(load_at); - if (it == stack.ptrs.end()) { + if (!it) { CRAB_ERROR("type_error: no field at loaded offset ", load_at, " in stack"); } - ptr_t type_loaded = it->second; + ptr_t type_loaded = it.value(); if (std::holds_alternative(type_loaded)) { ptr_with_off_t type_loaded_with_off = std::get(type_loaded); @@ -136,13 +136,12 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { } case crab::region::T_CTX: { - auto ptrs = ctx->packet_ptrs; - auto it = ptrs.find(load_at); + auto it = ctx->find(load_at); - if (it == ptrs.end()) { + if (!it) { CRAB_ERROR("type_error: no field at loaded offset ", load_at, " in context"); } - ptr_no_off_t type_loaded = it->second; + ptr_no_off_t type_loaded = it.value(); auto reg = reg_with_loc_t(target_reg.v, label, m_curr_pos); update(types.all_types, reg, type_loaded); @@ -195,16 +194,16 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { uint64_t store_at = offset+type_basereg_with_off.offset; - auto it3 = stack.ptrs.find(store_at); - if (it3 == stack.ptrs.end()) { - stack.ptrs.insert(std::make_pair(store_at, type_stored)); - } - else { - auto type_in_stack = it3->second; + auto it3 = stack.find(store_at); + if (it3) { + auto type_in_stack = it3.value(); if (type_stored != type_in_stack) { CRAB_ERROR("type_error: type being stored at offset ", store_at, " is not the same as stored already in stack"); } } + else { + stack.insert(store_at, type_stored); + } } void type_domain_t::operator()(const Mem& b) { diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 59a336472..259fe61ee 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -92,17 +92,30 @@ using offset_to_ptr_no_off_t = std::unordered_map; using offset_to_ptr_t = std::unordered_map; struct ctx_t { + private: offset_to_ptr_no_off_t packet_ptrs; + public: ctx_t(const ebpf_context_descriptor_t* desc) { packet_ptrs.insert(std::make_pair(desc->data, crab::ptr_no_off_t(crab::region::T_PACKET))); packet_ptrs.insert(std::make_pair(desc->end, crab::ptr_no_off_t(crab::region::T_PACKET))); } + + std::optional find(int key) const { + auto it = packet_ptrs.find(key); + if (it == packet_ptrs.end()) return {}; + return it->second; + } }; struct stack_t { + private: offset_to_ptr_t ptrs; + bool _is_bottom; + + public: + explicit stack_t(bool is_bottom = false) : _is_bottom(is_bottom) {} stack_t operator|(const stack_t& other) const { stack_t st{}; @@ -123,19 +136,43 @@ struct stack_t { } void set_to_bottom() { - ptrs.clear(); + this->~stack_t(); + new (this) stack_t(true); } - bool is_bottom() const { + void set_to_top() { + this->~stack_t(); + new (this) stack_t(false); + } + + static stack_t bottom() { return stack_t(true); } + + static stack_t top() { return stack_t(false); } + + bool is_bottom() const { return _is_bottom; } + + bool is_top() const { + if (_is_bottom) + return false; return ptrs.empty(); } + + offset_to_ptr_t get_ptrs() const { return ptrs; } + + void insert(int key, ptr_t value) { ptrs.insert(std::make_pair(key, value)); } + + std::optional find(int key) { + auto it = ptrs.find(key); + if (it == ptrs.end()) return {}; + return it->second; + } }; using all_types_t = std::unordered_map; struct types_t { std::array vars; - std::shared_ptr all_types; + std::shared_ptr all_types; types_t() {} types_t(const types_t& other) : vars(other.vars), all_types(other.all_types) {} From c44faafa8100d821f57faaa5acd11ed95b3814cb Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 3 Jul 2023 18:17:29 -0400 Subject: [PATCH 073/373] Cleaner API for both types_domain_t and types_t Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 69 +++++++++++++++++----------------------- src/crab/type_domain.hpp | 47 +++++++++++++++++++-------- 2 files changed, 63 insertions(+), 53 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 3ba051160..0d7db1d25 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -12,22 +12,19 @@ using crab::ptr_no_off_t; using crab::ctx_t; using crab::all_types_t; using crab::reg_with_loc_t; +using crab::reg_live_vars_t; +using crab::types_t; -void update(std::shared_ptr m, const reg_with_loc_t& key, const ptr_t& value) { - auto it = m->insert(std::make_pair(key, value)); - if (not it.second) it.first->second = value; +bool type_domain_t::is_bottom() const { + return (stack.is_bottom() && types.is_bottom()); } type_domain_t type_domain_t::bottom() { - type_domain_t typ(label_t::entry); + type_domain_t typ; typ.set_to_bottom(); return typ; } -bool type_domain_t::is_bottom() const { - return (stack.is_bottom() && types.is_bottom()); -} - void type_domain_t::set_to_bottom() { stack.set_to_bottom(); types.set_to_bottom(); @@ -51,15 +48,18 @@ void type_domain_t::operator()(const LockAdd &u) {} void type_domain_t::operator()(const Assume &u) {} void type_domain_t::operator()(const Assert &u) {} -type_domain_t type_domain_t::setup_entry(std::shared_ptr _ctx, std::shared_ptr _types) { +type_domain_t type_domain_t::setup_entry(std::shared_ptr _ctx, std::shared_ptr all_types) { + + reg_live_vars_t vars; + types_t typ(vars, all_types, true); - type_domain_t inv(label_t::entry); + auto r1 = reg_with_loc_t(R1_ARG, label_t::entry, 0); + auto r10 = reg_with_loc_t(R10_STACK_POINTER, label_t::entry, 0); - inv.types.all_types = _types; - inv.ctx = _ctx; + typ.insert(R1_ARG, r1, ptr_with_off_t(crab::region::T_CTX, 0)); + typ.insert(R10_STACK_POINTER, r10, ptr_with_off_t(crab::region::T_STACK, 512)); - inv.types.vars[R1_ARG] = reg_with_loc_t(R1_ARG, label_t::entry, 0); - inv.types.vars[R10_STACK_POINTER] = reg_with_loc_t(R10_STACK_POINTER, label_t::entry, 0); + type_domain_t inv(typ, crab::stack_t::bottom(), label_t::entry, _ctx); return inv; } @@ -72,15 +72,13 @@ void type_domain_t::operator()(const Bin& bin) { { case Bin::Op::MOV: { - auto reg_to_look = types.vars[src.v]; // need checks that array actually contains an element, not default value - auto it = types.all_types->find(reg_to_look); - if (it == types.all_types->end()) { + auto it = types.find(src.v); + if (!it) { CRAB_ERROR("type error: assigning an unknown pointer or a number - R", (int)src.v); } auto reg = reg_with_loc_t(bin.dst.v, label, m_curr_pos); - update(types.all_types, reg, it->second); - types.vars[bin.dst.v] = reg; + types.insert(bin.dst.v, reg, it.value()); } default: @@ -94,13 +92,11 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { int offset = b.access.offset; Reg basereg = b.access.basereg; - auto reg_to_look = types.vars[basereg.v]; - auto it = types.all_types->find(reg_to_look); - if (it == types.all_types->end()) { + auto it = types.find(basereg.v); + if (!it) { CRAB_ERROR("type_error: loading from an unknown pointer, or from number - R", (int)basereg.v); } - - ptr_t type_basereg = it->second; + ptr_t type_basereg = it.value(); if (std::holds_alternative(type_basereg)) { CRAB_ERROR("type_error: loading from either packet or shared region not allowed - R", (int)basereg.v); @@ -122,14 +118,12 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { if (std::holds_alternative(type_loaded)) { ptr_with_off_t type_loaded_with_off = std::get(type_loaded); auto reg = reg_with_loc_t(target_reg.v, label, m_curr_pos); - update(types.all_types, reg, type_loaded_with_off); - types.vars[target_reg.v] = reg; + types.insert(target_reg.v, reg, type_loaded_with_off); } else { ptr_no_off_t type_loaded_no_off = std::get(type_loaded); auto reg = reg_with_loc_t(target_reg.v, label, m_curr_pos); - update(types.all_types, reg, type_loaded_no_off); - types.vars[target_reg.v] = reg; + types.insert(target_reg.v, reg, type_loaded_no_off); } break; @@ -144,8 +138,7 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { ptr_no_off_t type_loaded = it.value(); auto reg = reg_with_loc_t(target_reg.v, label, m_curr_pos); - update(types.all_types, reg, type_loaded); - types.vars[target_reg.v] = reg; + types.insert(target_reg.v, reg, type_loaded); break; } @@ -160,21 +153,17 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { int offset = b.access.offset; Reg basereg = b.access.basereg; - auto reg_to_look = types.vars[basereg.v]; - auto it = types.all_types->find(reg_to_look); - if (it == types.all_types->end()) { + auto it = types.find(basereg.v); + if (!it) { CRAB_ERROR("type_error: storing at an unknown pointer, or from number - R", (int)basereg.v); } + ptr_t type_basereg = it.value(); - ptr_t type_basereg = it->second; - - reg_to_look = types.vars[target_reg.v]; - auto it2 = types.all_types->find(reg_to_look); - if (it2 == types.all_types->end()) { + auto it2 = types.find(target_reg.v); + if (!it2) { CRAB_ERROR("type_error: storing either a number or an unknown pointer - R", (int)target_reg.v); } - - ptr_t type_stored = it2->second; + ptr_t type_stored = it2.value(); if (std::holds_alternative(type_stored)) { ptr_with_off_t type_stored_with_off = std::get(type_stored); diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 259fe61ee..ce6379f7c 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -169,45 +169,66 @@ struct stack_t { }; using all_types_t = std::unordered_map; +using reg_live_vars_t = std::array; struct types_t { - std::array vars; + private: + reg_live_vars_t vars; std::shared_ptr all_types; + bool _is_bottom = false; - types_t() {} - types_t(const types_t& other) : vars(other.vars), all_types(other.all_types) {} + public: + types_t(bool is_bottom = false) : _is_bottom(is_bottom) {} + explicit types_t(reg_live_vars_t _vars, std::shared_ptr _all_types, bool is_bottom = false) + : vars(_vars), all_types(_all_types), _is_bottom(is_bottom) {} types_t operator|(const types_t& other) const { - types_t v{}; + reg_live_vars_t _vars; for (int i = 0; i < vars.size(); i++) { auto it1 = all_types->find(vars[i]); auto it2 = other.all_types->find(other.vars[i]); if (it1 != all_types->end() && it2 != other.all_types->end()) { if (it1->second == it2->second) { - v.vars[i] = vars[i]; + _vars[i] = vars[i]; } } } - v.all_types = all_types; + + types_t v(_vars, all_types, false); return v; } void set_to_bottom() { - vars = {}; + this->~types_t(); + new (this) types_t(true); } - bool is_bottom() const { - for (auto& it : vars) { - if (it.r != -1) return false; - } + bool is_bottom() const { return _is_bottom; } + + bool is_top() const { + if (_is_bottom) + return false; return true; } + + void insert(uint32_t reg, const reg_with_loc_t& reg_with_loc, const ptr_t& type) { + auto it = all_types->insert(std::make_pair(reg_with_loc, type)); + if (not it.second) it.first->second = type; + vars[reg] = reg_with_loc; + } + + std::optional find(uint32_t key) { + auto reg = vars[key]; + auto it = all_types->find(reg); + if (it == all_types->end()) return {}; + return it->second; + } }; } class type_domain_t final { - + private: crab::stack_t stack; crab::types_t types; std::shared_ptr ctx; @@ -216,7 +237,7 @@ class type_domain_t final { public: - type_domain_t(const label_t& _l) : label(_l) {} + type_domain_t() : label(label_t::entry) {} type_domain_t(const crab::types_t& _types, const crab::stack_t& _st, const label_t& _l, std::shared_ptr _ctx) : stack(_st), types(_types), ctx(_ctx), label(_l) {} // eBPF initialization: R1 points to ctx, R10 to stack, etc. From cf341fa40937d6b7362d78ad94591d4b4ec0f190 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 3 Jul 2023 18:19:48 -0400 Subject: [PATCH 074/373] Support for printing the state after each statement Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 8 ++++ src/crab/type_domain.hpp | 83 +++++++++++++++++++++++++++++++++++----- 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 0d7db1d25..b74b15f4c 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -37,6 +37,12 @@ type_domain_t type_domain_t::operator|(const type_domain_t& other) const& { return type_domain_t(types | other.types, stack | other.stack, label, other.ctx); } +std::ostream& operator<<(std::ostream& o, const type_domain_t& t) { + o << t.types; + o << "\t" << t.stack << "\n"; + return o; +} + void type_domain_t::operator()(const Undefined & u) {} void type_domain_t::operator()(const Un &u) {} void type_domain_t::operator()(const LoadMapFd &u) {} @@ -66,6 +72,7 @@ type_domain_t type_domain_t::setup_entry(std::shared_ptr _ctx, std::share void type_domain_t::operator()(const Bin& bin) { + std::cout << " " << bin << "\n"; if (std::holds_alternative(bin.v)) { Reg src = std::get(bin.v); switch (bin.op) @@ -197,6 +204,7 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { void type_domain_t::operator()(const Mem& b) { + std::cout << " " << b << "\n"; if (std::holds_alternative(b.value)) { if (b.is_load) { do_load(b, std::get(b.value)); diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index ce6379f7c..ee14e4abc 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -13,9 +13,29 @@ enum class region { T_CTX, T_STACK, T_PACKET, - T_SHARED, + T_SHARED }; +std::string get_region(const region& r) { + switch (r) { + case region::T_CTX: + return "ctx"; + case region::T_STACK: + return "stack"; + case region::T_PACKET: + return "packet"; + case region::T_SHARED: + return "shared"; + default: + return "undefined"; + } +} + +std::ostream& operator<<(std::ostream& o, const region& t) { + o << static_cast::type>(t); + return o; +} + struct ptr_no_off_t { region r; @@ -29,6 +49,10 @@ struct ptr_no_off_t { friend bool operator!=(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { return !(p2 == p1); } + + friend std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p) { + return o << "{" << get_region(p.r) << "}"; + } }; struct ptr_with_off_t { @@ -45,6 +69,10 @@ struct ptr_with_off_t { friend bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { return !(p1 == p2); } + + friend std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p) { + return o << "{" << get_region(p.r) << ", " << p.offset << "}"; + } }; using ptr_t = std::variant; @@ -166,6 +194,22 @@ struct stack_t { if (it == ptrs.end()) return {}; return it->second; } + + friend std::ostream& operator<<(std::ostream& o, const stack_t& st) { + o << "Stack: {"; + for (auto s : st.get_ptrs()) { + o << s.first << ": "; + if (std::holds_alternative(s.second)) { + auto t = std::get(s.second); + o << t << ", "; + } + else { + auto t = std::get(s.second); + o << t << ", "; + } + } + return o << "}"; + } }; using all_types_t = std::unordered_map; @@ -205,24 +249,42 @@ struct types_t { bool is_bottom() const { return _is_bottom; } - bool is_top() const { - if (_is_bottom) - return false; - return true; - } - void insert(uint32_t reg, const reg_with_loc_t& reg_with_loc, const ptr_t& type) { auto it = all_types->insert(std::make_pair(reg_with_loc, type)); if (not it.second) it.first->second = type; vars[reg] = reg_with_loc; } - std::optional find(uint32_t key) { - auto reg = vars[key]; + std::optional find(const reg_with_loc_t& reg) const { auto it = all_types->find(reg); if (it == all_types->end()) return {}; return it->second; } + + std::optional find(uint32_t key) const { + auto reg = vars[key]; + return find(reg); + } + + reg_live_vars_t get_vars() const { return vars; } + + friend std::ostream& operator<<(std::ostream& o, const types_t& typ) { + for (const auto& v : typ.get_vars()) { + auto it = typ.find(v); + if (it) { + o << "\ttype of r" << v.r << ": "; + if (std::holds_alternative(it.value())) { + auto t = std::get(it.value()); + o << t << "\n"; + } + else { + auto t = std::get(it.value()); + o << t << "\n"; + } + } + } + return o; + } }; } @@ -253,6 +315,7 @@ class type_domain_t final { // join void operator|=(type_domain_t& other) const; void operator|=(const type_domain_t& other) const; + friend std::ostream& operator<<(std::ostream&, const type_domain_t&); type_domain_t operator|(type_domain_t& other) const; type_domain_t operator|(const type_domain_t& other) const&; // meet @@ -278,9 +341,11 @@ class type_domain_t final { void operator()(const basic_block_t& bb) { m_curr_pos = 0; label = bb.label(); + std::cout << label << ": \n"; for (const Instruction& statement : bb) { m_curr_pos++; std::visit(*this, statement); + std::cout << *this << "\n"; } } From 2d4e327a8d15f89f0d697250e557c3315d9faab3 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 22 Mar 2022 11:56:42 -0400 Subject: [PATCH 075/373] Minor fix for comparing two types correctly Signed-off-by: Ameer Hamza --- src/crab/type_domain.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index ee14e4abc..749236a49 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -63,7 +63,7 @@ struct ptr_with_off_t { ptr_with_off_t(const ptr_with_off_t& p) : r(p.r), offset(p.offset) {} friend bool operator==(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { - return (p1.r == p2.r); + return (p1.r == p2.r && p1.offset == p2.offset); } friend bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { From e8b582c0253f18eb8ed12ce10a22b5d3ef55f125 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 3 Jul 2023 18:25:06 -0400 Subject: [PATCH 076/373] Attempt to fix Github Actions build Signed-off-by: Ameer Hamza --- src/crab/type_domain.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 749236a49..d9f3d4d3d 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -16,7 +16,7 @@ enum class region { T_SHARED }; -std::string get_region(const region& r) { +inline std::string get_region(const region& r) { switch (r) { case region::T_CTX: return "ctx"; @@ -31,7 +31,7 @@ std::string get_region(const region& r) { } } -std::ostream& operator<<(std::ostream& o, const region& t) { +inline std::ostream& operator<<(std::ostream& o, const region& t) { o << static_cast::type>(t); return o; } From 750aed2e6354740b3df771b08c58060ffedbf491 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 3 Jul 2023 18:35:22 -0400 Subject: [PATCH 077/373] Majority of revisions required in review Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 76 ++++++------ src/crab/type_domain.hpp | 241 ++++++++++++++++++++------------------- 2 files changed, 170 insertions(+), 147 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index b74b15f4c..492f91898 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -10,13 +10,18 @@ using crab::ptr_t; using crab::ptr_with_off_t; using crab::ptr_no_off_t; using crab::ctx_t; -using crab::all_types_t; +using crab::global_type_env_t; using crab::reg_with_loc_t; -using crab::reg_live_vars_t; -using crab::types_t; +using crab::live_registers_t; +using crab::register_types_t; + bool type_domain_t::is_bottom() const { - return (stack.is_bottom() && types.is_bottom()); + return (m_stack.is_bottom() || m_types.is_bottom()); +} + +bool type_domain_t::is_top() const { + return (m_stack.is_top() || m_types.is_top()); } type_domain_t type_domain_t::bottom() { @@ -26,20 +31,23 @@ type_domain_t type_domain_t::bottom() { } void type_domain_t::set_to_bottom() { - stack.set_to_bottom(); - types.set_to_bottom(); + m_stack.set_to_bottom(); + m_types.set_to_bottom(); } type_domain_t type_domain_t::operator|(const type_domain_t& other) const& { - if (is_bottom()) { + if (is_bottom() || other.is_top()) { return other; } - return type_domain_t(types | other.types, stack | other.stack, label, other.ctx); + else if (other.is_bottom() || is_top()) { + return *this; + } + return type_domain_t(m_types | std::move(other.m_types), m_stack | std::move(other.m_stack), m_label, other.m_ctx); } std::ostream& operator<<(std::ostream& o, const type_domain_t& t) { - o << t.types; - o << "\t" << t.stack << "\n"; + o << t.m_types; + o << "\t" << t.m_stack << "\n"; return o; } @@ -54,10 +62,14 @@ void type_domain_t::operator()(const LockAdd &u) {} void type_domain_t::operator()(const Assume &u) {} void type_domain_t::operator()(const Assert &u) {} -type_domain_t type_domain_t::setup_entry(std::shared_ptr _ctx, std::shared_ptr all_types) { +type_domain_t type_domain_t::setup_entry() { + + ebpf_context_descriptor_t context_descriptor{0, 0, 4, -1}; + std::shared_ptr ctx = std::make_shared(&context_descriptor); + std::shared_ptr all_types = std::make_shared(); - reg_live_vars_t vars; - types_t typ(vars, all_types, true); + live_registers_t vars; + register_types_t typ(std::move(vars), all_types, true); auto r1 = reg_with_loc_t(R1_ARG, label_t::entry, 0); auto r10 = reg_with_loc_t(R10_STACK_POINTER, label_t::entry, 0); @@ -65,7 +77,7 @@ type_domain_t type_domain_t::setup_entry(std::shared_ptr _ctx, std::share typ.insert(R1_ARG, r1, ptr_with_off_t(crab::region::T_CTX, 0)); typ.insert(R10_STACK_POINTER, r10, ptr_with_off_t(crab::region::T_STACK, 512)); - type_domain_t inv(typ, crab::stack_t::bottom(), label_t::entry, _ctx); + type_domain_t inv(std::move(typ), crab::stack_t::bottom(), label_t::entry, ctx); return inv; } @@ -79,13 +91,13 @@ void type_domain_t::operator()(const Bin& bin) { { case Bin::Op::MOV: { - auto it = types.find(src.v); + auto it = m_types.find(src.v); if (!it) { CRAB_ERROR("type error: assigning an unknown pointer or a number - R", (int)src.v); } - auto reg = reg_with_loc_t(bin.dst.v, label, m_curr_pos); - types.insert(bin.dst.v, reg, it.value()); + auto reg = reg_with_loc_t(bin.dst.v, m_label, m_curr_pos); + m_types.insert(bin.dst.v, reg, it.value()); } default: @@ -99,7 +111,7 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { int offset = b.access.offset; Reg basereg = b.access.basereg; - auto it = types.find(basereg.v); + auto it = m_types.find(basereg.v); if (!it) { CRAB_ERROR("type_error: loading from an unknown pointer, or from number - R", (int)basereg.v); } @@ -110,12 +122,12 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { } ptr_with_off_t type_with_off = std::get(type_basereg); - uint64_t load_at = offset+type_with_off.offset; + int load_at = offset+type_with_off.offset; switch (type_with_off.r) { case crab::region::T_STACK: { - auto it = stack.find(load_at); + auto it = m_stack.find(load_at); if (!it) { CRAB_ERROR("type_error: no field at loaded offset ", load_at, " in stack"); @@ -124,28 +136,28 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { if (std::holds_alternative(type_loaded)) { ptr_with_off_t type_loaded_with_off = std::get(type_loaded); - auto reg = reg_with_loc_t(target_reg.v, label, m_curr_pos); - types.insert(target_reg.v, reg, type_loaded_with_off); + auto reg = reg_with_loc_t(target_reg.v, m_label, m_curr_pos); + m_types.insert(target_reg.v, reg, type_loaded_with_off); } else { ptr_no_off_t type_loaded_no_off = std::get(type_loaded); - auto reg = reg_with_loc_t(target_reg.v, label, m_curr_pos); - types.insert(target_reg.v, reg, type_loaded_no_off); + auto reg = reg_with_loc_t(target_reg.v, m_label, m_curr_pos); + m_types.insert(target_reg.v, reg, type_loaded_no_off); } break; } case crab::region::T_CTX: { - auto it = ctx->find(load_at); + auto it = m_ctx->find(load_at); if (!it) { CRAB_ERROR("type_error: no field at loaded offset ", load_at, " in context"); } ptr_no_off_t type_loaded = it.value(); - auto reg = reg_with_loc_t(target_reg.v, label, m_curr_pos); - types.insert(target_reg.v, reg, type_loaded); + auto reg = reg_with_loc_t(target_reg.v, m_label, m_curr_pos); + m_types.insert(target_reg.v, reg, type_loaded); break; } @@ -160,13 +172,13 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { int offset = b.access.offset; Reg basereg = b.access.basereg; - auto it = types.find(basereg.v); + auto it = m_types.find(basereg.v); if (!it) { CRAB_ERROR("type_error: storing at an unknown pointer, or from number - R", (int)basereg.v); } ptr_t type_basereg = it.value(); - auto it2 = types.find(target_reg.v); + auto it2 = m_types.find(target_reg.v); if (!it2) { CRAB_ERROR("type_error: storing either a number or an unknown pointer - R", (int)target_reg.v); } @@ -188,9 +200,9 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { CRAB_ERROR("type_error: we cannot store pointer, R", (int)target_reg.v, ", into ctx"); } - uint64_t store_at = offset+type_basereg_with_off.offset; + int store_at = offset+type_basereg_with_off.offset; - auto it3 = stack.find(store_at); + auto it3 = m_stack.find(store_at); if (it3) { auto type_in_stack = it3.value(); if (type_stored != type_in_stack) { @@ -198,7 +210,7 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { } } else { - stack.insert(store_at, type_stored); + m_stack.insert(store_at, type_stored); } } diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index d9f3d4d3d..4f77fca0e 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -24,10 +24,8 @@ inline std::string get_region(const region& r) { return "stack"; case region::T_PACKET: return "packet"; - case region::T_SHARED: - return "shared"; default: - return "undefined"; + return "shared"; } } @@ -39,8 +37,13 @@ inline std::ostream& operator<<(std::ostream& o, const region& t) { struct ptr_no_off_t { region r; + ptr_no_off_t() = default; + ptr_no_off_t(const ptr_no_off_t &) = default; + ptr_no_off_t(ptr_no_off_t &&) = default; + ptr_no_off_t &operator=(const ptr_no_off_t &) = default; + ptr_no_off_t &operator=(ptr_no_off_t &&) = default; + ptr_no_off_t(region _r) : r(_r) {} - ptr_no_off_t(const ptr_no_off_t& p) : r(p.r) {} friend bool operator==(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { return (p1.r == p2.r); @@ -59,8 +62,13 @@ struct ptr_with_off_t { region r; int offset; + ptr_with_off_t() = default; + ptr_with_off_t(const ptr_with_off_t &) = default; + ptr_with_off_t(ptr_with_off_t &&) = default; + ptr_with_off_t &operator=(const ptr_with_off_t &) = default; + ptr_with_off_t &operator=(ptr_with_off_t &&) = default; + ptr_with_off_t(region _r, int _off) : r(_r), offset(_off) {} - ptr_with_off_t(const ptr_with_off_t& p) : r(p.r), offset(p.offset) {} friend bool operator==(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { return (p1.r == p2.r && p1.offset == p2.offset); @@ -76,128 +84,121 @@ struct ptr_with_off_t { }; using ptr_t = std::variant; - +using register_t = uint8_t; struct reg_with_loc_t { - int r; + register_t r; std::pair loc; - reg_with_loc_t() : r(-1), loc(std::make_pair(label_t::entry, 0)) {} - reg_with_loc_t(int _r, const label_t& l, uint32_t loc_instr) : r(_r), loc(std::make_pair(l, loc_instr)) {} + reg_with_loc_t() : r(11), loc(std::make_pair(label_t::entry, 0)) {} + reg_with_loc_t(register_t _r, const label_t& l, uint32_t loc_instr) : r(_r), loc(std::make_pair(l, loc_instr)) {} bool operator==(const reg_with_loc_t& other) const { - return (r != -1 && r == other.r); + return (r < 11 && r == other.r); } -}; -} -// adapted from top answer in -// https://stackoverflow.com/questions/17016175/c-unordered-map-using-a-custom-class-type-as-the-key -// works for now but needs to be checked again -template <> -struct std::hash -{ - std::size_t operator()(const crab::reg_with_loc_t& reg) const - { - using std::size_t; + std::size_t hash() const { + // Similar to boost::hash_combine using std::hash; - using std::string; - // Compute individual hash values for first, - // second and third and combine them using XOR - // and bit shifting: + std::size_t seed = hash()(r); + seed ^= hash()(loc.first.from) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= hash()(loc.second) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - return ((hash()(reg.r) - ^ (hash()(reg.loc.first.from) << 1)) >> 1) - ^ (hash()(reg.loc.second) << 1); + return seed; } }; +} -namespace crab { +namespace std { + template <> + struct std::hash { + std::size_t operator()(const crab::reg_with_loc_t& reg) const { return reg.hash(); } + }; +} +namespace crab { -using offset_to_ptr_no_off_t = std::unordered_map; -using offset_to_ptr_t = std::unordered_map; +class ctx_t { + using offset_to_ptr_no_off_t = std::unordered_map; -struct ctx_t { - private: - offset_to_ptr_no_off_t packet_ptrs; + offset_to_ptr_no_off_t m_packet_ptrs; public: ctx_t(const ebpf_context_descriptor_t* desc) { - packet_ptrs.insert(std::make_pair(desc->data, crab::ptr_no_off_t(crab::region::T_PACKET))); - packet_ptrs.insert(std::make_pair(desc->end, crab::ptr_no_off_t(crab::region::T_PACKET))); + m_packet_ptrs[desc->data] = crab::ptr_no_off_t(crab::region::T_PACKET); + m_packet_ptrs[desc->end] = crab::ptr_no_off_t(crab::region::T_PACKET); } std::optional find(int key) const { - auto it = packet_ptrs.find(key); - if (it == packet_ptrs.end()) return {}; + auto it = m_packet_ptrs.find(key); + if (it == m_packet_ptrs.end()) return {}; return it->second; } }; -struct stack_t { - private: - offset_to_ptr_t ptrs; - bool _is_bottom; +class stack_t { + using offset_to_ptr_t = std::unordered_map; + + offset_to_ptr_t m_ptrs; + bool m_is_bottom; public: - explicit stack_t(bool is_bottom = false) : _is_bottom(is_bottom) {} + stack_t(bool is_bottom = false) : m_is_bottom(is_bottom) {} + stack_t(offset_to_ptr_t && ptrs, bool is_bottom) + : m_ptrs(std::move(ptrs)) , m_is_bottom(is_bottom) {} stack_t operator|(const stack_t& other) const { - stack_t st{}; - for (auto& e : ptrs) { - auto it = other.ptrs.find(e.first); - if (it == other.ptrs.end() || it->second == e.second) { - st.ptrs.insert(e); - } + if (is_bottom() || other.is_top()) { + return other; + } else if (other.is_bottom() || is_top()) { + return *this; } - - for (auto& e : other.ptrs) { - auto it = ptrs.find(e.first); - if (it == ptrs.end()) { - st.ptrs.insert(e); - } + offset_to_ptr_t out_ptrs; + for (auto const&kv: m_ptrs) { + auto it = other.find(kv.first); + if (it && kv.second == it.value()) + out_ptrs.insert(kv); } - return st; + return stack_t(std::move(out_ptrs), false); } void set_to_bottom() { - this->~stack_t(); - new (this) stack_t(true); + m_ptrs.clear(); + m_is_bottom = true; } void set_to_top() { - this->~stack_t(); - new (this) stack_t(false); + m_ptrs.clear(); + m_is_bottom = false; } static stack_t bottom() { return stack_t(true); } static stack_t top() { return stack_t(false); } - bool is_bottom() const { return _is_bottom; } + bool is_bottom() const { return m_is_bottom; } bool is_top() const { - if (_is_bottom) + if (m_is_bottom) return false; - return ptrs.empty(); + return m_ptrs.empty(); } - offset_to_ptr_t get_ptrs() const { return ptrs; } + const offset_to_ptr_t &get_ptrs() { return m_ptrs; } - void insert(int key, ptr_t value) { ptrs.insert(std::make_pair(key, value)); } + void insert(int key, ptr_t value) { m_ptrs.insert(std::make_pair(key, value)); } - std::optional find(int key) { - auto it = ptrs.find(key); - if (it == ptrs.end()) return {}; + std::optional find(int key) const { + auto it = m_ptrs.find(key); + if (it == m_ptrs.end()) return {}; return it->second; } friend std::ostream& operator<<(std::ostream& o, const stack_t& st) { o << "Stack: {"; - for (auto s : st.get_ptrs()) { + for (auto s : st.m_ptrs) { o << s.first << ": "; if (std::holds_alternative(s.second)) { auto t = std::get(s.second); @@ -212,64 +213,75 @@ struct stack_t { } }; -using all_types_t = std::unordered_map; -using reg_live_vars_t = std::array; +using live_registers_t = std::array; +using global_type_env_t = std::unordered_map; -struct types_t { - private: - reg_live_vars_t vars; - std::shared_ptr all_types; - bool _is_bottom = false; +class register_types_t { + live_registers_t m_vars; + std::shared_ptr m_all_types; + bool m_is_bottom = false; public: - types_t(bool is_bottom = false) : _is_bottom(is_bottom) {} - explicit types_t(reg_live_vars_t _vars, std::shared_ptr _all_types, bool is_bottom = false) - : vars(_vars), all_types(_all_types), _is_bottom(is_bottom) {} - - types_t operator|(const types_t& other) const { - reg_live_vars_t _vars; - for (int i = 0; i < vars.size(); i++) { - auto it1 = all_types->find(vars[i]); - auto it2 = other.all_types->find(other.vars[i]); - if (it1 != all_types->end() && it2 != other.all_types->end()) { - if (it1->second == it2->second) { - _vars[i] = vars[i]; - } + register_types_t(bool is_bottom = false) : m_all_types(nullptr), m_is_bottom(is_bottom) {} + explicit register_types_t(live_registers_t&& vars, std::shared_ptr all_types, bool is_bottom = false) + : m_vars(std::move(vars)), m_all_types(all_types), m_is_bottom(is_bottom) {} + + register_types_t operator|(const register_types_t& other) const { + if (is_bottom() || other.is_top()) { + return other; + } else if (other.is_bottom() || is_top()) { + return *this; + } + live_registers_t out_vars; + for (size_t i = 0; i < m_vars.size(); i++) { + auto it1 = find(m_vars[i]); + auto it2 = other.find(other.m_vars[i]); + if (it1 && it2 && it1.value() == it2.value()) { + out_vars[i] = m_vars[i]; } } - types_t v(_vars, all_types, false); - return v; + return register_types_t(std::move(out_vars), m_all_types, false); } void set_to_bottom() { - this->~types_t(); - new (this) types_t(true); + m_vars = live_registers_t{}; + m_is_bottom = true; } - bool is_bottom() const { return _is_bottom; } + void set_to_top() { + m_vars = live_registers_t{}; + m_is_bottom = false; + } - void insert(uint32_t reg, const reg_with_loc_t& reg_with_loc, const ptr_t& type) { - auto it = all_types->insert(std::make_pair(reg_with_loc, type)); + bool is_bottom() const { return m_is_bottom; } + + bool is_top() const { + if (m_is_bottom) { return false; } + return (m_all_types == nullptr || m_vars.empty()); + } + + void insert(register_t reg, const reg_with_loc_t& reg_with_loc, const ptr_t& type) { + auto it = m_all_types->insert(std::make_pair(reg_with_loc, type)); if (not it.second) it.first->second = type; - vars[reg] = reg_with_loc; + m_vars[reg] = reg_with_loc; } std::optional find(const reg_with_loc_t& reg) const { - auto it = all_types->find(reg); - if (it == all_types->end()) return {}; + auto it = m_all_types->find(reg); + if (it == m_all_types->end()) return {}; return it->second; } - std::optional find(uint32_t key) const { - auto reg = vars[key]; + std::optional find(register_t key) const { + auto reg = m_vars[key]; return find(reg); } - reg_live_vars_t get_vars() const { return vars; } + const live_registers_t &get_vars() { return m_vars; } - friend std::ostream& operator<<(std::ostream& o, const types_t& typ) { - for (const auto& v : typ.get_vars()) { + friend std::ostream& operator<<(std::ostream& o, const register_types_t& typ) { + for (const auto& v : typ.m_vars) { auto it = typ.find(v); if (it) { o << "\ttype of r" << v.r << ": "; @@ -290,20 +302,19 @@ struct types_t { } class type_domain_t final { - private: - crab::stack_t stack; - crab::types_t types; - std::shared_ptr ctx; - label_t label; + crab::stack_t m_stack; + crab::register_types_t m_types; + std::shared_ptr m_ctx; + label_t m_label; uint32_t m_curr_pos = 0; public: - type_domain_t() : label(label_t::entry) {} - type_domain_t(const crab::types_t& _types, const crab::stack_t& _st, const label_t& _l, std::shared_ptr _ctx) - : stack(_st), types(_types), ctx(_ctx), label(_l) {} + type_domain_t() : m_label(label_t::entry) {} + type_domain_t(crab::register_types_t&& _types, crab::stack_t&& _st, const label_t& _l, std::shared_ptr _ctx) + : m_stack(std::move(_st)), m_types(std::move(_types)), m_ctx(_ctx), m_label(_l) {} // eBPF initialization: R1 points to ctx, R10 to stack, etc. - static type_domain_t setup_entry(std::shared_ptr, std::shared_ptr); + static type_domain_t setup_entry(); // bottom/top static type_domain_t bottom(); void set_to_top(); @@ -340,8 +351,8 @@ class type_domain_t final { void operator()(const Assert &); void operator()(const basic_block_t& bb) { m_curr_pos = 0; - label = bb.label(); - std::cout << label << ": \n"; + m_label = bb.label(); + std::cout << m_label << ": \n"; for (const Instruction& statement : bb) { m_curr_pos++; std::visit(*this, statement); From bda0f208af8edeffae46b2a7c6cbc840eca56292 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Fri, 25 Mar 2022 11:31:54 -0400 Subject: [PATCH 078/373] Attempt to add specialization for std::equal_to failed Signed-off-by: Ameer Hamza --- src/crab/type_domain.hpp | 55 +++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 4f77fca0e..7d6e1e627 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -45,14 +45,6 @@ struct ptr_no_off_t { ptr_no_off_t(region _r) : r(_r) {} - friend bool operator==(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { - return (p1.r == p2.r); - } - - friend bool operator!=(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { - return !(p2 == p1); - } - friend std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p) { return o << "{" << get_region(p.r) << "}"; } @@ -70,19 +62,28 @@ struct ptr_with_off_t { ptr_with_off_t(region _r, int _off) : r(_r), offset(_off) {} - friend bool operator==(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { - return (p1.r == p2.r && p1.offset == p2.offset); - } - - friend bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { - return !(p1 == p2); - } - friend std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p) { - return o << "{" << get_region(p.r) << ", " << p.offset << "}"; + o << "{" << get_region(p.r) << ", " << p.offset << "}"; + return o; } }; +bool operator==(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { + return (p1.r == p2.r); +} + +bool operator!=(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { + return !(p1 == p2); +} + +bool operator==(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { + return (p1.r == p2.r && p1.offset == p2.offset); +} + +bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { + return !(p1 == p2); +} + using ptr_t = std::variant; using register_t = uint8_t; @@ -110,13 +111,33 @@ struct reg_with_loc_t { }; } + namespace std { template <> struct std::hash { std::size_t operator()(const crab::reg_with_loc_t& reg) const { return reg.hash(); } }; + + // does not seem to work for me + template <> + struct std::equal_to { + constexpr bool operator()(const crab::ptr_t& p1, const crab::ptr_t& p2) const { + if (p1.index() != p2.index()) return false; + if (std::holds_alternative(p1)) { + auto ptr_no_off1 = std::get(p1); + auto ptr_no_off2 = std::get(p2); + return (ptr_no_off1.r == ptr_no_off2.r); + } + else { + auto ptr_with_off1 = std::get(p1); + auto ptr_with_off2 = std::get(p2); + return (ptr_with_off1.r == ptr_with_off2.r && ptr_with_off1.offset == ptr_with_off2.offset); + } + } + }; } + namespace crab { class ctx_t { From 4ec9cfd86af84c669b87827dc7626ef92dec805d Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 3 Jul 2023 19:53:13 -0400 Subject: [PATCH 079/373] Added type_domain in the abstract_domain framework Signed-off-by: Ameer Hamza --- src/crab/abstract_domain.cpp | 2 + src/crab/type_domain.cpp | 59 ++++++++++++++++++++++++--- src/crab/type_domain.hpp | 79 +++++++++++++++++++++++++++--------- src/crab_verifier.cpp | 6 ++- src/main/check.cpp | 3 ++ 5 files changed, 123 insertions(+), 26 deletions(-) diff --git a/src/crab/abstract_domain.cpp b/src/crab/abstract_domain.cpp index 244f770ae..ed580787d 100644 --- a/src/crab/abstract_domain.cpp +++ b/src/crab/abstract_domain.cpp @@ -1,5 +1,6 @@ #include "abstract_domain.hpp" #include "ebpf_domain.hpp" +#include "type_domain.hpp" template abstract_domain_t::abstract_domain_model::abstract_domain_model(Domain abs_val) @@ -283,3 +284,4 @@ std::ostream& operator<<(std::ostream& o, const abstract_domain_t& dom) { // REQUIRED: instantiation for supported domains template abstract_domain_t::abstract_domain_t(crab::ebpf_domain_t); +template abstract_domain_t::abstract_domain_t(type_domain_t); diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 492f91898..61c65081c 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -15,6 +15,7 @@ using crab::reg_with_loc_t; using crab::live_registers_t; using crab::register_types_t; +type_domain_t::type_domain_t(const type_domain_t& o) : m_label(label_t::entry) {} bool type_domain_t::is_bottom() const { return (m_stack.is_bottom() || m_types.is_bottom()); @@ -35,6 +36,19 @@ void type_domain_t::set_to_bottom() { m_types.set_to_bottom(); } +void type_domain_t::set_to_top() { + m_stack.set_to_top(); + m_types.set_to_top(); +} + +bool type_domain_t::operator<=(const type_domain_t& abs) const { + return true; +} + +void type_domain_t::operator|=(const type_domain_t& abs) const {} + +void type_domain_t::operator|=(type_domain_t&& abs) const {} + type_domain_t type_domain_t::operator|(const type_domain_t& other) const& { if (is_bottom() || other.is_top()) { return other; @@ -45,10 +59,39 @@ type_domain_t type_domain_t::operator|(const type_domain_t& other) const& { return type_domain_t(m_types | std::move(other.m_types), m_stack | std::move(other.m_stack), m_label, other.m_ctx); } -std::ostream& operator<<(std::ostream& o, const type_domain_t& t) { - o << t.m_types; - o << "\t" << t.m_stack << "\n"; - return o; +type_domain_t type_domain_t::operator|(type_domain_t&& abs) const { + return std::move(abs); +} + +type_domain_t type_domain_t::operator&(const type_domain_t& abs) const { + return abs; +} + +type_domain_t type_domain_t::widen(const type_domain_t& abs) const { + return abs; +} + +type_domain_t type_domain_t::narrow(const type_domain_t& other) const { + return other; +} + +void type_domain_t::write(std::ostream& os) const { + os << "I am printing types in the start\n"; + os << m_types; + os << "I am printing stack in the start\n"; + os << "\t" << m_stack << "\n"; +} + +std::string type_domain_t::domain_name() const { + return "type_domain"; +} + +int type_domain_t::get_instruction_count_upper_bound() { + return 0; +} + +string_invariant type_domain_t::to_set() { + return string_invariant{}; } void type_domain_t::operator()(const Undefined & u) {} @@ -60,7 +103,10 @@ void type_domain_t::operator()(const Jmp &u) {} void type_domain_t::operator()(const Packet & u) {} void type_domain_t::operator()(const LockAdd &u) {} void type_domain_t::operator()(const Assume &u) {} -void type_domain_t::operator()(const Assert &u) {} +void type_domain_t::operator()(const Assert &u) { + + std::cout << "I am in Assert\n"; +} type_domain_t type_domain_t::setup_entry() { @@ -84,6 +130,7 @@ type_domain_t type_domain_t::setup_entry() { void type_domain_t::operator()(const Bin& bin) { + std::cout << "I am in Bin\n"; std::cout << " " << bin << "\n"; if (std::holds_alternative(bin.v)) { Reg src = std::get(bin.v); @@ -227,3 +274,5 @@ void type_domain_t::operator()(const Mem& b) { CRAB_ERROR("Either loading to a number (not allowed) or storing a number (not allowed yet) - ", std::get(b.value).v); } } + +void type_domain_t::set_require_check(check_require_func_t f) {} diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 7d6e1e627..1625abe87 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -5,7 +5,10 @@ #include +#include "crab/abstract_domain.hpp" #include "crab/cfg.hpp" +#include "linear_constraint.hpp" +#include "string_constraints.hpp" namespace crab { @@ -48,6 +51,15 @@ struct ptr_no_off_t { friend std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p) { return o << "{" << get_region(p.r) << "}"; } + + // temporarily make operators friend functions in order to avoid duplicate symbol errors + friend bool operator==(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { + return (p1.r == p2.r); + } + + friend bool operator!=(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { + return !(p1 == p2); + } }; struct ptr_with_off_t { @@ -66,23 +78,16 @@ struct ptr_with_off_t { o << "{" << get_region(p.r) << ", " << p.offset << "}"; return o; } -}; -bool operator==(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { - return (p1.r == p2.r); -} - -bool operator!=(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { - return !(p1 == p2); -} - -bool operator==(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { - return (p1.r == p2.r && p1.offset == p2.offset); -} + // temporarily make operators friend functions in order to avoid duplicate symbol errors + friend bool operator==(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { + return (p1.r == p2.r && p1.offset == p2.offset); + } -bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { - return !(p1 == p2); -} + friend bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { + return !(p1 == p2); + } +}; using ptr_t = std::variant; using register_t = uint8_t; @@ -169,6 +174,12 @@ class stack_t { stack_t(bool is_bottom = false) : m_is_bottom(is_bottom) {} stack_t(offset_to_ptr_t && ptrs, bool is_bottom) : m_ptrs(std::move(ptrs)) , m_is_bottom(is_bottom) {} + + // stack_t(stack_t&& s) = default; + // stack_t(const stack_t& s) = default; + + // stack_t& operator=(const stack_t& s) = default; + // stack_t& operator=(stack_t&& s) = default; stack_t operator|(const stack_t& other) const { if (is_bottom() || other.is_top()) { @@ -184,7 +195,27 @@ class stack_t { } return stack_t(std::move(out_ptrs), false); } +/* + stack_t operator|(stack_t&& other) && { + if (is_bottom() || other.is_top) + return std::move(other); + if (other.is_bottom() || is_top()) + return std::move(*this); + return ((const stack_t&)*this) | (const stack_t&)other; + } + + stack_t operator|(const stack_t& other) && { + if (is_top() || other.is_bottom()) + return std::move(*this); + return ((const stack_t& + } + stack_t operator|(stack_t&& other) const& { + if (is_bottom() || other.is_top()) + return std::move(other); + return (*this) | (const stack_t&)other; + } +*/ void set_to_bottom() { m_ptrs.clear(); m_is_bottom = true; @@ -246,6 +277,8 @@ class register_types_t { register_types_t(bool is_bottom = false) : m_all_types(nullptr), m_is_bottom(is_bottom) {} explicit register_types_t(live_registers_t&& vars, std::shared_ptr all_types, bool is_bottom = false) : m_vars(std::move(vars)), m_all_types(all_types), m_is_bottom(is_bottom) {} + //register_types_t(register_types_t&& t) = default; + // register_types_t& operator=(register_types_t&& t) = default; register_types_t operator|(const register_types_t& other) const { if (is_bottom() || other.is_top()) { @@ -331,7 +364,10 @@ class type_domain_t final { public: + type_domain_t(const type_domain_t& o); type_domain_t() : m_label(label_t::entry) {} + type_domain_t(type_domain_t&& o) = default; + type_domain_t& operator=(type_domain_t&& o) = default; type_domain_t(crab::register_types_t&& _types, crab::stack_t&& _st, const label_t& _l, std::shared_ptr _ctx) : m_stack(std::move(_st)), m_types(std::move(_types)), m_ctx(_ctx), m_label(_l) {} // eBPF initialization: R1 points to ctx, R10 to stack, etc. @@ -345,10 +381,9 @@ class type_domain_t final { // inclusion bool operator<=(const type_domain_t& other) const; // join - void operator|=(type_domain_t& other) const; + void operator|=(type_domain_t&& other) const; void operator|=(const type_domain_t& other) const; - friend std::ostream& operator<<(std::ostream&, const type_domain_t&); - type_domain_t operator|(type_domain_t& other) const; + type_domain_t operator|(type_domain_t&& other) const; type_domain_t operator|(const type_domain_t& other) const&; // meet type_domain_t operator&(const type_domain_t& other) const; @@ -370,16 +405,20 @@ class type_domain_t final { void operator()(const LockAdd &); void operator()(const Assume &); void operator()(const Assert &); - void operator()(const basic_block_t& bb) { + void operator()(const basic_block_t& bb, bool check_termination) { m_curr_pos = 0; m_label = bb.label(); std::cout << m_label << ": \n"; for (const Instruction& statement : bb) { m_curr_pos++; std::visit(*this, statement); - std::cout << *this << "\n"; } } + void write(std::ostream& os) const; + std::string domain_name() const; + int get_instruction_count_upper_bound(); + string_invariant to_set(); + void set_require_check(check_require_func_t f); private: diff --git a/src/crab_verifier.cpp b/src/crab_verifier.cpp index f7cd3265c..66237a04b 100644 --- a/src/crab_verifier.cpp +++ b/src/crab_verifier.cpp @@ -14,6 +14,7 @@ #include "crab/abstract_domain.hpp" #include "crab/ebpf_domain.hpp" +#include "crab/type_domain.hpp" #include "crab/fwd_analyzer.hpp" #include "crab_utils/lazy_allocator.hpp" @@ -124,7 +125,9 @@ static abstract_domain_t make_initial(const ebpf_verifier_options_t* options) { return abstract_domain_t(entry_inv); } case abstract_domain_kind::TYPE_DOMAIN: { - // TODO + type_domain_t entry_inv = type_domain_t::setup_entry(); + entry_inv.write(std::cout); + return abstract_domain_t(entry_inv); } default: // FIXME: supported abstract domains should be checked in check.cpp @@ -162,6 +165,7 @@ crab_results get_ebpf_report(std::ostream& s, cfg_t& cfg, program_info info, con try { abstract_domain_t entry_dom = make_initial(options); + entry_dom.write(std::cout); // Get dictionaries of pre-invariants and post-invariants for each basic block. auto [pre_invariants, post_invariants] = crab::run_forward_analyzer(cfg, std::move(entry_dom)); diff --git a/src/main/check.cpp b/src/main/check.cpp index 72ee1b2b8..ed9a00c40 100644 --- a/src/main/check.cpp +++ b/src/main/check.cpp @@ -92,6 +92,9 @@ int main(int argc, char** argv) { ebpf_verifier_options.print_invariants = ebpf_verifier_options.print_failures = true; ebpf_verifier_options.allow_division_by_zero = !no_division_by_zero; + if (gen_proof) + ebpf_verifier_options.abstract_domain = abstract_domain_kind::TYPE_DOMAIN; + // Main program if (filename == "@headers") { From 64c865d5e9b2b0a56298d533c51dc1686dbc2224 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 28 Mar 2022 22:39:34 -0400 Subject: [PATCH 080/373] Cleaned up prints in previous code; implemented required features for type_domain_t to be used as an abstract_domain_t; printing types after each statement that is supported; Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 109 ++++++++++++++++++++++++++++++++------- src/crab/type_domain.hpp | 86 +++++++----------------------- src/crab_verifier.cpp | 3 +- 3 files changed, 112 insertions(+), 86 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 61c65081c..6c2554f92 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -15,14 +15,54 @@ using crab::reg_with_loc_t; using crab::live_registers_t; using crab::register_types_t; -type_domain_t::type_domain_t(const type_domain_t& o) : m_label(label_t::entry) {} +void print_pointer(const ptr_t& p) { + if (std::holds_alternative(p)) { + auto t = std::get(p); + std::cout << t; + } + else { + auto t = std::get(p); + std::cout << t; + } +} + +void print_type(register_t r, const ptr_t& p) { + std::cout << ">>>>type of r" << static_cast(r) << ": "; + print_pointer(p); + std::cout << "\n"; +} + +namespace crab { + +std::ostream& operator<<(std::ostream& o, const stack_t& st) { + o << "Stack: {"; + for (auto it = st.m_ptrs.begin(); it != st.m_ptrs.end(); it++) { + auto s = *it; + o << s.first << ": "; + print_pointer(s.second); + if (++it != st.m_ptrs.end()) o << ","; + } + return o << "}"; +} + +std::ostream& operator<<(std::ostream& o, const register_types_t& typ) { + for (const auto& v : typ.m_vars) { + auto it = typ.find(v); + if (it) { + o << "\t"; + print_type(v.r, it.value()); + } + } + return o; +} +} bool type_domain_t::is_bottom() const { return (m_stack.is_bottom() || m_types.is_bottom()); } bool type_domain_t::is_top() const { - return (m_stack.is_top() || m_types.is_top()); + return (m_stack.is_top() && m_types.is_top()); } type_domain_t type_domain_t::bottom() { @@ -45,22 +85,37 @@ bool type_domain_t::operator<=(const type_domain_t& abs) const { return true; } -void type_domain_t::operator|=(const type_domain_t& abs) const {} - -void type_domain_t::operator|=(type_domain_t&& abs) const {} +void type_domain_t::operator|=(const type_domain_t& abs) { + type_domain_t tmp{abs}; + operator|=(std::move(tmp)); +} + +void type_domain_t::operator|=(type_domain_t&& abs) { + if (is_bottom()) { + *this = abs; + return; + } + *this = *this | std::move(abs); +} -type_domain_t type_domain_t::operator|(const type_domain_t& other) const& { +type_domain_t type_domain_t::operator|(const type_domain_t& other) const { if (is_bottom() || other.is_top()) { return other; } else if (other.is_bottom() || is_top()) { return *this; } - return type_domain_t(m_types | std::move(other.m_types), m_stack | std::move(other.m_stack), m_label, other.m_ctx); + return type_domain_t(m_types | other.m_types, m_stack | other.m_stack, m_label, other.m_ctx); } -type_domain_t type_domain_t::operator|(type_domain_t&& abs) const { - return std::move(abs); +type_domain_t type_domain_t::operator|(type_domain_t&& other) const { + if (is_bottom() || other.is_top()) { + return std::move(other); + } + else if (other.is_bottom() || is_top()) { + return *this; + } + return type_domain_t(m_types | std::move(other.m_types), m_stack | std::move(other.m_stack), m_label, other.m_ctx); } type_domain_t type_domain_t::operator&(const type_domain_t& abs) const { @@ -76,9 +131,7 @@ type_domain_t type_domain_t::narrow(const type_domain_t& other) const { } void type_domain_t::write(std::ostream& os) const { - os << "I am printing types in the start\n"; os << m_types; - os << "I am printing stack in the start\n"; os << "\t" << m_stack << "\n"; } @@ -103,10 +156,7 @@ void type_domain_t::operator()(const Jmp &u) {} void type_domain_t::operator()(const Packet & u) {} void type_domain_t::operator()(const LockAdd &u) {} void type_domain_t::operator()(const Assume &u) {} -void type_domain_t::operator()(const Assert &u) { - - std::cout << "I am in Assert\n"; -} +void type_domain_t::operator()(const Assert &u) {} type_domain_t type_domain_t::setup_entry() { @@ -114,6 +164,9 @@ type_domain_t type_domain_t::setup_entry() { std::shared_ptr ctx = std::make_shared(&context_descriptor); std::shared_ptr all_types = std::make_shared(); + std::cout << "Printing types ==============\n\n"; + std::cout << *ctx << "\n"; + live_registers_t vars; register_types_t typ(std::move(vars), all_types, true); @@ -123,6 +176,15 @@ type_domain_t type_domain_t::setup_entry() { typ.insert(R1_ARG, r1, ptr_with_off_t(crab::region::T_CTX, 0)); typ.insert(R10_STACK_POINTER, r10, ptr_with_off_t(crab::region::T_STACK, 512)); + auto it = typ.find(R1_ARG); + if (it) + print_type(R1_ARG, it.value()); + + auto it2 = typ.find(R10_STACK_POINTER); + if (it2) + print_type(R10_STACK_POINTER, it2.value()); + std::cout << "\n"; + type_domain_t inv(std::move(typ), crab::stack_t::bottom(), label_t::entry, ctx); return inv; @@ -130,8 +192,6 @@ type_domain_t type_domain_t::setup_entry() { void type_domain_t::operator()(const Bin& bin) { - std::cout << "I am in Bin\n"; - std::cout << " " << bin << "\n"; if (std::holds_alternative(bin.v)) { Reg src = std::get(bin.v); switch (bin.op) @@ -145,6 +205,13 @@ void type_domain_t::operator()(const Bin& bin) { auto reg = reg_with_loc_t(bin.dst.v, m_label, m_curr_pos); m_types.insert(bin.dst.v, reg, it.value()); + + auto it2 = m_types.find(bin.dst.v); + if (it2) { + std::cout << "\t"; + print_type(bin.dst.v, it2.value()); + std::cout << "\n"; + } } default: @@ -212,6 +279,13 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { assert(false); } } + + auto it2 = m_types.find(target_reg.v); + if (it2) { + std::cout << "\t"; + print_type(target_reg.v, it2.value()); + std::cout << "\n"; + } } void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { @@ -263,7 +337,6 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { void type_domain_t::operator()(const Mem& b) { - std::cout << " " << b << "\n"; if (std::holds_alternative(b.value)) { if (b.is_load) { do_load(b, std::get(b.value)); diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 1625abe87..6916312f3 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -162,6 +162,15 @@ class ctx_t { if (it == m_packet_ptrs.end()) return {}; return it->second; } + + friend std::ostream& operator<<(std::ostream& o, const ctx_t& _ctx) { + + o << "type of context: \n"; + for (const auto& it : _ctx.m_packet_ptrs) { + o << "\tstores at " << it.first << ": " << it.second << "\n"; + } + return o; + } }; class stack_t { @@ -175,12 +184,6 @@ class stack_t { stack_t(offset_to_ptr_t && ptrs, bool is_bottom) : m_ptrs(std::move(ptrs)) , m_is_bottom(is_bottom) {} - // stack_t(stack_t&& s) = default; - // stack_t(const stack_t& s) = default; - - // stack_t& operator=(const stack_t& s) = default; - // stack_t& operator=(stack_t&& s) = default; - stack_t operator|(const stack_t& other) const { if (is_bottom() || other.is_top()) { return other; @@ -195,27 +198,7 @@ class stack_t { } return stack_t(std::move(out_ptrs), false); } -/* - stack_t operator|(stack_t&& other) && { - if (is_bottom() || other.is_top) - return std::move(other); - if (other.is_bottom() || is_top()) - return std::move(*this); - return ((const stack_t&)*this) | (const stack_t&)other; - } - stack_t operator|(const stack_t& other) && { - if (is_top() || other.is_bottom()) - return std::move(*this); - return ((const stack_t& - } - - stack_t operator|(stack_t&& other) const& { - if (is_bottom() || other.is_top()) - return std::move(other); - return (*this) | (const stack_t&)other; - } -*/ void set_to_bottom() { m_ptrs.clear(); m_is_bottom = true; @@ -248,21 +231,7 @@ class stack_t { return it->second; } - friend std::ostream& operator<<(std::ostream& o, const stack_t& st) { - o << "Stack: {"; - for (auto s : st.m_ptrs) { - o << s.first << ": "; - if (std::holds_alternative(s.second)) { - auto t = std::get(s.second); - o << t << ", "; - } - else { - auto t = std::get(s.second); - o << t << ", "; - } - } - return o << "}"; - } + friend std::ostream& operator<<(std::ostream& o, const stack_t& st); }; using live_registers_t = std::array; @@ -277,8 +246,6 @@ class register_types_t { register_types_t(bool is_bottom = false) : m_all_types(nullptr), m_is_bottom(is_bottom) {} explicit register_types_t(live_registers_t&& vars, std::shared_ptr all_types, bool is_bottom = false) : m_vars(std::move(vars)), m_all_types(all_types), m_is_bottom(is_bottom) {} - //register_types_t(register_types_t&& t) = default; - // register_types_t& operator=(register_types_t&& t) = default; register_types_t operator|(const register_types_t& other) const { if (is_bottom() || other.is_top()) { @@ -334,28 +301,13 @@ class register_types_t { const live_registers_t &get_vars() { return m_vars; } - friend std::ostream& operator<<(std::ostream& o, const register_types_t& typ) { - for (const auto& v : typ.m_vars) { - auto it = typ.find(v); - if (it) { - o << "\ttype of r" << v.r << ": "; - if (std::holds_alternative(it.value())) { - auto t = std::get(it.value()); - o << t << "\n"; - } - else { - auto t = std::get(it.value()); - o << t << "\n"; - } - } - } - return o; - } + friend std::ostream& operator<<(std::ostream& o, const register_types_t& p); }; } class type_domain_t final { + crab::stack_t m_stack; crab::register_types_t m_types; std::shared_ptr m_ctx; @@ -364,10 +316,11 @@ class type_domain_t final { public: - type_domain_t(const type_domain_t& o); type_domain_t() : m_label(label_t::entry) {} type_domain_t(type_domain_t&& o) = default; + type_domain_t(const type_domain_t& o) = default; type_domain_t& operator=(type_domain_t&& o) = default; + type_domain_t& operator=(const type_domain_t& o) = default; type_domain_t(crab::register_types_t&& _types, crab::stack_t&& _st, const label_t& _l, std::shared_ptr _ctx) : m_stack(std::move(_st)), m_types(std::move(_types)), m_ctx(_ctx), m_label(_l) {} // eBPF initialization: R1 points to ctx, R10 to stack, etc. @@ -381,10 +334,10 @@ class type_domain_t final { // inclusion bool operator<=(const type_domain_t& other) const; // join - void operator|=(type_domain_t&& other) const; - void operator|=(const type_domain_t& other) const; - type_domain_t operator|(type_domain_t&& other) const; - type_domain_t operator|(const type_domain_t& other) const&; + void operator|=(const type_domain_t& abs); + void operator|=(type_domain_t&& abs); + type_domain_t operator|(const type_domain_t& other) const; + type_domain_t operator|(type_domain_t&& abs) const; // meet type_domain_t operator&(const type_domain_t& other) const; // widening @@ -408,8 +361,9 @@ class type_domain_t final { void operator()(const basic_block_t& bb, bool check_termination) { m_curr_pos = 0; m_label = bb.label(); - std::cout << m_label << ": \n"; + std::cout << m_label << "\n"; for (const Instruction& statement : bb) { + std::cout << " " << statement << "\n"; m_curr_pos++; std::visit(*this, statement); } diff --git a/src/crab_verifier.cpp b/src/crab_verifier.cpp index 66237a04b..8760db327 100644 --- a/src/crab_verifier.cpp +++ b/src/crab_verifier.cpp @@ -126,7 +126,6 @@ static abstract_domain_t make_initial(const ebpf_verifier_options_t* options) { } case abstract_domain_kind::TYPE_DOMAIN: { type_domain_t entry_inv = type_domain_t::setup_entry(); - entry_inv.write(std::cout); return abstract_domain_t(entry_inv); } default: @@ -164,8 +163,8 @@ crab_results get_ebpf_report(std::ostream& s, cfg_t& cfg, program_info info, con try { + s << cfg; abstract_domain_t entry_dom = make_initial(options); - entry_dom.write(std::cout); // Get dictionaries of pre-invariants and post-invariants for each basic block. auto [pre_invariants, post_invariants] = crab::run_forward_analyzer(cfg, std::move(entry_dom)); From 566ccdad29b3720ce7cc295ac14e38b0c47df548 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 29 Mar 2022 11:28:34 -0400 Subject: [PATCH 081/373] Minor refactorings Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 49 ++++++++++++++++++++++++++++------------ src/crab/type_domain.hpp | 22 +++++++++++++----- src/crab_verifier.cpp | 3 ++- 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 6c2554f92..092f8153c 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -130,6 +130,10 @@ type_domain_t type_domain_t::narrow(const type_domain_t& other) const { return other; } +void type_domain_t::operator-=(variable_t var) { + m_types -= var; +} + void type_domain_t::write(std::ostream& os) const { os << m_types; os << "\t" << m_stack << "\n"; @@ -160,8 +164,7 @@ void type_domain_t::operator()(const Assert &u) {} type_domain_t type_domain_t::setup_entry() { - ebpf_context_descriptor_t context_descriptor{0, 0, 4, -1}; - std::shared_ptr ctx = std::make_shared(&context_descriptor); + std::shared_ptr ctx = std::make_shared(global_program_info.type.context_descriptor); std::shared_ptr all_types = std::make_shared(); std::cout << "Printing types ==============\n\n"; @@ -176,13 +179,17 @@ type_domain_t type_domain_t::setup_entry() { typ.insert(R1_ARG, r1, ptr_with_off_t(crab::region::T_CTX, 0)); typ.insert(R10_STACK_POINTER, r10, ptr_with_off_t(crab::region::T_STACK, 512)); + std::cout << "Initial register types:\n"; auto it = typ.find(R1_ARG); - if (it) + if (it) { + std::cout << "\t"; print_type(R1_ARG, it.value()); - + } auto it2 = typ.find(R10_STACK_POINTER); - if (it2) + if (it2) { + std::cout << "\t"; print_type(R10_STACK_POINTER, it2.value()); + } std::cout << "\n"; type_domain_t inv(std::move(typ), crab::stack_t::bottom(), label_t::entry, ctx); @@ -197,10 +204,9 @@ void type_domain_t::operator()(const Bin& bin) { switch (bin.op) { case Bin::Op::MOV: { - auto it = m_types.find(src.v); if (!it) { - CRAB_ERROR("type error: assigning an unknown pointer or a number - R", (int)src.v); + CRAB_ERROR("type error: assigning an unknown pointer or a number - r", (int)src.v); } auto reg = reg_with_loc_t(bin.dst.v, m_label, m_curr_pos); @@ -212,6 +218,9 @@ void type_domain_t::operator()(const Bin& bin) { print_type(bin.dst.v, it2.value()); std::cout << "\n"; } + else { + CRAB_ERROR("Type of r", static_cast(bin.dst.v), " is not being stored"); + } } default: @@ -227,12 +236,12 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { auto it = m_types.find(basereg.v); if (!it) { - CRAB_ERROR("type_error: loading from an unknown pointer, or from number - R", (int)basereg.v); + CRAB_ERROR("type_error: loading from an unknown pointer, or from number - r", (int)basereg.v); } ptr_t type_basereg = it.value(); if (std::holds_alternative(type_basereg)) { - CRAB_ERROR("type_error: loading from either packet or shared region not allowed - R", (int)basereg.v); + CRAB_ERROR("type_error: loading from either packet or shared region not allowed - r", (int)basereg.v); } ptr_with_off_t type_with_off = std::get(type_basereg); @@ -286,43 +295,55 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { print_type(target_reg.v, it2.value()); std::cout << "\n"; } + else { + CRAB_ERROR("Type of r", static_cast(target_reg.v), " is not being stored"); + } } void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { int offset = b.access.offset; Reg basereg = b.access.basereg; + int width = b.access.width; auto it = m_types.find(basereg.v); if (!it) { - CRAB_ERROR("type_error: storing at an unknown pointer, or from number - R", (int)basereg.v); + CRAB_ERROR("type_error: storing at an unknown pointer, or from number - r", (int)basereg.v); } ptr_t type_basereg = it.value(); auto it2 = m_types.find(target_reg.v); if (!it2) { - CRAB_ERROR("type_error: storing either a number or an unknown pointer - R", (int)target_reg.v); + CRAB_ERROR("type_error: storing either a number or an unknown pointer - r", (int)target_reg.v); } ptr_t type_stored = it2.value(); if (std::holds_alternative(type_stored)) { ptr_with_off_t type_stored_with_off = std::get(type_stored); if (type_stored_with_off.r == crab::region::T_STACK) { - CRAB_ERROR("type_error: we cannot store stack pointer, R", (int)target_reg.v, ", into stack"); + CRAB_ERROR("type_error: we cannot store stack pointer, r", (int)target_reg.v, ", into stack"); } } if (std::holds_alternative(type_basereg)) { - CRAB_ERROR("type_error: we cannot store pointer, R", (int)target_reg.v, ", into packet or shared"); + CRAB_ERROR("type_error: we cannot store pointer, r", (int)target_reg.v, ", into packet or shared"); } ptr_with_off_t type_basereg_with_off = std::get(type_basereg); if (type_basereg_with_off.r == crab::region::T_CTX) { - CRAB_ERROR("type_error: we cannot store pointer, R", (int)target_reg.v, ", into ctx"); + CRAB_ERROR("type_error: we cannot store pointer, r", (int)target_reg.v, ", into ctx"); } int store_at = offset+type_basereg_with_off.offset; + for (auto i = store_at; i < store_at+width; i++) { + auto it = m_stack.find(i); + if (it) { + CRAB_ERROR("type_error: type being stored into stack at ", store_at, " is overlapping with already stored\ + at", i); + } + } + auto it3 = m_stack.find(store_at); if (it3) { auto type_in_stack = it3.value(); diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 6916312f3..228a4ae9d 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -153,8 +153,10 @@ class ctx_t { public: ctx_t(const ebpf_context_descriptor_t* desc) { - m_packet_ptrs[desc->data] = crab::ptr_no_off_t(crab::region::T_PACKET); - m_packet_ptrs[desc->end] = crab::ptr_no_off_t(crab::region::T_PACKET); + if (desc->data != -1) + m_packet_ptrs[desc->data] = crab::ptr_no_off_t(crab::region::T_PACKET); + if (desc->end != -1) + m_packet_ptrs[desc->end] = crab::ptr_no_off_t(crab::region::T_PACKET); } std::optional find(int key) const { @@ -165,7 +167,7 @@ class ctx_t { friend std::ostream& operator<<(std::ostream& o, const ctx_t& _ctx) { - o << "type of context: \n"; + o << "type of context: " << (_ctx.m_packet_ptrs.empty() ? "_|_" : "") << "\n"; for (const auto& it : _ctx.m_packet_ptrs) { o << "\tstores at " << it.first << ": " << it.second << "\n"; } @@ -283,8 +285,13 @@ class register_types_t { } void insert(register_t reg, const reg_with_loc_t& reg_with_loc, const ptr_t& type) { - auto it = m_all_types->insert(std::make_pair(reg_with_loc, type)); - if (not it.second) it.first->second = type; + auto it = m_all_types->find(reg_with_loc); + if (it == m_all_types->end()) + m_all_types->insert(std::make_pair(reg_with_loc, type)); + else + it->second = type; + //auto it = m_all_types->insert(std::make_pair(reg_with_loc, type)); + //if (not it.second) it.first->second = type; m_vars[reg] = reg_with_loc; } @@ -344,6 +351,8 @@ class type_domain_t final { type_domain_t widen(const type_domain_t& other) const; // narrowing type_domain_t narrow(const type_domain_t& other) const; + //forget + void operator-=(variable_t var); //// abstract transformers void operator()(const Undefined &); @@ -361,12 +370,13 @@ class type_domain_t final { void operator()(const basic_block_t& bb, bool check_termination) { m_curr_pos = 0; m_label = bb.label(); - std::cout << m_label << "\n"; + std::cout << m_label << ":\n"; for (const Instruction& statement : bb) { std::cout << " " << statement << "\n"; m_curr_pos++; std::visit(*this, statement); } + std::cout << "\n"; } void write(std::ostream& os) const; std::string domain_name() const; diff --git a/src/crab_verifier.cpp b/src/crab_verifier.cpp index 8760db327..306bb8a00 100644 --- a/src/crab_verifier.cpp +++ b/src/crab_verifier.cpp @@ -162,8 +162,9 @@ crab_results get_ebpf_report(std::ostream& s, cfg_t& cfg, program_info info, con thread_local_options = *options; try { - + s << "\nprinting the cfg ==============\n"; s << cfg; + s << "\n"; abstract_domain_t entry_dom = make_initial(options); // Get dictionaries of pre-invariants and post-invariants for each basic block. auto [pre_invariants, post_invariants] = From cbb58f6c0fff7e137a18151a692b14fae26a64fb Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 29 Mar 2022 19:33:02 -0400 Subject: [PATCH 082/373] Fixed computation of types when non-supported operations are present; Fixed printing of types; Refactoring Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 177 ++++++++++++++++++++++++++------------- src/crab/type_domain.hpp | 81 +++++++++++------- src/crab_verifier.cpp | 4 +- 3 files changed, 173 insertions(+), 89 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 092f8153c..968081421 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -15,7 +15,9 @@ using crab::reg_with_loc_t; using crab::live_registers_t; using crab::register_types_t; -void print_pointer(const ptr_t& p) { +static std::string size(int w) { return std::string("u") + std::to_string(w * 8); } + +void print_ptr_type(const ptr_t& p) { if (std::holds_alternative(p)) { auto t = std::get(p); std::cout << t; @@ -27,30 +29,55 @@ void print_pointer(const ptr_t& p) { } void print_type(register_t r, const ptr_t& p) { - std::cout << ">>>>type of r" << static_cast(r) << ": "; - print_pointer(p); - std::cout << "\n"; + std::cout << "r" << static_cast(r) << " : "; + print_ptr_type(p); +} + +void print_annotated(Mem const& b, const ptr_t& p, std::ostream& os_) { + if (b.is_load) { + os_ << " "; + print_type(std::get(b.value).v, p); + os_ << " = "; + } + std::string sign = b.access.offset < 0 ? " - " : " + "; + int offset = std::abs(b.access.offset); + os_ << "*(" << size(b.access.width) << " *)"; + os_ << "(" << b.access.basereg << sign << offset << ")\n"; +} + +void print_annotated(Call const& call, const ptr_t& p, std::ostream& os_) { + os_ << " "; + print_type(0, p); + os_ << " = " << call.name << ":" << call.func << "(...)\n"; } namespace crab { std::ostream& operator<<(std::ostream& o, const stack_t& st) { - o << "Stack: {"; - for (auto it = st.m_ptrs.begin(); it != st.m_ptrs.end(); it++) { - auto s = *it; - o << s.first << ": "; - print_pointer(s.second); - if (++it != st.m_ptrs.end()) o << ","; + o << "Stack: "; + if (st.is_bottom()) + o << "_|_\n"; + else { + o << "{"; + for (auto it = st.m_ptrs.begin(); it != st.m_ptrs.end(); it++) { + auto s = *it; + o << s.first << ": "; + print_ptr_type(s.second); + if (++it != st.m_ptrs.end()) o << ","; + } + o << "}"; } - return o << "}"; + return o; } std::ostream& operator<<(std::ostream& o, const register_types_t& typ) { - for (const auto& v : typ.m_vars) { - auto it = typ.find(v); - if (it) { - o << "\t"; - print_type(v.r, it.value()); + if (typ.is_bottom()) + o << "_|_\n"; + else { + for (const auto& v : *(typ.m_all_types)) { + o << v.first << ": "; + print_ptr_type(v.second); + o << "\n"; } } return o; @@ -72,7 +99,6 @@ type_domain_t type_domain_t::bottom() { } void type_domain_t::set_to_bottom() { - m_stack.set_to_bottom(); m_types.set_to_bottom(); } @@ -130,13 +156,9 @@ type_domain_t type_domain_t::narrow(const type_domain_t& other) const { return other; } -void type_domain_t::operator-=(variable_t var) { - m_types -= var; -} - void type_domain_t::write(std::ostream& os) const { os << m_types; - os << "\t" << m_stack << "\n"; + os << m_stack << "\n"; } std::string type_domain_t::domain_name() const { @@ -151,27 +173,71 @@ string_invariant type_domain_t::to_set() { return string_invariant{}; } -void type_domain_t::operator()(const Undefined & u) {} -void type_domain_t::operator()(const Un &u) {} -void type_domain_t::operator()(const LoadMapFd &u) {} -void type_domain_t::operator()(const Call &u) {} -void type_domain_t::operator()(const Exit &u) {} -void type_domain_t::operator()(const Jmp &u) {} -void type_domain_t::operator()(const Packet & u) {} -void type_domain_t::operator()(const LockAdd &u) {} -void type_domain_t::operator()(const Assume &u) {} -void type_domain_t::operator()(const Assert &u) {} +void type_domain_t::operator()(const Undefined & u) { + std::cout << " " << u << ";\n"; +} +void type_domain_t::operator()(const Un &u) { + std::cout << " " << u << ";\n"; +} +void type_domain_t::operator()(const LoadMapFd &u) { + std::cout << " " << u << ";\n"; + m_types -= u.dst.v; +} +void type_domain_t::operator()(const Call &u) { + register_t r0_reg{R0_RETURN_VALUE}; + if (u.is_map_lookup) { + auto r0 = reg_with_loc_t(r0_reg, m_label, m_curr_pos); + auto type = ptr_no_off_t(crab::region::T_SHARED); + m_types.insert(r0_reg, r0, type); + print_annotated(u, type, std::cout); + } + else { + m_types -= r0_reg; + std::cout << " " << u << ";\n"; + } +} +void type_domain_t::operator()(const Exit &u) { + std::cout << " " << u << ";\n"; +} +void type_domain_t::operator()(const Jmp &u) { + std::cout << " " << u << ";\n"; +} +void type_domain_t::operator()(const Packet & u) { + std::cout << " " << u << ";\n"; + //CRAB_ERROR("type_error: loading from packet region not allowed"); + m_types -= register_t{0}; +} +void type_domain_t::operator()(const LockAdd &u) { + std::cout << " " << u << ";\n"; +} +void type_domain_t::operator()(const Assume &u) { + std::cout << " " << u << ";\n"; +} +void type_domain_t::operator()(const Assert &u) { + std::cout << " " << u << ";\n"; +} + +void print_info() { + std::cout << "\nhow to interpret:\n"; + std::cout << " packet_p = packet pointer\n"; + std::cout << " shared_p = shared pointer\n"; + std::cout << " stack_p = stack pointer at offset n\n"; + std::cout << " ctx_p = context pointer at offset n\n"; + std::cout << " context = _|_ means context contains no elements stored\n\n"; + std::cout << "**************************************************************\n\n"; +} type_domain_t type_domain_t::setup_entry() { + print_info(); + std::shared_ptr ctx = std::make_shared(global_program_info.type.context_descriptor); std::shared_ptr all_types = std::make_shared(); - std::cout << "Printing types ==============\n\n"; std::cout << *ctx << "\n"; live_registers_t vars; - register_types_t typ(std::move(vars), all_types, true); + register_types_t typ(std::move(vars), all_types); auto r1 = reg_with_loc_t(R1_ARG, label_t::entry, 0); auto r10 = reg_with_loc_t(R10_STACK_POINTER, label_t::entry, 0); @@ -182,18 +248,19 @@ type_domain_t type_domain_t::setup_entry() { std::cout << "Initial register types:\n"; auto it = typ.find(R1_ARG); if (it) { - std::cout << "\t"; + std::cout << " "; print_type(R1_ARG, it.value()); + std::cout << "\n"; } auto it2 = typ.find(R10_STACK_POINTER); if (it2) { - std::cout << "\t"; + std::cout << " "; print_type(R10_STACK_POINTER, it2.value()); + std::cout << "\n"; } std::cout << "\n"; - type_domain_t inv(std::move(typ), crab::stack_t::bottom(), label_t::entry, ctx); - + type_domain_t inv(std::move(typ), crab::stack_t::top(), label_t::entry, ctx); return inv; } @@ -211,22 +278,18 @@ void type_domain_t::operator()(const Bin& bin) { auto reg = reg_with_loc_t(bin.dst.v, m_label, m_curr_pos); m_types.insert(bin.dst.v, reg, it.value()); - - auto it2 = m_types.find(bin.dst.v); - if (it2) { - std::cout << "\t"; - print_type(bin.dst.v, it2.value()); - std::cout << "\n"; - } - else { - CRAB_ERROR("Type of r", static_cast(bin.dst.v), " is not being stored"); - } + break; } default: + m_types -= bin.dst.v; break; } } + else { + m_types -= bin.dst.v; + } + std::cout << " " << bin << ";\n"; } void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { @@ -236,11 +299,13 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { auto it = m_types.find(basereg.v); if (!it) { + std::cout << " " << b << "\n"; CRAB_ERROR("type_error: loading from an unknown pointer, or from number - r", (int)basereg.v); } ptr_t type_basereg = it.value(); if (std::holds_alternative(type_basereg)) { + std::cout << " " << b << "\n"; CRAB_ERROR("type_error: loading from either packet or shared region not allowed - r", (int)basereg.v); } @@ -253,6 +318,7 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { auto it = m_stack.find(load_at); if (!it) { + std::cout << " " << b << "\n"; CRAB_ERROR("type_error: no field at loaded offset ", load_at, " in stack"); } ptr_t type_loaded = it.value(); @@ -261,11 +327,13 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { ptr_with_off_t type_loaded_with_off = std::get(type_loaded); auto reg = reg_with_loc_t(target_reg.v, m_label, m_curr_pos); m_types.insert(target_reg.v, reg, type_loaded_with_off); + print_annotated(b, type_loaded_with_off, std::cout); } else { ptr_no_off_t type_loaded_no_off = std::get(type_loaded); auto reg = reg_with_loc_t(target_reg.v, m_label, m_curr_pos); m_types.insert(target_reg.v, reg, type_loaded_no_off); + print_annotated(b, type_loaded_no_off, std::cout); } break; @@ -275,12 +343,14 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { auto it = m_ctx->find(load_at); if (!it) { + std::cout << " " << b << "\n"; CRAB_ERROR("type_error: no field at loaded offset ", load_at, " in context"); } ptr_no_off_t type_loaded = it.value(); auto reg = reg_with_loc_t(target_reg.v, m_label, m_curr_pos); m_types.insert(target_reg.v, reg, type_loaded); + print_annotated(b, type_loaded, std::cout); break; } @@ -288,20 +358,11 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { assert(false); } } - - auto it2 = m_types.find(target_reg.v); - if (it2) { - std::cout << "\t"; - print_type(target_reg.v, it2.value()); - std::cout << "\n"; - } - else { - CRAB_ERROR("Type of r", static_cast(target_reg.v), " is not being stored"); - } } void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { + std::cout << " " << b << ";\n"; int offset = b.access.offset; Reg basereg = b.access.basereg; int width = b.access.width; diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 228a4ae9d..a7665857e 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -19,16 +19,16 @@ enum class region { T_SHARED }; -inline std::string get_region(const region& r) { +inline std::string get_reg_ptr(const region& r) { switch (r) { case region::T_CTX: - return "ctx"; + return "ctx_p"; case region::T_STACK: - return "stack"; + return "stack_p"; case region::T_PACKET: - return "packet"; + return "packet_p"; default: - return "shared"; + return "shared_p"; } } @@ -49,7 +49,7 @@ struct ptr_no_off_t { ptr_no_off_t(region _r) : r(_r) {} friend std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p) { - return o << "{" << get_region(p.r) << "}"; + return o << get_reg_ptr(p.r); } // temporarily make operators friend functions in order to avoid duplicate symbol errors @@ -75,7 +75,7 @@ struct ptr_with_off_t { ptr_with_off_t(region _r, int _off) : r(_r), offset(_off) {} friend std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p) { - o << "{" << get_region(p.r) << ", " << p.offset << "}"; + o << get_reg_ptr(p.r) << "<" << p.offset << ">"; return o; } @@ -96,11 +96,10 @@ struct reg_with_loc_t { register_t r; std::pair loc; - reg_with_loc_t() : r(11), loc(std::make_pair(label_t::entry, 0)) {} reg_with_loc_t(register_t _r, const label_t& l, uint32_t loc_instr) : r(_r), loc(std::make_pair(l, loc_instr)) {} bool operator==(const reg_with_loc_t& other) const { - return (r < 11 && r == other.r); + return (r == other.r && loc == other.loc); } std::size_t hash() const { @@ -113,6 +112,11 @@ struct reg_with_loc_t { return seed; } + + friend std::ostream& operator<<(std::ostream& o, const reg_with_loc_t& reg) { + o << "r" << static_cast(reg.r) << "@" << reg.loc.second << " in " << reg.loc.first; + return o; + } }; } @@ -169,7 +173,7 @@ class ctx_t { o << "type of context: " << (_ctx.m_packet_ptrs.empty() ? "_|_" : "") << "\n"; for (const auto& it : _ctx.m_packet_ptrs) { - o << "\tstores at " << it.first << ": " << it.second << "\n"; + o << " stores at " << it.first << ": " << it.second << "\n"; } return o; } @@ -236,7 +240,7 @@ class stack_t { friend std::ostream& operator<<(std::ostream& o, const stack_t& st); }; -using live_registers_t = std::array; +using live_registers_t = std::array, 11>; using global_type_env_t = std::unordered_map; class register_types_t { @@ -257,8 +261,9 @@ class register_types_t { } live_registers_t out_vars; for (size_t i = 0; i < m_vars.size(); i++) { - auto it1 = find(m_vars[i]); - auto it2 = other.find(other.m_vars[i]); + if (m_vars[i] == nullptr) continue; + auto it1 = find(*(m_vars[i])); + auto it2 = other.find(*(other.m_vars[i])); if (it1 && it2 && it1.value() == it2.value()) { out_vars[i] = m_vars[i]; } @@ -267,13 +272,20 @@ class register_types_t { return register_types_t(std::move(out_vars), m_all_types, false); } + void operator-=(register_t var) { + if (is_bottom()) { + return; + } + m_vars[var] = nullptr; + } + void set_to_bottom() { - m_vars = live_registers_t{}; + m_vars = live_registers_t{nullptr}; m_is_bottom = true; } void set_to_top() { - m_vars = live_registers_t{}; + m_vars = live_registers_t{nullptr}; m_is_bottom = false; } @@ -281,34 +293,34 @@ class register_types_t { bool is_top() const { if (m_is_bottom) { return false; } - return (m_all_types == nullptr || m_vars.empty()); + if (m_all_types == nullptr) return true; + for (auto it : m_vars) { + if (it != nullptr) return false; + } + return true; } void insert(register_t reg, const reg_with_loc_t& reg_with_loc, const ptr_t& type) { - auto it = m_all_types->find(reg_with_loc); - if (it == m_all_types->end()) - m_all_types->insert(std::make_pair(reg_with_loc, type)); - else - it->second = type; - //auto it = m_all_types->insert(std::make_pair(reg_with_loc, type)); - //if (not it.second) it.first->second = type; - m_vars[reg] = reg_with_loc; + (*m_all_types)[reg_with_loc] = type; + m_vars[reg] = std::make_shared(reg_with_loc); } - std::optional find(const reg_with_loc_t& reg) const { + std::optional find(reg_with_loc_t reg) const { auto it = m_all_types->find(reg); if (it == m_all_types->end()) return {}; return it->second; } std::optional find(register_t key) const { - auto reg = m_vars[key]; + if (m_vars[key] == nullptr) return {}; + const reg_with_loc_t& reg = *(m_vars[key]); return find(reg); } const live_registers_t &get_vars() { return m_vars; } friend std::ostream& operator<<(std::ostream& o, const register_types_t& p); + }; } @@ -372,11 +384,24 @@ class type_domain_t final { m_label = bb.label(); std::cout << m_label << ":\n"; for (const Instruction& statement : bb) { - std::cout << " " << statement << "\n"; m_curr_pos++; std::visit(*this, statement); } - std::cout << "\n"; + auto [it, et] = bb.next_blocks(); + if (it != et) { + std::cout << " " + << "goto "; + for (; it != et;) { + std::cout << *it; + ++it; + if (it == et) { + std::cout << ";"; + } else { + std::cout << ","; + } + } + } + std::cout << "\n\n"; } void write(std::ostream& os) const; std::string domain_name() const; diff --git a/src/crab_verifier.cpp b/src/crab_verifier.cpp index 306bb8a00..c8c82ec8b 100644 --- a/src/crab_verifier.cpp +++ b/src/crab_verifier.cpp @@ -162,9 +162,7 @@ crab_results get_ebpf_report(std::ostream& s, cfg_t& cfg, program_info info, con thread_local_options = *options; try { - s << "\nprinting the cfg ==============\n"; - s << cfg; - s << "\n"; + abstract_domain_t entry_dom = make_initial(options); // Get dictionaries of pre-invariants and post-invariants for each basic block. auto [pre_invariants, post_invariants] = From 2f5689919f36aa49f5ca0d97ed75838d50ba03ba Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 29 Mar 2022 20:52:35 -0400 Subject: [PATCH 083/373] Refactoring Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 251 ++++++++++++++++++++++++- src/crab/type_domain.hpp | 384 +++++++++------------------------------ 2 files changed, 331 insertions(+), 304 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 968081421..01f732157 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -17,6 +17,33 @@ using crab::register_types_t; static std::string size(int w) { return std::string("u") + std::to_string(w * 8); } + +namespace std { + template <> + struct std::hash { + std::size_t operator()(const crab::reg_with_loc_t& reg) const { return reg.hash(); } + }; + + // does not seem to work for me + template <> + struct std::equal_to { + constexpr bool operator()(const crab::ptr_t& p1, const crab::ptr_t& p2) const { + if (p1.index() != p2.index()) return false; + if (std::holds_alternative(p1)) { + auto ptr_no_off1 = std::get(p1); + auto ptr_no_off2 = std::get(p2); + return (ptr_no_off1.r == ptr_no_off2.r); + } + else { + auto ptr_with_off1 = std::get(p1); + auto ptr_with_off2 = std::get(p2); + return (ptr_with_off1.r == ptr_with_off2.r && ptr_with_off1.offset == ptr_with_off2.offset); + } + } + }; +} + + void print_ptr_type(const ptr_t& p) { if (std::holds_alternative(p)) { auto t = std::get(p); @@ -53,6 +80,69 @@ void print_annotated(Call const& call, const ptr_t& p, std::ostream& os_) { namespace crab { +inline std::string get_reg_ptr(const region& r) { + switch (r) { + case region::T_CTX: + return "ctx_p"; + case region::T_STACK: + return "stack_p"; + case region::T_PACKET: + return "packet_p"; + default: + return "shared_p"; + } +} + +inline std::ostream& operator<<(std::ostream& o, const region& t) { + o << static_cast::type>(t); + return o; +} + +bool operator==(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { + return (p1.r == p2.r && p1.offset == p2.offset); +} + +bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { + return !(p1 == p2); +} + +std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p) { + o << get_reg_ptr(p.r) << "<" << p.offset << ">"; + return o; +} + +bool operator==(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { + return (p1.r == p2.r); +} + +bool operator!=(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { + return !(p1 == p2); +} + +std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p) { + return o << get_reg_ptr(p.r); +} + +std::ostream& operator<<(std::ostream& o, const reg_with_loc_t& reg) { + o << "r" << static_cast(reg.r) << "@" << reg.loc.second << " in " << reg.loc.first; + return o; +} + +bool reg_with_loc_t::operator==(const reg_with_loc_t& other) const { + return (r == other.r && loc == other.loc); +} + +std::size_t reg_with_loc_t::hash() const { + // Similar to boost::hash_combine + using std::hash; + + std::size_t seed = hash()(r); + seed ^= hash()(loc.first.from) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= hash()(loc.second) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + + return seed; +} + std::ostream& operator<<(std::ostream& o, const stack_t& st) { o << "Stack: "; if (st.is_bottom()) @@ -70,6 +160,30 @@ std::ostream& operator<<(std::ostream& o, const stack_t& st) { return o; } +std::ostream& operator<<(std::ostream& o, const ctx_t& _ctx) { + + o << "type of context: " << (_ctx.m_packet_ptrs.empty() ? "_|_" : "") << "\n"; + for (const auto& it : _ctx.m_packet_ptrs) { + o << " stores at " << it.first << ": " << it.second << "\n"; + } + return o; +} + +ctx_t::ctx_t(const ebpf_context_descriptor_t* desc) +{ + if (desc->data != -1) + m_packet_ptrs[desc->data] = crab::ptr_no_off_t(crab::region::T_PACKET); + if (desc->end != -1) + m_packet_ptrs[desc->end] = crab::ptr_no_off_t(crab::region::T_PACKET); +} + +std::optional ctx_t::find(int key) const { + auto it = m_packet_ptrs.find(key); + if (it == m_packet_ptrs.end()) return {}; + return it->second; +} + + std::ostream& operator<<(std::ostream& o, const register_types_t& typ) { if (typ.is_bottom()) o << "_|_\n"; @@ -82,6 +196,116 @@ std::ostream& operator<<(std::ostream& o, const register_types_t& typ) { } return o; } + +register_types_t register_types_t::operator|(const register_types_t& other) const { + if (is_bottom() || other.is_top()) { + return other; + } else if (other.is_bottom() || is_top()) { + return *this; + } + live_registers_t out_vars; + for (size_t i = 0; i < m_vars.size(); i++) { + if (m_vars[i] == nullptr) continue; + auto it1 = find(*(m_vars[i])); + auto it2 = other.find(*(other.m_vars[i])); + if (it1 && it2 && it1.value() == it2.value()) { + out_vars[i] = m_vars[i]; + } + } + + return register_types_t(std::move(out_vars), m_all_types, false); +} + +void register_types_t::operator-=(register_t var) { + if (is_bottom()) { + return; + } + m_vars[var] = nullptr; +} + +void register_types_t::set_to_bottom() { + m_vars = live_registers_t{nullptr}; + m_is_bottom = true; +} + +void register_types_t::set_to_top() { + m_vars = live_registers_t{nullptr}; + m_is_bottom = false; +} + +bool register_types_t::is_bottom() const { return m_is_bottom; } + +bool register_types_t::is_top() const { + if (m_is_bottom) { return false; } + if (m_all_types == nullptr) return true; + for (auto it : m_vars) { + if (it != nullptr) return false; + } + return true; +} + +void register_types_t::insert(register_t reg, const reg_with_loc_t& reg_with_loc, const ptr_t& type) { + (*m_all_types)[reg_with_loc] = type; + m_vars[reg] = std::make_shared(reg_with_loc); +} + +std::optional register_types_t::find(reg_with_loc_t reg) const { + auto it = m_all_types->find(reg); + if (it == m_all_types->end()) return {}; + return it->second; +} + +std::optional register_types_t::find(register_t key) const { + if (m_vars[key] == nullptr) return {}; + const reg_with_loc_t& reg = *(m_vars[key]); + return find(reg); +} + +stack_t stack_t::operator|(const stack_t& other) const { + if (is_bottom() || other.is_top()) { + return other; + } else if (other.is_bottom() || is_top()) { + return *this; + } + offset_to_ptr_t out_ptrs; + for (auto const&kv: m_ptrs) { + auto it = other.find(kv.first); + if (it && kv.second == it.value()) + out_ptrs.insert(kv); + } + return stack_t(std::move(out_ptrs), false); +} + +void stack_t::set_to_bottom() { + m_ptrs.clear(); + m_is_bottom = true; +} + +void stack_t::set_to_top() { + m_ptrs.clear(); + m_is_bottom = false; +} + +stack_t stack_t::bottom() { return stack_t(true); } + +stack_t stack_t::top() { return stack_t(false); } + +bool stack_t::is_bottom() const { return m_is_bottom; } + +bool stack_t::is_top() const { + if (m_is_bottom) + return false; + return m_ptrs.empty(); +} + +void stack_t::insert(int key, ptr_t value) { m_ptrs.insert(std::make_pair(key, value)); } + +std::optional stack_t::find(int key) const { + auto it = m_ptrs.find(key); + if (it == m_ptrs.end()) return {}; + return it->second; +} + } bool type_domain_t::is_bottom() const { @@ -223,7 +447,7 @@ void print_info() { std::cout << " shared_p = shared pointer\n"; std::cout << " stack_p = stack pointer at offset n\n"; std::cout << " ctx_p = context pointer at offset n\n"; - std::cout << " context = _|_ means context contains no elements stored\n\n"; + std::cout << " 'context = _|_' means context contains no elements stored\n\n"; std::cout << "**************************************************************\n\n"; } @@ -430,4 +654,29 @@ void type_domain_t::operator()(const Mem& b) { } } +void type_domain_t::operator()(const basic_block_t& bb, bool check_termination) { + m_curr_pos = 0; + m_label = bb.label(); + std::cout << m_label << ":\n"; + for (const Instruction& statement : bb) { + m_curr_pos++; + std::visit(*this, statement); + } + auto [it, et] = bb.next_blocks(); + if (it != et) { + std::cout << " " + << "goto "; + for (; it != et;) { + std::cout << *it; + ++it; + if (it == et) { + std::cout << ";"; + } else { + std::cout << ","; + } + } + } + std::cout << "\n\n"; +} + void type_domain_t::set_require_check(check_require_func_t f) {} diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index a7665857e..0c3c330e0 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -19,24 +19,6 @@ enum class region { T_SHARED }; -inline std::string get_reg_ptr(const region& r) { - switch (r) { - case region::T_CTX: - return "ctx_p"; - case region::T_STACK: - return "stack_p"; - case region::T_PACKET: - return "packet_p"; - default: - return "shared_p"; - } -} - -inline std::ostream& operator<<(std::ostream& o, const region& t) { - o << static_cast::type>(t); - return o; -} - struct ptr_no_off_t { region r; @@ -45,21 +27,11 @@ struct ptr_no_off_t { ptr_no_off_t(ptr_no_off_t &&) = default; ptr_no_off_t &operator=(const ptr_no_off_t &) = default; ptr_no_off_t &operator=(ptr_no_off_t &&) = default; - ptr_no_off_t(region _r) : r(_r) {} - friend std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p) { - return o << get_reg_ptr(p.r); - } - - // temporarily make operators friend functions in order to avoid duplicate symbol errors - friend bool operator==(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { - return (p1.r == p2.r); - } - - friend bool operator!=(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { - return !(p1 == p2); - } + friend std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p); + friend bool operator==(const ptr_no_off_t& p1, const ptr_no_off_t& p2); + friend bool operator!=(const ptr_no_off_t& p1, const ptr_no_off_t& p2); }; struct ptr_with_off_t { @@ -71,22 +43,11 @@ struct ptr_with_off_t { ptr_with_off_t(ptr_with_off_t &&) = default; ptr_with_off_t &operator=(const ptr_with_off_t &) = default; ptr_with_off_t &operator=(ptr_with_off_t &&) = default; - ptr_with_off_t(region _r, int _off) : r(_r), offset(_off) {} - friend std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p) { - o << get_reg_ptr(p.r) << "<" << p.offset << ">"; - return o; - } - - // temporarily make operators friend functions in order to avoid duplicate symbol errors - friend bool operator==(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { - return (p1.r == p2.r && p1.offset == p2.offset); - } - - friend bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { - return !(p1 == p2); - } + friend std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p); + friend bool operator==(const ptr_with_off_t& p1, const ptr_with_off_t& p2); + friend bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2); }; using ptr_t = std::variant; @@ -97,57 +58,10 @@ struct reg_with_loc_t { std::pair loc; reg_with_loc_t(register_t _r, const label_t& l, uint32_t loc_instr) : r(_r), loc(std::make_pair(l, loc_instr)) {} - - bool operator==(const reg_with_loc_t& other) const { - return (r == other.r && loc == other.loc); - } - - std::size_t hash() const { - // Similar to boost::hash_combine - using std::hash; - - std::size_t seed = hash()(r); - seed ^= hash()(loc.first.from) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - seed ^= hash()(loc.second) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - - return seed; - } - - friend std::ostream& operator<<(std::ostream& o, const reg_with_loc_t& reg) { - o << "r" << static_cast(reg.r) << "@" << reg.loc.second << " in " << reg.loc.first; - return o; - } + bool operator==(const reg_with_loc_t& other) const; + std::size_t hash() const; + friend std::ostream& operator<<(std::ostream& o, const reg_with_loc_t& reg); }; -} - - -namespace std { - template <> - struct std::hash { - std::size_t operator()(const crab::reg_with_loc_t& reg) const { return reg.hash(); } - }; - - // does not seem to work for me - template <> - struct std::equal_to { - constexpr bool operator()(const crab::ptr_t& p1, const crab::ptr_t& p2) const { - if (p1.index() != p2.index()) return false; - if (std::holds_alternative(p1)) { - auto ptr_no_off1 = std::get(p1); - auto ptr_no_off2 = std::get(p2); - return (ptr_no_off1.r == ptr_no_off2.r); - } - else { - auto ptr_with_off1 = std::get(p1); - auto ptr_with_off2 = std::get(p2); - return (ptr_with_off1.r == ptr_with_off2.r && ptr_with_off1.offset == ptr_with_off2.offset); - } - } - }; -} - - -namespace crab { class ctx_t { using offset_to_ptr_no_off_t = std::unordered_map; @@ -155,28 +69,9 @@ class ctx_t { offset_to_ptr_no_off_t m_packet_ptrs; public: - ctx_t(const ebpf_context_descriptor_t* desc) - { - if (desc->data != -1) - m_packet_ptrs[desc->data] = crab::ptr_no_off_t(crab::region::T_PACKET); - if (desc->end != -1) - m_packet_ptrs[desc->end] = crab::ptr_no_off_t(crab::region::T_PACKET); - } - - std::optional find(int key) const { - auto it = m_packet_ptrs.find(key); - if (it == m_packet_ptrs.end()) return {}; - return it->second; - } - - friend std::ostream& operator<<(std::ostream& o, const ctx_t& _ctx) { - - o << "type of context: " << (_ctx.m_packet_ptrs.empty() ? "_|_" : "") << "\n"; - for (const auto& it : _ctx.m_packet_ptrs) { - o << " stores at " << it.first << ": " << it.second << "\n"; - } - return o; - } + ctx_t(const ebpf_context_descriptor_t* desc); + std::optional find(int key) const; + friend std::ostream& operator<<(std::ostream& o, const ctx_t& _ctx); }; class stack_t { @@ -190,53 +85,16 @@ class stack_t { stack_t(offset_to_ptr_t && ptrs, bool is_bottom) : m_ptrs(std::move(ptrs)) , m_is_bottom(is_bottom) {} - stack_t operator|(const stack_t& other) const { - if (is_bottom() || other.is_top()) { - return other; - } else if (other.is_bottom() || is_top()) { - return *this; - } - offset_to_ptr_t out_ptrs; - for (auto const&kv: m_ptrs) { - auto it = other.find(kv.first); - if (it && kv.second == it.value()) - out_ptrs.insert(kv); - } - return stack_t(std::move(out_ptrs), false); - } - - void set_to_bottom() { - m_ptrs.clear(); - m_is_bottom = true; - } - - void set_to_top() { - m_ptrs.clear(); - m_is_bottom = false; - } - - static stack_t bottom() { return stack_t(true); } - - static stack_t top() { return stack_t(false); } - - bool is_bottom() const { return m_is_bottom; } - - bool is_top() const { - if (m_is_bottom) - return false; - return m_ptrs.empty(); - } - + stack_t operator|(const stack_t& other) const; + void set_to_bottom(); + void set_to_top(); + static stack_t bottom(); + static stack_t top(); + bool is_bottom() const; + bool is_top() const; const offset_to_ptr_t &get_ptrs() { return m_ptrs; } - - void insert(int key, ptr_t value) { m_ptrs.insert(std::make_pair(key, value)); } - - std::optional find(int key) const { - auto it = m_ptrs.find(key); - if (it == m_ptrs.end()) return {}; - return it->second; - } - + void insert(int key, ptr_t value); + std::optional find(int key) const; friend std::ostream& operator<<(std::ostream& o, const stack_t& st); }; @@ -253,74 +111,17 @@ class register_types_t { explicit register_types_t(live_registers_t&& vars, std::shared_ptr all_types, bool is_bottom = false) : m_vars(std::move(vars)), m_all_types(all_types), m_is_bottom(is_bottom) {} - register_types_t operator|(const register_types_t& other) const { - if (is_bottom() || other.is_top()) { - return other; - } else if (other.is_bottom() || is_top()) { - return *this; - } - live_registers_t out_vars; - for (size_t i = 0; i < m_vars.size(); i++) { - if (m_vars[i] == nullptr) continue; - auto it1 = find(*(m_vars[i])); - auto it2 = other.find(*(other.m_vars[i])); - if (it1 && it2 && it1.value() == it2.value()) { - out_vars[i] = m_vars[i]; - } - } - - return register_types_t(std::move(out_vars), m_all_types, false); - } - - void operator-=(register_t var) { - if (is_bottom()) { - return; - } - m_vars[var] = nullptr; - } - - void set_to_bottom() { - m_vars = live_registers_t{nullptr}; - m_is_bottom = true; - } - - void set_to_top() { - m_vars = live_registers_t{nullptr}; - m_is_bottom = false; - } - - bool is_bottom() const { return m_is_bottom; } - - bool is_top() const { - if (m_is_bottom) { return false; } - if (m_all_types == nullptr) return true; - for (auto it : m_vars) { - if (it != nullptr) return false; - } - return true; - } - - void insert(register_t reg, const reg_with_loc_t& reg_with_loc, const ptr_t& type) { - (*m_all_types)[reg_with_loc] = type; - m_vars[reg] = std::make_shared(reg_with_loc); - } - - std::optional find(reg_with_loc_t reg) const { - auto it = m_all_types->find(reg); - if (it == m_all_types->end()) return {}; - return it->second; - } - - std::optional find(register_t key) const { - if (m_vars[key] == nullptr) return {}; - const reg_with_loc_t& reg = *(m_vars[key]); - return find(reg); - } - + register_types_t operator|(const register_types_t& other) const; + void operator-=(register_t var); + void set_to_bottom(); + void set_to_top(); + bool is_bottom() const; + bool is_top() const; + void insert(register_t reg, const reg_with_loc_t& reg_with_loc, const ptr_t& type); + std::optional find(reg_with_loc_t reg) const; + std::optional find(register_t key) const; const live_registers_t &get_vars() { return m_vars; } - friend std::ostream& operator<<(std::ostream& o, const register_types_t& p); - }; } @@ -335,83 +136,60 @@ class type_domain_t final { public: - type_domain_t() : m_label(label_t::entry) {} - type_domain_t(type_domain_t&& o) = default; - type_domain_t(const type_domain_t& o) = default; - type_domain_t& operator=(type_domain_t&& o) = default; - type_domain_t& operator=(const type_domain_t& o) = default; - type_domain_t(crab::register_types_t&& _types, crab::stack_t&& _st, const label_t& _l, std::shared_ptr _ctx) + type_domain_t() : m_label(label_t::entry) {} + type_domain_t(type_domain_t&& o) = default; + type_domain_t(const type_domain_t& o) = default; + type_domain_t& operator=(type_domain_t&& o) = default; + type_domain_t& operator=(const type_domain_t& o) = default; + type_domain_t(crab::register_types_t&& _types, crab::stack_t&& _st, const label_t& _l, std::shared_ptr _ctx) : m_stack(std::move(_st)), m_types(std::move(_types)), m_ctx(_ctx), m_label(_l) {} - // eBPF initialization: R1 points to ctx, R10 to stack, etc. - static type_domain_t setup_entry(); - // bottom/top - static type_domain_t bottom(); - void set_to_top(); - void set_to_bottom(); - bool is_bottom() const; - bool is_top() const; - // inclusion - bool operator<=(const type_domain_t& other) const; - // join - void operator|=(const type_domain_t& abs); - void operator|=(type_domain_t&& abs); - type_domain_t operator|(const type_domain_t& other) const; - type_domain_t operator|(type_domain_t&& abs) const; - // meet - type_domain_t operator&(const type_domain_t& other) const; - // widening - type_domain_t widen(const type_domain_t& other) const; - // narrowing - type_domain_t narrow(const type_domain_t& other) const; - //forget - void operator-=(variable_t var); - - //// abstract transformers - void operator()(const Undefined &); - void operator()(const Bin &); - void operator()(const Un &) ; - void operator()(const LoadMapFd &); - void operator()(const Call &); - void operator()(const Exit &); - void operator()(const Jmp &); - void operator()(const Mem &); - void operator()(const Packet &); - void operator()(const LockAdd &); - void operator()(const Assume &); - void operator()(const Assert &); - void operator()(const basic_block_t& bb, bool check_termination) { - m_curr_pos = 0; - m_label = bb.label(); - std::cout << m_label << ":\n"; - for (const Instruction& statement : bb) { - m_curr_pos++; - std::visit(*this, statement); - } - auto [it, et] = bb.next_blocks(); - if (it != et) { - std::cout << " " - << "goto "; - for (; it != et;) { - std::cout << *it; - ++it; - if (it == et) { - std::cout << ";"; - } else { - std::cout << ","; - } - } - } - std::cout << "\n\n"; - } - void write(std::ostream& os) const; - std::string domain_name() const; - int get_instruction_count_upper_bound(); - string_invariant to_set(); - void set_require_check(check_require_func_t f); + // eBPF initialization: R1 points to ctx, R10 to stack, etc. + static type_domain_t setup_entry(); + // bottom/top + static type_domain_t bottom(); + void set_to_top(); + void set_to_bottom(); + bool is_bottom() const; + bool is_top() const; + // inclusion + bool operator<=(const type_domain_t& other) const; + // join + void operator|=(const type_domain_t& abs); + void operator|=(type_domain_t&& abs); + type_domain_t operator|(const type_domain_t& other) const; + type_domain_t operator|(type_domain_t&& abs) const; + // meet + type_domain_t operator&(const type_domain_t& other) const; + // widening + type_domain_t widen(const type_domain_t& other) const; + // narrowing + type_domain_t narrow(const type_domain_t& other) const; + //forget + void operator-=(variable_t var); + + //// abstract transformers + void operator()(const Undefined &); + void operator()(const Bin &); + void operator()(const Un &) ; + void operator()(const LoadMapFd &); + void operator()(const Call &); + void operator()(const Exit &); + void operator()(const Jmp &); + void operator()(const Mem &); + void operator()(const Packet &); + void operator()(const LockAdd &); + void operator()(const Assume &); + void operator()(const Assert &); + void operator()(const basic_block_t& bb, bool check_termination); + void write(std::ostream& os) const; + std::string domain_name() const; + int get_instruction_count_upper_bound(); + string_invariant to_set(); + void set_require_check(check_require_func_t f); private: - void do_load(const Mem&, const Reg&); - void do_mem_store(const Mem&, const Reg&); + void do_load(const Mem&, const Reg&); + void do_mem_store(const Mem&, const Reg&); }; // end type_domain_t From 2e3bf4456a914b40f5bdd5a757dc2629cb888298 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 29 Mar 2022 21:22:15 -0400 Subject: [PATCH 084/373] Fixing automatic builds on Github Actions Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 01f732157..e20a9ef44 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -20,13 +20,13 @@ static std::string size(int w) { return std::string("u") + std::to_string(w * 8) namespace std { template <> - struct std::hash { - std::size_t operator()(const crab::reg_with_loc_t& reg) const { return reg.hash(); } + struct hash { + size_t operator()(const crab::reg_with_loc_t& reg) const { return reg.hash(); } }; // does not seem to work for me template <> - struct std::equal_to { + struct equal_to { constexpr bool operator()(const crab::ptr_t& p1, const crab::ptr_t& p2) const { if (p1.index() != p2.index()) return false; if (std::holds_alternative(p1)) { From f153d64a853b7f613d2b5775dd250487d776c5db Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 3 Jul 2023 20:13:19 -0400 Subject: [PATCH 085/373] Changes needed in current type domain due to latest prevail version upgrade Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 6 +++--- src/crab/type_domain.hpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index e20a9ef44..953d289c9 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -389,8 +389,8 @@ std::string type_domain_t::domain_name() const { return "type_domain"; } -int type_domain_t::get_instruction_count_upper_bound() { - return 0; +crab::bound_t type_domain_t::get_instruction_count_upper_bound() { + return crab::bound_t(crab::number_t(0)); } string_invariant type_domain_t::to_set() { @@ -455,7 +455,7 @@ type_domain_t type_domain_t::setup_entry() { print_info(); - std::shared_ptr ctx = std::make_shared(global_program_info.type.context_descriptor); + std::shared_ptr ctx = std::make_shared(global_program_info.get().type.context_descriptor); std::shared_ptr all_types = std::make_shared(); std::cout << *ctx << "\n"; diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 0c3c330e0..850cdd197 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -165,7 +165,7 @@ class type_domain_t final { // narrowing type_domain_t narrow(const type_domain_t& other) const; //forget - void operator-=(variable_t var); + void operator-=(crab::variable_t var); //// abstract transformers void operator()(const Undefined &); @@ -183,7 +183,7 @@ class type_domain_t final { void operator()(const basic_block_t& bb, bool check_termination); void write(std::ostream& os) const; std::string domain_name() const; - int get_instruction_count_upper_bound(); + crab::bound_t get_instruction_count_upper_bound(); string_invariant to_set(); void set_require_check(check_require_func_t f); From 4bb4c20412f7ca8de6fd7287980a4ae5c5ad439a Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 29 Mar 2022 22:11:39 -0400 Subject: [PATCH 086/373] minor printing fix Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 953d289c9..7c7ce26f2 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -497,6 +497,7 @@ void type_domain_t::operator()(const Bin& bin) { case Bin::Op::MOV: { auto it = m_types.find(src.v); if (!it) { + std::cout << " " << bin << "\n"; CRAB_ERROR("type error: assigning an unknown pointer or a number - r", (int)src.v); } From f460cafaf86c93112837645cb0f0a2e806d82a66 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Wed, 30 Mar 2022 19:11:41 -0400 Subject: [PATCH 087/373] Removed the type errors that are too restrictive; Forgetting the offset into stack when we cannot reason about it Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 125 ++++++++++++++++++++++++--------------- src/crab/type_domain.hpp | 1 + 2 files changed, 79 insertions(+), 47 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 7c7ce26f2..9cc50a56a 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -149,11 +149,10 @@ std::ostream& operator<<(std::ostream& o, const stack_t& st) { o << "_|_\n"; else { o << "{"; - for (auto it = st.m_ptrs.begin(); it != st.m_ptrs.end(); it++) { - auto s = *it; + for (auto s : st.m_ptrs) { o << s.first << ": "; print_ptr_type(s.second); - if (++it != st.m_ptrs.end()) o << ","; + o << ", "; } o << "}"; } @@ -205,7 +204,7 @@ register_types_t register_types_t::operator|(const register_types_t& other) cons } live_registers_t out_vars; for (size_t i = 0; i < m_vars.size(); i++) { - if (m_vars[i] == nullptr) continue; + if (m_vars[i] == nullptr || other.m_vars[i] == nullptr) continue; auto it1 = find(*(m_vars[i])); auto it2 = other.find(*(other.m_vars[i])); if (it1 && it2 && it1.value() == it2.value()) { @@ -276,6 +275,12 @@ stack_t stack_t::operator|(const stack_t& other) const { return stack_t(std::move(out_ptrs), false); } +void stack_t::operator-=(int key) { + auto it = find(key); + if (it) + m_ptrs.erase(key); +} + void stack_t::set_to_bottom() { m_ptrs.clear(); m_is_bottom = true; @@ -298,7 +303,9 @@ bool stack_t::is_top() const { return m_ptrs.empty(); } -void stack_t::insert(int key, ptr_t value) { m_ptrs.insert(std::make_pair(key, value)); } +void stack_t::insert(int key, ptr_t value) { + m_ptrs[key] = value; +} std::optional stack_t::find(int key) const { auto it = m_ptrs.find(key); @@ -381,7 +388,7 @@ type_domain_t type_domain_t::narrow(const type_domain_t& other) const { } void type_domain_t::write(std::ostream& os) const { - os << m_types; + //os << m_types; os << m_stack << "\n"; } @@ -453,7 +460,7 @@ void print_info() { type_domain_t type_domain_t::setup_entry() { - print_info(); + //print_info(); std::shared_ptr ctx = std::make_shared(global_program_info.get().type.context_descriptor); std::shared_ptr all_types = std::make_shared(); @@ -469,6 +476,8 @@ type_domain_t type_domain_t::setup_entry() { typ.insert(R1_ARG, r1, ptr_with_off_t(crab::region::T_CTX, 0)); typ.insert(R10_STACK_POINTER, r10, ptr_with_off_t(crab::region::T_STACK, 512)); + std::cout << "is types bottom in the start: " << typ.is_bottom() << "\n"; + std::cout << "Initial register types:\n"; auto it = typ.find(R1_ARG); if (it) { @@ -497,8 +506,10 @@ void type_domain_t::operator()(const Bin& bin) { case Bin::Op::MOV: { auto it = m_types.find(src.v); if (!it) { - std::cout << " " << bin << "\n"; - CRAB_ERROR("type error: assigning an unknown pointer or a number - r", (int)src.v); + //std::cout << " " << bin << "\n"; + //CRAB_ERROR("type error: assigning an unknown pointer or a number - r", (int)src.v); + m_types -= bin.dst.v; + break; } auto reg = reg_with_loc_t(bin.dst.v, m_label, m_curr_pos); @@ -531,7 +542,9 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { if (std::holds_alternative(type_basereg)) { std::cout << " " << b << "\n"; - CRAB_ERROR("type_error: loading from either packet or shared region not allowed - r", (int)basereg.v); + //CRAB_ERROR("type_error: loading from either packet or shared region not allowed - r", (int)basereg.v); + m_types -= target_reg.v; + return; } ptr_with_off_t type_with_off = std::get(type_basereg); @@ -544,7 +557,9 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { if (!it) { std::cout << " " << b << "\n"; - CRAB_ERROR("type_error: no field at loaded offset ", load_at, " in stack"); + //CRAB_ERROR("type_error: no field at loaded offset ", load_at, " in stack"); + m_types -= target_reg.v; + return; } ptr_t type_loaded = it.value(); @@ -569,7 +584,9 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { if (!it) { std::cout << " " << b << "\n"; - CRAB_ERROR("type_error: no field at loaded offset ", load_at, " in context"); + //CRAB_ERROR("type_error: no field at loaded offset ", load_at, " in context"); + m_types -= target_reg.v; + return; } ptr_no_off_t type_loaded = it.value(); @@ -599,46 +616,60 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { ptr_t type_basereg = it.value(); auto it2 = m_types.find(target_reg.v); - if (!it2) { - CRAB_ERROR("type_error: storing either a number or an unknown pointer - r", (int)target_reg.v); - } - ptr_t type_stored = it2.value(); - if (std::holds_alternative(type_stored)) { - ptr_with_off_t type_stored_with_off = std::get(type_stored); - if (type_stored_with_off.r == crab::region::T_STACK) { - CRAB_ERROR("type_error: we cannot store stack pointer, r", (int)target_reg.v, ", into stack"); - } - } - - if (std::holds_alternative(type_basereg)) { - CRAB_ERROR("type_error: we cannot store pointer, r", (int)target_reg.v, ", into packet or shared"); - } - - ptr_with_off_t type_basereg_with_off = std::get(type_basereg); - if (type_basereg_with_off.r == crab::region::T_CTX) { - CRAB_ERROR("type_error: we cannot store pointer, r", (int)target_reg.v, ", into ctx"); - } - - int store_at = offset+type_basereg_with_off.offset; - - for (auto i = store_at; i < store_at+width; i++) { - auto it = m_stack.find(i); - if (it) { - CRAB_ERROR("type_error: type being stored into stack at ", store_at, " is overlapping with already stored\ - at", i); + if (std::holds_alternative(type_basereg)) { + // we know base register is either CTX_P or STACK_P + ptr_with_off_t type_basereg_with_off = std::get(type_basereg); + + int store_at = offset+type_basereg_with_off.offset; + if (type_basereg_with_off.r == crab::region::T_STACK) { + // type of basereg is STACK_P + if (!it2) { + //CRAB_ERROR("type_error: storing either a number or an unknown pointer - r", (int)target_reg.v); + m_stack -= store_at; + return; + } + else { + auto type_to_store = it2.value(); + if (std::holds_alternative(type_to_store) && + std::get(type_to_store).r == crab::region::T_STACK) { + CRAB_ERROR("type_error: we cannot store stack pointer, r", (int)target_reg.v, ", into stack"); + } + else { + for (auto i = store_at; i < store_at+width; i++) { + auto it3 = m_stack.find(i); + if (it3) { + CRAB_ERROR("type_error: type being stored into stack at ", store_at, " is overlapping with already stored\ + at", i); + } + } + auto it4 = m_stack.find(store_at); + if (it4) { + auto type_in_stack = it4.value(); + if (type_to_store != type_in_stack) { + CRAB_ERROR("type_error: type being stored at offset ", store_at, " is not the same as stored already in stack"); + } + } + else { + m_stack.insert(store_at, type_to_store); + } + } + } } - } - - auto it3 = m_stack.find(store_at); - if (it3) { - auto type_in_stack = it3.value(); - if (type_stored != type_in_stack) { - CRAB_ERROR("type_error: type being stored at offset ", store_at, " is not the same as stored already in stack"); + else if (type_basereg_with_off.r == crab::region::T_CTX) { + // type of basereg is CTX_P + if (it2) { + CRAB_ERROR("type_error: we cannot store pointer, r", (int)target_reg.v, ", into ctx"); + } } + else + assert(false); } else { - m_stack.insert(store_at, type_stored); + // base register type is either PACKET_P or SHARED_P + if (it2) { + CRAB_ERROR("type_error: we cannot store pointer, r", (int)target_reg.v, ", into packet or shared"); + } } } diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 850cdd197..22bd0e30b 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -86,6 +86,7 @@ class stack_t { : m_ptrs(std::move(ptrs)) , m_is_bottom(is_bottom) {} stack_t operator|(const stack_t& other) const; + void operator-=(int); void set_to_bottom(); void set_to_top(); static stack_t bottom(); From e5ad362610aefdeddcd521f55a77a8087086c2e6 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Sun, 31 Mar 2024 16:17:23 -0400 Subject: [PATCH 088/373] Removed dependency to keep bb label and instruction count in type domain state Also, multiple changes to ebpf_domain_t, type_domain_t, and abstract_domain_t, as we are currently rebasing the type domain to latest PREVAIL, and a lot of changes happened in PREVAIL since old version; Author: Ameer Hamza Date: Thu Mar 31 07:47:48 2022 -0400 --- src/crab/abstract_domain.cpp | 66 +++++++++++----------- src/crab/abstract_domain.hpp | 74 ++++++++++++------------ src/crab/ebpf_domain.cpp | 46 +++++++-------- src/crab/ebpf_domain.hpp | 47 ++++++++-------- src/crab/type_domain.cpp | 105 +++++++++++++++++++---------------- src/crab/type_domain.hpp | 51 ++++++++--------- 6 files changed, 200 insertions(+), 189 deletions(-) diff --git a/src/crab/abstract_domain.cpp b/src/crab/abstract_domain.cpp index ed580787d..f89cd5576 100644 --- a/src/crab/abstract_domain.cpp +++ b/src/crab/abstract_domain.cpp @@ -110,58 +110,58 @@ void abstract_domain_t::abstract_domain_model::operator()(const basic_bl } template -void abstract_domain_t::abstract_domain_model::operator()(const Undefined& s) { - m_abs_val.operator()(s); +void abstract_domain_t::abstract_domain_model::operator()(const Undefined& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const Bin& s) { - m_abs_val.operator()(s); +void abstract_domain_t::abstract_domain_model::operator()(const Bin& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const Un& s) { - m_abs_val.operator()(s); +void abstract_domain_t::abstract_domain_model::operator()(const Un& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const LoadMapFd& s) { - m_abs_val.operator()(s); +void abstract_domain_t::abstract_domain_model::operator()(const LoadMapFd& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const Call& s) { - m_abs_val.operator()(s); +void abstract_domain_t::abstract_domain_model::operator()(const Call& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const Exit& s) { - m_abs_val.operator()(s); +void abstract_domain_t::abstract_domain_model::operator()(const Exit& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const Jmp& s) { - m_abs_val.operator()(s); +void abstract_domain_t::abstract_domain_model::operator()(const Jmp& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const Mem& s) { - m_abs_val.operator()(s); +void abstract_domain_t::abstract_domain_model::operator()(const Mem& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const Packet& s) { - m_abs_val.operator()(s); +void abstract_domain_t::abstract_domain_model::operator()(const Packet& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const Assume& s) { - m_abs_val.operator()(s); +void abstract_domain_t::abstract_domain_model::operator()(const Assume& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const Assert& s) { - m_abs_val.operator()(s); +void abstract_domain_t::abstract_domain_model::operator()(const Assert& s, location_t loc) { + m_abs_val.operator()(s, loc); } template @@ -245,27 +245,27 @@ void abstract_domain_t::operator()(const basic_block_t& bb) { m_concept->operator()(bb); } -void abstract_domain_t::operator()(const Undefined& s) { m_concept->operator()(s); } +void abstract_domain_t::operator()(const Undefined& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const Bin& s) { m_concept->operator()(s); } +void abstract_domain_t::operator()(const Bin& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const Un& s) { m_concept->operator()(s); } +void abstract_domain_t::operator()(const Un& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const LoadMapFd& s) { m_concept->operator()(s); } +void abstract_domain_t::operator()(const LoadMapFd& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const Call& s) { m_concept->operator()(s); } +void abstract_domain_t::operator()(const Call& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const Exit& s) { m_concept->operator()(s); } +void abstract_domain_t::operator()(const Exit& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const Jmp& s) { m_concept->operator()(s); } +void abstract_domain_t::operator()(const Jmp& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const Mem& s) { m_concept->operator()(s); } +void abstract_domain_t::operator()(const Mem& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const Packet& s) { m_concept->operator()(s); } +void abstract_domain_t::operator()(const Packet& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const Assume& s) { m_concept->operator()(s); } +void abstract_domain_t::operator()(const Assume& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const Assert& s) { m_concept->operator()(s); } +void abstract_domain_t::operator()(const Assert& s, location_t loc) { m_concept->operator()(s, loc); } void abstract_domain_t::write(std::ostream& os) const { m_concept->write(os); } diff --git a/src/crab/abstract_domain.hpp b/src/crab/abstract_domain.hpp index ae03cb2a1..c3f8a43e5 100644 --- a/src/crab/abstract_domain.hpp +++ b/src/crab/abstract_domain.hpp @@ -1,11 +1,13 @@ #pragma once +#include #include "cfg.hpp" #include "linear_constraint.hpp" #include "string_constraints.hpp" #include "array_domain.hpp" using check_require_func_t = std::function; +using location_t = boost::optional>; class abstract_domain_t { private: class abstract_domain_concept { @@ -30,18 +32,18 @@ class abstract_domain_t { virtual std::unique_ptr widen(const abstract_domain_concept& abs, bool) = 0; virtual std::unique_ptr narrow(const abstract_domain_concept& abs) const = 0; virtual void operator()(const basic_block_t&) = 0; - virtual void operator()(const Undefined&) = 0; - virtual void operator()(const Bin&) = 0; - virtual void operator()(const Un&) = 0; - virtual void operator()(const LoadMapFd&) = 0; - virtual void operator()(const Call&) = 0; - virtual void operator()(const Exit&) = 0; - virtual void operator()(const Jmp&) = 0; - virtual void operator()(const Mem&) = 0; - virtual void operator()(const Packet&) = 0; - virtual void operator()(const Assume&) = 0; - virtual void operator()(const Assert&) = 0; - virtual void write(std::ostream& o) const = 0; + virtual void operator()(const Undefined&, location_t loc = boost::none) = 0; + virtual void operator()(const Bin&, location_t loc = boost::none) = 0; + virtual void operator()(const Un&, location_t loc = boost::none) = 0; + virtual void operator()(const LoadMapFd&, location_t loc = boost::none) = 0; + virtual void operator()(const Call&, location_t loc = boost::none) = 0; + virtual void operator()(const Exit&, location_t loc = boost::none) = 0; + virtual void operator()(const Jmp&, location_t loc = boost::none) = 0; + virtual void operator()(const Mem&, location_t loc = boost::none) = 0; + virtual void operator()(const Packet&, location_t loc = boost::none) = 0; + virtual void operator()(const Assume&, location_t loc = boost::none) = 0; + virtual void operator()(const Assert&, location_t loc = boost::none) = 0; + virtual void write(std::ostream& os) const = 0; /* These operations are not very conventional for an abstract domain but it's convenient to have them */ @@ -72,18 +74,18 @@ class abstract_domain_t { std::unique_ptr widen(const abstract_domain_concept& abs, bool) override; std::unique_ptr narrow(const abstract_domain_concept& abs) const override; void operator()(const basic_block_t& bb) override; - void operator()(const Undefined& s) override; - void operator()(const Bin& s) override; - void operator()(const Un& s) override; - void operator()(const LoadMapFd& s) override; - void operator()(const Call& s) override; - void operator()(const Exit& s) override; - void operator()(const Jmp& s) override; - void operator()(const Mem& s) override; - void operator()(const Packet& s) override; - void operator()(const Assume& s) override; - void operator()(const Assert& s) override; - void write(std::ostream& o) const override; + void operator()(const Undefined& s, location_t loc = boost::none) override; + void operator()(const Bin& s, location_t loc = boost::none) override; + void operator()(const Un& s, location_t loc = boost::none) override; + void operator()(const LoadMapFd& s, location_t loc = boost::none) override; + void operator()(const Call& s, location_t loc = boost::none) override; + void operator()(const Exit& s, location_t loc = boost::none) override; + void operator()(const Jmp& s, location_t loc = boost::none) override; + void operator()(const Mem& s, location_t loc = boost::none) override; + void operator()(const Packet& s, location_t loc = boost::none) override; + void operator()(const Assume& s, location_t loc = boost::none) override; + void operator()(const Assert& s, location_t loc = boost::none) override; + void write(std::ostream& os) const override; void initialize_loop_counter(const label_t) override; crab::bound_t get_loop_count_upper_bound() override; string_invariant to_set() override; @@ -114,18 +116,18 @@ class abstract_domain_t { abstract_domain_t widen(const abstract_domain_t& abs, bool); abstract_domain_t narrow(const abstract_domain_t& abs) const; void operator()(const basic_block_t& bb); - void operator()(const Undefined& s); - void operator()(const Bin& s); - void operator()(const Un& s); - void operator()(const LoadMapFd& s); - void operator()(const Call& s); - void operator()(const Exit& s); - void operator()(const Jmp& s); - void operator()(const Mem& s); - void operator()(const Packet& s); - void operator()(const Assume& s); - void operator()(const Assert& s); - void write(std::ostream& o) const; + void operator()(const Undefined& s, location_t loc = boost::none); + void operator()(const Bin& s, location_t loc = boost::none); + void operator()(const Un& s, location_t loc = boost::none); + void operator()(const LoadMapFd& s, location_t loc = boost::none); + void operator()(const Call& s, location_t loc = boost::none); + void operator()(const Exit& s, location_t loc = boost::none); + void operator()(const Jmp& s, location_t loc = boost::none); + void operator()(const Mem& s, location_t loc = boost::none); + void operator()(const Packet& s, location_t loc = boost::none); + void operator()(const Assume& s, location_t loc = boost::none); + void operator()(const Assert& s, location_t loc = boost::none); + void write(std::ostream& os) const; crab::bound_t get_loop_count_upper_bound(); void initialize_loop_counter(const label_t); string_invariant to_set(); diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index 9fb6d0811..822961689 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -1417,7 +1417,7 @@ void ebpf_domain_t::check_access_shared(NumAbsDomain& inv, const linear_expressi require(inv, ub <= region_size, std::string("Upper bound must be at most ") + region_size.name()); } -void ebpf_domain_t::operator()(const Assume& s) { +void ebpf_domain_t::operator()(const Assume& s, location_t loc) { Condition cond = s.cond; auto dst = reg_pack(cond.left); if (std::holds_alternative(cond.right)) { @@ -1450,9 +1450,9 @@ void ebpf_domain_t::operator()(const Assume& s) { } } -void ebpf_domain_t::operator()(const Undefined& a) {} +void ebpf_domain_t::operator()(const Undefined& a, location_t loc) {} -void ebpf_domain_t::operator()(const Un& stmt) { +void ebpf_domain_t::operator()(const Un& stmt, location_t loc) { auto dst = reg_pack(stmt.dst); auto swap_endianness = [&](variable_t v, auto input, const auto& be_or_le) { if (m_inv.entail(type_is_number(stmt.dst))) { @@ -1516,11 +1516,11 @@ void ebpf_domain_t::operator()(const Un& stmt) { } } -void ebpf_domain_t::operator()(const Exit& a) {} +void ebpf_domain_t::operator()(const Exit& a, location_t loc) {} -void ebpf_domain_t::operator()(const Jmp& a) {} +void ebpf_domain_t::operator()(const Jmp& a, location_t loc) {} -void ebpf_domain_t::operator()(const Comparable& s) { +void ebpf_domain_t::operator()(const Comparable& s, location_t loc) { using namespace crab::dsl_syntax; if (type_inv.same_type(m_inv, s.r1, s.r2)) { // Same type. If both are numbers, that's okay. Otherwise: @@ -1541,12 +1541,12 @@ void ebpf_domain_t::operator()(const Comparable& s) { }; } -void ebpf_domain_t::operator()(const Addable& s) { +void ebpf_domain_t::operator()(const Addable& s, location_t loc) { if (!type_inv.implies_type(m_inv, type_is_pointer(reg_pack(s.ptr)), type_is_number(s.num))) require(m_inv, linear_constraint_t::FALSE(), "Only numbers can be added to pointers"); } -void ebpf_domain_t::operator()(const ValidDivisor& s) { +void ebpf_domain_t::operator()(const ValidDivisor& s, location_t loc) { using namespace crab::dsl_syntax; auto reg = reg_pack(s.reg); if (!type_inv.implies_type(m_inv, type_is_pointer(reg), type_is_number(s.reg))) @@ -1557,12 +1557,12 @@ void ebpf_domain_t::operator()(const ValidDivisor& s) { } } -void ebpf_domain_t::operator()(const ValidStore& s) { +void ebpf_domain_t::operator()(const ValidStore& s, location_t loc) { if (!type_inv.implies_type(m_inv, type_is_not_stack(reg_pack(s.mem)), type_is_number(s.val))) require(m_inv, linear_constraint_t::FALSE(), "Only numbers can be stored to externally-visible regions"); } -void ebpf_domain_t::operator()(const TypeConstraint& s) { +void ebpf_domain_t::operator()(const TypeConstraint& s, location_t loc) { if (!type_inv.is_in_group(m_inv, s.reg, s.types)) require(m_inv, linear_constraint_t::FALSE(), "Invalid type"); } @@ -1589,7 +1589,7 @@ void ebpf_domain_t::operator()(const FuncConstraint& s) { require(m_inv, linear_constraint_t::FALSE(), "callx helper function id is not a valid singleton"); } -void ebpf_domain_t::operator()(const ValidSize& s) { +void ebpf_domain_t::operator()(const ValidSize& s, location_t loc) { using namespace crab::dsl_syntax; auto r = reg_pack(s.reg); require(m_inv, s.can_be_zero ? r.svalue >= 0 : r.svalue > 0, "Invalid size"); @@ -1698,7 +1698,7 @@ crab::interval_t ebpf_domain_t::get_map_max_entries(const Reg& map_fd_reg) const return result; } -void ebpf_domain_t::operator()(const ValidMapKeyValue& s) { +void ebpf_domain_t::operator()(const ValidMapKeyValue& s, location_t loc) { using namespace crab::dsl_syntax; auto fd_type = get_map_type(s.map_fd_reg); @@ -1764,7 +1764,7 @@ void ebpf_domain_t::operator()(const ValidMapKeyValue& s) { }); } -void ebpf_domain_t::operator()(const ValidAccess& s) { +void ebpf_domain_t::operator()(const ValidAccess& s, location_t loc) { using namespace crab::dsl_syntax; bool is_comparison_check = s.width == (Value)Imm{0}; @@ -1850,13 +1850,13 @@ void ebpf_domain_t::operator()(const ValidAccess& s) { }); } -void ebpf_domain_t::operator()(const ZeroCtxOffset& s) { +void ebpf_domain_t::operator()(const ZeroCtxOffset& s, location_t loc) { using namespace crab::dsl_syntax; auto reg = reg_pack(s.reg); require(m_inv, reg.ctx_offset == 0, "Nonzero context offset"); } -void ebpf_domain_t::operator()(const Assert& stmt) { +void ebpf_domain_t::operator()(const Assert& stmt, location_t loc) { if (check_require || thread_local_options.assume_assertions) { this->current_assertion = to_string(stmt.cst); std::visit(*this, stmt.cst); @@ -1864,7 +1864,7 @@ void ebpf_domain_t::operator()(const Assert& stmt) { } } -void ebpf_domain_t::operator()(const Packet& a) { +void ebpf_domain_t::operator()(const Packet& a, location_t loc) { auto reg = reg_pack(R0_RETURN_VALUE); Reg r0_reg{(uint8_t)R0_RETURN_VALUE}; type_inv.assign_type(m_inv, r0_reg, T_NUM); @@ -2119,7 +2119,7 @@ void ebpf_domain_t::do_store_stack(NumAbsDomain& inv, const number_t& width, con } } -void ebpf_domain_t::operator()(const Mem& b) { +void ebpf_domain_t::operator()(const Mem& b, location_t loc) { if (m_inv.is_bottom()) return; if (std::holds_alternative(b.value)) { @@ -2175,7 +2175,7 @@ static Bin atomic_to_bin(const Atomic& a) { return bin; } -void ebpf_domain_t::operator()(const Atomic& a) { +void ebpf_domain_t::operator()(const Atomic& a, location_t loc) { if (m_inv.is_bottom()) return; if (!m_inv.entail(type_is_pointer(reg_pack(a.access.basereg))) || @@ -2224,7 +2224,7 @@ void ebpf_domain_t::operator()(const Atomic& a) { type_inv.havoc_type(m_inv, r11); } -void ebpf_domain_t::operator()(const Call& call) { +void ebpf_domain_t::operator()(const Call& call, location_t loc) { using namespace crab::dsl_syntax; if (m_inv.is_bottom()) return; @@ -2321,7 +2321,7 @@ void ebpf_domain_t::operator()(const Call& call) { } } -void ebpf_domain_t::operator()(const Callx& callx) { +void ebpf_domain_t::operator()(const Callx& callx, location_t loc) { using namespace crab::dsl_syntax; if (m_inv.is_bottom()) return; @@ -2356,7 +2356,7 @@ void ebpf_domain_t::do_load_mapfd(const Reg& dst_reg, int mapfd, bool maybe_null assign_valid_ptr(dst_reg, maybe_null); } -void ebpf_domain_t::operator()(const LoadMapFd& ins) { do_load_mapfd(ins.dst, ins.mapfd, false); } +void ebpf_domain_t::operator()(const LoadMapFd& ins, location_t loc) { do_load_mapfd(ins.dst, ins.mapfd, false); } void ebpf_domain_t::assign_valid_ptr(const Reg& dst_reg, bool maybe_null) { using namespace crab::dsl_syntax; @@ -2578,7 +2578,7 @@ void ebpf_domain_t::ashr(const Reg& dst_reg, const linear_expression_t& right_sv havoc_offsets(dst_reg); } -void ebpf_domain_t::operator()(const Bin& bin) { +void ebpf_domain_t::operator()(const Bin& bin, location_t loc) { using namespace crab::dsl_syntax; auto dst = reg_pack(bin.dst); @@ -3010,7 +3010,7 @@ bound_t ebpf_domain_t::get_loop_count_upper_bound() { return ub; } -void ebpf_domain_t::operator()(const IncrementLoopCounter& ins) { +void ebpf_domain_t::operator()(const IncrementLoopCounter& ins, location_t loc) { this->add(variable_t::loop_counter(to_string(ins.name)), 1); } } // namespace crab diff --git a/src/crab/ebpf_domain.hpp b/src/crab/ebpf_domain.hpp index 92b683d1c..b3147f395 100644 --- a/src/crab/ebpf_domain.hpp +++ b/src/crab/ebpf_domain.hpp @@ -23,6 +23,7 @@ struct reg_pack_t; class ebpf_domain_t final { struct TypeDomain; + using location_t = boost::optional>; public: ebpf_domain_t(); ebpf_domain_t(crab::domains::NumAbsDomain inv, crab::domains::array_domain_t stack); @@ -56,30 +57,30 @@ class ebpf_domain_t final { // abstract transformers void operator()(const basic_block_t& bb); - void operator()(const Addable&); - void operator()(const Assert&); - void operator()(const Assume&); - void operator()(const Bin&); - void operator()(const Call&); - void operator()(const Callx&); - void operator()(const Comparable&); - void operator()(const Exit&); + void operator()(const Addable&, location_t loc = boost::none); + void operator()(const Assert&, location_t loc = boost::none); + void operator()(const Assume&, location_t loc = boost::none); + void operator()(const Bin&, location_t loc = boost::none); + void operator()(const Call&, location_t loc = boost::none); + void operator()(const Callx&, location_t loc = boost::none); + void operator()(const Comparable&, location_t loc = boost::none); + void operator()(const Exit&, location_t loc = boost::none); void operator()(const FuncConstraint&); - void operator()(const Jmp&); - void operator()(const LoadMapFd&); - void operator()(const Atomic&); - void operator()(const Mem&); - void operator()(const ValidDivisor&); - void operator()(const Packet&); - void operator()(const TypeConstraint&); - void operator()(const Un&); - void operator()(const Undefined&); - void operator()(const ValidAccess&); - void operator()(const ValidMapKeyValue&); - void operator()(const ValidSize&); - void operator()(const ValidStore&); - void operator()(const ZeroCtxOffset&); - void operator()(const IncrementLoopCounter&); + void operator()(const Jmp&, location_t loc = boost::none); + void operator()(const LoadMapFd&, location_t loc = boost::none); + void operator()(const Atomic&, location_t loc = boost::none); + void operator()(const Mem&, location_t loc = boost::none); + void operator()(const ValidDivisor&, location_t loc = boost::none); + void operator()(const Packet&, location_t loc = boost::none); + void operator()(const TypeConstraint&, location_t loc = boost::none); + void operator()(const Un&, location_t loc = boost::none); + void operator()(const Undefined&, location_t loc = boost::none); + void operator()(const ValidAccess&, location_t loc = boost::none); + void operator()(const ValidMapKeyValue&, location_t loc = boost::none); + void operator()(const ValidSize&, location_t loc = boost::none); + void operator()(const ValidStore&, location_t loc = boost::none); + void operator()(const ZeroCtxOffset&, location_t loc = boost::none); + void operator()(const IncrementLoopCounter&, location_t loc = boost::none); // write operation is important to keep in ebpf_domain_t because of the parametric abstract domain void write(std::ostream& o) const; diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 9cc50a56a..3cf26f3a2 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -124,7 +124,7 @@ std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p) { } std::ostream& operator<<(std::ostream& o, const reg_with_loc_t& reg) { - o << "r" << static_cast(reg.r) << "@" << reg.loc.second << " in " << reg.loc.first; + o << "r" << static_cast(reg.r) << "@" << reg.loc->second << " in " << reg.loc->first; return o; } @@ -137,8 +137,9 @@ std::size_t reg_with_loc_t::hash() const { using std::hash; std::size_t seed = hash()(r); - seed ^= hash()(loc.first.from) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - seed ^= hash()(loc.second) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= hash()(loc->first.from) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= hash()(loc->first.to) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= hash()(loc->second) + 0x9e3779b9 + (seed << 6) + (seed >> 2); return seed; } @@ -342,6 +343,12 @@ bool type_domain_t::operator<=(const type_domain_t& abs) const { return true; } +type_domain_t type_domain_t::widen(const type_domain_t& other, bool to_constants) { + // WARNING: Not implemented yet + type_domain_t res{m_types | other.m_types, m_stack | other.m_stack, other.m_ctx}; + return res; +} + void type_domain_t::operator|=(const type_domain_t& abs) { type_domain_t tmp{abs}; operator|=(std::move(tmp)); @@ -362,7 +369,7 @@ type_domain_t type_domain_t::operator|(const type_domain_t& other) const { else if (other.is_bottom() || is_top()) { return *this; } - return type_domain_t(m_types | other.m_types, m_stack | other.m_stack, m_label, other.m_ctx); + return type_domain_t(m_types | other.m_types, m_stack | other.m_stack, other.m_ctx); } type_domain_t type_domain_t::operator|(type_domain_t&& other) const { @@ -372,52 +379,55 @@ type_domain_t type_domain_t::operator|(type_domain_t&& other) const { else if (other.is_bottom() || is_top()) { return *this; } - return type_domain_t(m_types | std::move(other.m_types), m_stack | std::move(other.m_stack), m_label, other.m_ctx); + return type_domain_t(m_types | std::move(other.m_types), m_stack | std::move(other.m_stack), other.m_ctx); } type_domain_t type_domain_t::operator&(const type_domain_t& abs) const { return abs; } -type_domain_t type_domain_t::widen(const type_domain_t& abs) const { - return abs; -} - type_domain_t type_domain_t::narrow(const type_domain_t& other) const { return other; } void type_domain_t::write(std::ostream& os) const { - //os << m_types; + os << m_types; os << m_stack << "\n"; } -std::string type_domain_t::domain_name() const { - return "type_domain"; +void type_domain_t::initialize_loop_counter(label_t label) { + // WARNING: Not implemented yet } -crab::bound_t type_domain_t::get_instruction_count_upper_bound() { - return crab::bound_t(crab::number_t(0)); +crab::bound_t type_domain_t::get_loop_count_upper_bound() { + // WARNING: Not implemented yet + return crab::bound_t{crab::number_t{0}}; } string_invariant type_domain_t::to_set() { return string_invariant{}; } -void type_domain_t::operator()(const Undefined & u) { +void type_domain_t::operator()(const Undefined & u, location_t loc) { std::cout << " " << u << ";\n"; } -void type_domain_t::operator()(const Un &u) { +void type_domain_t::operator()(const Un &u, location_t loc) { std::cout << " " << u << ";\n"; } -void type_domain_t::operator()(const LoadMapFd &u) { +void type_domain_t::operator()(const LoadMapFd &u, location_t loc) { std::cout << " " << u << ";\n"; m_types -= u.dst.v; } -void type_domain_t::operator()(const Call &u) { +void type_domain_t::operator()(const Atomic &u, location_t loc) { + // WARNING: Not implemented yet +} +void type_domain_t::operator()(const IncrementLoopCounter &u, location_t loc) { + // WARNING: Not implemented yet +} +void type_domain_t::operator()(const Call &u, location_t loc) { register_t r0_reg{R0_RETURN_VALUE}; if (u.is_map_lookup) { - auto r0 = reg_with_loc_t(r0_reg, m_label, m_curr_pos); + auto r0 = reg_with_loc_t(r0_reg, loc); auto type = ptr_no_off_t(crab::region::T_SHARED); m_types.insert(r0_reg, r0, type); print_annotated(u, type, std::cout); @@ -427,24 +437,24 @@ void type_domain_t::operator()(const Call &u) { std::cout << " " << u << ";\n"; } } -void type_domain_t::operator()(const Exit &u) { +void type_domain_t::operator()(const Callx &u, location_t loc) { + // WARNING: Not implemented yet +} +void type_domain_t::operator()(const Exit &u, location_t loc) { std::cout << " " << u << ";\n"; } -void type_domain_t::operator()(const Jmp &u) { +void type_domain_t::operator()(const Jmp &u, location_t loc) { std::cout << " " << u << ";\n"; } -void type_domain_t::operator()(const Packet & u) { +void type_domain_t::operator()(const Packet & u, location_t loc) { std::cout << " " << u << ";\n"; //CRAB_ERROR("type_error: loading from packet region not allowed"); m_types -= register_t{0}; } -void type_domain_t::operator()(const LockAdd &u) { - std::cout << " " << u << ";\n"; -} -void type_domain_t::operator()(const Assume &u) { +void type_domain_t::operator()(const Assume &u, location_t loc) { std::cout << " " << u << ";\n"; } -void type_domain_t::operator()(const Assert &u) { +void type_domain_t::operator()(const Assert &u, location_t loc) { std::cout << " " << u << ";\n"; } @@ -470,14 +480,12 @@ type_domain_t type_domain_t::setup_entry() { live_registers_t vars; register_types_t typ(std::move(vars), all_types); - auto r1 = reg_with_loc_t(R1_ARG, label_t::entry, 0); - auto r10 = reg_with_loc_t(R10_STACK_POINTER, label_t::entry, 0); + auto r1 = reg_with_loc_t(R1_ARG, std::make_pair(label_t::entry, static_cast(0))); + auto r10 = reg_with_loc_t(R10_STACK_POINTER, std::make_pair(label_t::entry, static_cast(0))); typ.insert(R1_ARG, r1, ptr_with_off_t(crab::region::T_CTX, 0)); typ.insert(R10_STACK_POINTER, r10, ptr_with_off_t(crab::region::T_STACK, 512)); - std::cout << "is types bottom in the start: " << typ.is_bottom() << "\n"; - std::cout << "Initial register types:\n"; auto it = typ.find(R1_ARG); if (it) { @@ -493,12 +501,11 @@ type_domain_t type_domain_t::setup_entry() { } std::cout << "\n"; - type_domain_t inv(std::move(typ), crab::stack_t::top(), label_t::entry, ctx); + type_domain_t inv(std::move(typ), crab::stack_t::top(), ctx); return inv; } -void type_domain_t::operator()(const Bin& bin) { - +void type_domain_t::operator()(const Bin& bin, location_t loc) { if (std::holds_alternative(bin.v)) { Reg src = std::get(bin.v); switch (bin.op) @@ -512,7 +519,7 @@ void type_domain_t::operator()(const Bin& bin) { break; } - auto reg = reg_with_loc_t(bin.dst.v, m_label, m_curr_pos); + auto reg = reg_with_loc_t(bin.dst.v, loc); m_types.insert(bin.dst.v, reg, it.value()); break; } @@ -528,7 +535,7 @@ void type_domain_t::operator()(const Bin& bin) { std::cout << " " << bin << ";\n"; } -void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { +void type_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc) { int offset = b.access.offset; Reg basereg = b.access.basereg; @@ -565,13 +572,13 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { if (std::holds_alternative(type_loaded)) { ptr_with_off_t type_loaded_with_off = std::get(type_loaded); - auto reg = reg_with_loc_t(target_reg.v, m_label, m_curr_pos); + auto reg = reg_with_loc_t(target_reg.v, loc); m_types.insert(target_reg.v, reg, type_loaded_with_off); print_annotated(b, type_loaded_with_off, std::cout); } else { ptr_no_off_t type_loaded_no_off = std::get(type_loaded); - auto reg = reg_with_loc_t(target_reg.v, m_label, m_curr_pos); + auto reg = reg_with_loc_t(target_reg.v, loc); m_types.insert(target_reg.v, reg, type_loaded_no_off); print_annotated(b, type_loaded_no_off, std::cout); } @@ -590,7 +597,7 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { } ptr_no_off_t type_loaded = it.value(); - auto reg = reg_with_loc_t(target_reg.v, m_label, m_curr_pos); + auto reg = reg_with_loc_t(target_reg.v, loc); m_types.insert(target_reg.v, reg, type_loaded); print_annotated(b, type_loaded, std::cout); break; @@ -602,7 +609,7 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg) { } } -void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { +void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t loc) { std::cout << " " << b << ";\n"; int offset = b.access.offset; @@ -673,26 +680,26 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg) { } } -void type_domain_t::operator()(const Mem& b) { +void type_domain_t::operator()(const Mem& b, location_t loc) { if (std::holds_alternative(b.value)) { if (b.is_load) { - do_load(b, std::get(b.value)); + do_load(b, std::get(b.value), loc); } else { - do_mem_store(b, std::get(b.value)); + do_mem_store(b, std::get(b.value), loc); } } else { CRAB_ERROR("Either loading to a number (not allowed) or storing a number (not allowed yet) - ", std::get(b.value).v); } } -void type_domain_t::operator()(const basic_block_t& bb, bool check_termination) { - m_curr_pos = 0; - m_label = bb.label(); - std::cout << m_label << ":\n"; +void type_domain_t::operator()(const basic_block_t& bb) { + uint32_t curr_pos = 0; + auto label = bb.label(); + std::cout << label << ":\n"; for (const Instruction& statement : bb) { - m_curr_pos++; - std::visit(*this, statement); + location_t loc = location_t(std::make_pair(label, ++curr_pos)); + std::visit([this, loc](const auto& v) { std::apply(*this, std::make_pair(v, loc)); }, statement); } auto [it, et] = bb.next_blocks(); if (it != et) { diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 22bd0e30b..ec918b5c1 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -52,12 +52,13 @@ struct ptr_with_off_t { using ptr_t = std::variant; using register_t = uint8_t; +using location_t = boost::optional>; struct reg_with_loc_t { register_t r; - std::pair loc; + location_t loc; - reg_with_loc_t(register_t _r, const label_t& l, uint32_t loc_instr) : r(_r), loc(std::make_pair(l, loc_instr)) {} + reg_with_loc_t(register_t _r, location_t _loc) : r(_r), loc(_loc) {} bool operator==(const reg_with_loc_t& other) const; std::size_t hash() const; friend std::ostream& operator<<(std::ostream& o, const reg_with_loc_t& reg); @@ -132,18 +133,16 @@ class type_domain_t final { crab::stack_t m_stack; crab::register_types_t m_types; std::shared_ptr m_ctx; - label_t m_label; - uint32_t m_curr_pos = 0; public: - type_domain_t() : m_label(label_t::entry) {} + type_domain_t() = default; type_domain_t(type_domain_t&& o) = default; type_domain_t(const type_domain_t& o) = default; type_domain_t& operator=(type_domain_t&& o) = default; type_domain_t& operator=(const type_domain_t& o) = default; - type_domain_t(crab::register_types_t&& _types, crab::stack_t&& _st, const label_t& _l, std::shared_ptr _ctx) - : m_stack(std::move(_st)), m_types(std::move(_types)), m_ctx(_ctx), m_label(_l) {} + type_domain_t(crab::register_types_t&& _types, crab::stack_t&& _st, std::shared_ptr _ctx) + : m_stack(std::move(_st)), m_types(std::move(_types)), m_ctx(_ctx) {} // eBPF initialization: R1 points to ctx, R10 to stack, etc. static type_domain_t setup_entry(); // bottom/top @@ -162,35 +161,37 @@ class type_domain_t final { // meet type_domain_t operator&(const type_domain_t& other) const; // widening - type_domain_t widen(const type_domain_t& other) const; + type_domain_t widen(const type_domain_t& other, bool); // narrowing type_domain_t narrow(const type_domain_t& other) const; //forget void operator-=(crab::variable_t var); //// abstract transformers - void operator()(const Undefined &); - void operator()(const Bin &); - void operator()(const Un &) ; - void operator()(const LoadMapFd &); - void operator()(const Call &); - void operator()(const Exit &); - void operator()(const Jmp &); - void operator()(const Mem &); - void operator()(const Packet &); - void operator()(const LockAdd &); - void operator()(const Assume &); - void operator()(const Assert &); - void operator()(const basic_block_t& bb, bool check_termination); + void operator()(const Undefined &, location_t loc = boost::none); + void operator()(const Bin &, location_t loc = boost::none); + void operator()(const Un &, location_t loc = boost::none); + void operator()(const LoadMapFd &, location_t loc = boost::none); + void operator()(const Atomic&, location_t loc = boost::none); + void operator()(const Call &, location_t loc = boost::none); + void operator()(const Callx&, location_t loc = boost::none); + void operator()(const Exit &, location_t loc = boost::none); + void operator()(const Jmp &, location_t loc = boost::none); + void operator()(const Mem &, location_t loc = boost::none); + void operator()(const Packet &, location_t loc = boost::none); + void operator()(const Assume &, location_t loc = boost::none); + void operator()(const Assert &, location_t loc = boost::none); + void operator()(const IncrementLoopCounter&, location_t loc = boost::none); + void operator()(const basic_block_t& bb); void write(std::ostream& os) const; - std::string domain_name() const; - crab::bound_t get_instruction_count_upper_bound(); + void initialize_loop_counter(label_t label); + crab::bound_t get_loop_count_upper_bound(); string_invariant to_set(); void set_require_check(check_require_func_t f); private: - void do_load(const Mem&, const Reg&); - void do_mem_store(const Mem&, const Reg&); + void do_load(const Mem&, const Reg&, location_t); + void do_mem_store(const Mem&, const Reg&, location_t); }; // end type_domain_t From 56b9fce1d6a4e14e2c8d8856ceef77f01ff29c90 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Thu, 31 Mar 2022 07:57:05 -0400 Subject: [PATCH 089/373] Pretty printing fix Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 3cf26f3a2..23ea9b9b4 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -124,7 +124,7 @@ std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p) { } std::ostream& operator<<(std::ostream& o, const reg_with_loc_t& reg) { - o << "r" << static_cast(reg.r) << "@" << reg.loc->second << " in " << reg.loc->first; + o << "r" << static_cast(reg.r) << "@" << reg.loc->second << " in " << reg.loc->first << " "; return o; } @@ -464,13 +464,15 @@ void print_info() { std::cout << " shared_p = shared pointer\n"; std::cout << " stack_p = stack pointer at offset n\n"; std::cout << " ctx_p = context pointer at offset n\n"; - std::cout << " 'context = _|_' means context contains no elements stored\n\n"; + std::cout << " 'context = _|_' means context contains no elements stored\n"; + std::cout << " when invoked with print invariants option\n"; + std::cout << " 'r@n in bb : p_type' means register 'r' in basic block 'bb' at offset 'n' has type 'p_type'\n\n"; std::cout << "**************************************************************\n\n"; } type_domain_t type_domain_t::setup_entry() { - //print_info(); + print_info(); std::shared_ptr ctx = std::make_shared(global_program_info.get().type.context_descriptor); std::shared_ptr all_types = std::make_shared(); From 59c4015bdfd12de053917d2f46059cb93e00ab82 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 25 Apr 2022 12:22:04 -0400 Subject: [PATCH 090/373] configured types as separate domain option; refactored some redundant code Signed-off-by: Ameer Hamza Conflicts: src/main/check.cpp Author: Ameer Hamza Date: Mon Apr 25 12:22:04 2022 -0400 Changes to be committed: modified: src/ebpf_proof.cpp modified: src/main/check.cpp --- src/ebpf_proof.cpp | 1 + src/main/check.cpp | 17 +++++------------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/ebpf_proof.cpp b/src/ebpf_proof.cpp index 0ec47b746..58203f08e 100644 --- a/src/ebpf_proof.cpp +++ b/src/ebpf_proof.cpp @@ -8,6 +8,7 @@ bool ebpf_generate_proof(std::ostream& s, const InstructionSeq& prog, const prog if (!results.pass_verify()) { // If the program is not correct then we cannot generate a proof + std::cout << "Proof generation not implemented\n"; return false; } diff --git a/src/main/check.cpp b/src/main/check.cpp index ed9a00c40..968c02c9e 100644 --- a/src/main/check.cpp +++ b/src/main/check.cpp @@ -55,7 +55,7 @@ int main(int argc, char** argv) { app.add_flag("-l", list, "List sections"); std::string domain = "zoneCrab"; - std::set doms{"stats", "linux", "zoneCrab", "cfg"}; + std::set doms{"stats", "linux", "zoneCrab", "types", "cfg"}; app.add_set("-d,--dom,--domain", domain, doms, "Abstract domain")->type_name("DOMAIN"); app.add_flag("--termination", ebpf_verifier_options.check_termination, "Verify termination"); @@ -77,9 +77,6 @@ int main(int argc, char** argv) { app.add_flag("--line-info", ebpf_verifier_options.print_line_info, "Print line information"); app.add_flag("--print-btf-types", ebpf_verifier_options.dump_btf_types_json, "Print BTF types"); - bool gen_proof = false; - app.add_flag("--gen-proof", gen_proof, "Generate a proof if program is proven correct"); - std::string asmfile; app.add_option("--asm", asmfile, "Print disassembly to FILE")->type_name("FILE"); std::string dotfile; @@ -92,9 +89,6 @@ int main(int argc, char** argv) { ebpf_verifier_options.print_invariants = ebpf_verifier_options.print_failures = true; ebpf_verifier_options.allow_division_by_zero = !no_division_by_zero; - if (gen_proof) - ebpf_verifier_options.abstract_domain = abstract_domain_kind::TYPE_DOMAIN; - // Main program if (filename == "@headers") { @@ -169,8 +163,11 @@ int main(int argc, char** argv) { print_map_descriptors(global_program_info->map_descriptors, out); } - if (domain == "zoneCrab") { + if (domain == "zoneCrab" || domain == "types") { ebpf_verifier_stats_t verifier_stats; + if (domain == "types") { + ebpf_verifier_options.abstract_domain = abstract_domain_kind::TYPE_DOMAIN; + } auto [res, seconds] = timed_execution([&] { return ebpf_verify_program(std::cout, prog, raw_prog.info, &ebpf_verifier_options, &verifier_stats); }); @@ -179,10 +176,6 @@ int main(int argc, char** argv) { } std::cout << res.pass_verify() << "," << seconds << "," << resident_set_size_kb() << "\n"; - if (gen_proof) { - ebpf_generate_proof(std::cout, prog, raw_prog.info, &ebpf_verifier_options, res); - } - return !res.pass_verify(); } else if (domain == "linux") { // Pass the instruction sequence to the Linux kernel verifier. From ec91cd2393389e280d40f053f87becc9114f5e38 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Wed, 27 Apr 2022 09:50:44 -0400 Subject: [PATCH 091/373] Move printing after the analysis; by default, prints minimal types; -v option prints types for debugging Author: Ameer Hamza Date: Wed Apr 27 09:50:44 2022 -0400 --- src/crab/abstract_domain.cpp | 74 ++++----- src/crab/abstract_domain.hpp | 72 ++++----- src/crab/ebpf_domain.cpp | 48 +++--- src/crab/ebpf_domain.hpp | 50 +++--- src/crab/type_domain.cpp | 294 ++++++++++++++++++++++------------- src/crab/type_domain.hpp | 37 +++-- src/crab_verifier.cpp | 8 +- 7 files changed, 339 insertions(+), 244 deletions(-) diff --git a/src/crab/abstract_domain.cpp b/src/crab/abstract_domain.cpp index f89cd5576..8a5fb1c7f 100644 --- a/src/crab/abstract_domain.cpp +++ b/src/crab/abstract_domain.cpp @@ -105,63 +105,63 @@ abstract_domain_t::abstract_domain_model::narrow(const abstract_domain_t } template -void abstract_domain_t::abstract_domain_model::operator()(const basic_block_t& bb) { - m_abs_val.operator()(bb); +void abstract_domain_t::abstract_domain_model::operator()(const basic_block_t& bb, int print) { + m_abs_val.operator()(bb, print); } template -void abstract_domain_t::abstract_domain_model::operator()(const Undefined& s, location_t loc) { - m_abs_val.operator()(s, loc); +void abstract_domain_t::abstract_domain_model::operator()(const Undefined& s, location_t loc, int print) { + m_abs_val.operator()(s, loc, print); } template -void abstract_domain_t::abstract_domain_model::operator()(const Bin& s, location_t loc) { - m_abs_val.operator()(s, loc); +void abstract_domain_t::abstract_domain_model::operator()(const Bin& s, location_t loc, int print) { + m_abs_val.operator()(s, loc, print); } template -void abstract_domain_t::abstract_domain_model::operator()(const Un& s, location_t loc) { - m_abs_val.operator()(s, loc); +void abstract_domain_t::abstract_domain_model::operator()(const Un& s, location_t loc, int print) { + m_abs_val.operator()(s, loc, print); } template -void abstract_domain_t::abstract_domain_model::operator()(const LoadMapFd& s, location_t loc) { - m_abs_val.operator()(s, loc); +void abstract_domain_t::abstract_domain_model::operator()(const LoadMapFd& s, location_t loc, int print) { + m_abs_val.operator()(s, loc, print); } template -void abstract_domain_t::abstract_domain_model::operator()(const Call& s, location_t loc) { - m_abs_val.operator()(s, loc); +void abstract_domain_t::abstract_domain_model::operator()(const Call& s, location_t loc, int print) { + m_abs_val.operator()(s, loc, print); } template -void abstract_domain_t::abstract_domain_model::operator()(const Exit& s, location_t loc) { - m_abs_val.operator()(s, loc); +void abstract_domain_t::abstract_domain_model::operator()(const Exit& s, location_t loc, int print) { + m_abs_val.operator()(s, loc, print); } template -void abstract_domain_t::abstract_domain_model::operator()(const Jmp& s, location_t loc) { - m_abs_val.operator()(s, loc); +void abstract_domain_t::abstract_domain_model::operator()(const Jmp& s, location_t loc, int print) { + m_abs_val.operator()(s, loc, print); } template -void abstract_domain_t::abstract_domain_model::operator()(const Mem& s, location_t loc) { - m_abs_val.operator()(s, loc); +void abstract_domain_t::abstract_domain_model::operator()(const Mem& s, location_t loc, int print) { + m_abs_val.operator()(s, loc, print); } template -void abstract_domain_t::abstract_domain_model::operator()(const Packet& s, location_t loc) { - m_abs_val.operator()(s, loc); +void abstract_domain_t::abstract_domain_model::operator()(const Packet& s, location_t loc, int print) { + m_abs_val.operator()(s, loc, print); } template -void abstract_domain_t::abstract_domain_model::operator()(const Assume& s, location_t loc) { - m_abs_val.operator()(s, loc); +void abstract_domain_t::abstract_domain_model::operator()(const Assume& s, location_t loc, int print) { + m_abs_val.operator()(s, loc, print); } template -void abstract_domain_t::abstract_domain_model::operator()(const Assert& s, location_t loc) { - m_abs_val.operator()(s, loc); +void abstract_domain_t::abstract_domain_model::operator()(const Assert& s, location_t loc, int print) { + m_abs_val.operator()(s, loc, print); } template @@ -241,31 +241,31 @@ abstract_domain_t abstract_domain_t::narrow(const abstract_domain_t& abs) const return abstract_domain_t(std::move(m_concept->narrow(*(abs.m_concept)))); } -void abstract_domain_t::operator()(const basic_block_t& bb) { - m_concept->operator()(bb); +void abstract_domain_t::operator()(const basic_block_t& bb, int print) { + m_concept->operator()(bb, print); } -void abstract_domain_t::operator()(const Undefined& s, location_t loc) { m_concept->operator()(s, loc); } +void abstract_domain_t::operator()(const Undefined& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } -void abstract_domain_t::operator()(const Bin& s, location_t loc) { m_concept->operator()(s, loc); } +void abstract_domain_t::operator()(const Bin& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } -void abstract_domain_t::operator()(const Un& s, location_t loc) { m_concept->operator()(s, loc); } +void abstract_domain_t::operator()(const Un& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } -void abstract_domain_t::operator()(const LoadMapFd& s, location_t loc) { m_concept->operator()(s, loc); } +void abstract_domain_t::operator()(const LoadMapFd& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } -void abstract_domain_t::operator()(const Call& s, location_t loc) { m_concept->operator()(s, loc); } +void abstract_domain_t::operator()(const Call& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } -void abstract_domain_t::operator()(const Exit& s, location_t loc) { m_concept->operator()(s, loc); } +void abstract_domain_t::operator()(const Exit& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } -void abstract_domain_t::operator()(const Jmp& s, location_t loc) { m_concept->operator()(s, loc); } +void abstract_domain_t::operator()(const Jmp& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } -void abstract_domain_t::operator()(const Mem& s, location_t loc) { m_concept->operator()(s, loc); } +void abstract_domain_t::operator()(const Mem& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } -void abstract_domain_t::operator()(const Packet& s, location_t loc) { m_concept->operator()(s, loc); } +void abstract_domain_t::operator()(const Packet& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } -void abstract_domain_t::operator()(const Assume& s, location_t loc) { m_concept->operator()(s, loc); } +void abstract_domain_t::operator()(const Assume& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } -void abstract_domain_t::operator()(const Assert& s, location_t loc) { m_concept->operator()(s, loc); } +void abstract_domain_t::operator()(const Assert& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } void abstract_domain_t::write(std::ostream& os) const { m_concept->write(os); } diff --git a/src/crab/abstract_domain.hpp b/src/crab/abstract_domain.hpp index c3f8a43e5..172d93f36 100644 --- a/src/crab/abstract_domain.hpp +++ b/src/crab/abstract_domain.hpp @@ -31,18 +31,18 @@ class abstract_domain_t { virtual std::unique_ptr operator&(const abstract_domain_concept& abs) const = 0; virtual std::unique_ptr widen(const abstract_domain_concept& abs, bool) = 0; virtual std::unique_ptr narrow(const abstract_domain_concept& abs) const = 0; - virtual void operator()(const basic_block_t&) = 0; - virtual void operator()(const Undefined&, location_t loc = boost::none) = 0; - virtual void operator()(const Bin&, location_t loc = boost::none) = 0; - virtual void operator()(const Un&, location_t loc = boost::none) = 0; - virtual void operator()(const LoadMapFd&, location_t loc = boost::none) = 0; - virtual void operator()(const Call&, location_t loc = boost::none) = 0; - virtual void operator()(const Exit&, location_t loc = boost::none) = 0; - virtual void operator()(const Jmp&, location_t loc = boost::none) = 0; - virtual void operator()(const Mem&, location_t loc = boost::none) = 0; - virtual void operator()(const Packet&, location_t loc = boost::none) = 0; - virtual void operator()(const Assume&, location_t loc = boost::none) = 0; - virtual void operator()(const Assert&, location_t loc = boost::none) = 0; + virtual void operator()(const basic_block_t&, int) = 0; + virtual void operator()(const Undefined&, location_t, int) = 0; + virtual void operator()(const Bin&, location_t, int) = 0; + virtual void operator()(const Un&, location_t, int) = 0; + virtual void operator()(const LoadMapFd&, location_t, int) = 0; + virtual void operator()(const Call&, location_t, int) = 0; + virtual void operator()(const Exit&, location_t, int) = 0; + virtual void operator()(const Jmp&, location_t, int) = 0; + virtual void operator()(const Mem&, location_t, int) = 0; + virtual void operator()(const Packet&, location_t, int) = 0; + virtual void operator()(const Assume&, location_t, int) = 0; + virtual void operator()(const Assert&, location_t, int) = 0; virtual void write(std::ostream& os) const = 0; /* These operations are not very conventional for an abstract @@ -73,18 +73,18 @@ class abstract_domain_t { std::unique_ptr operator&(const abstract_domain_concept& abs) const override; std::unique_ptr widen(const abstract_domain_concept& abs, bool) override; std::unique_ptr narrow(const abstract_domain_concept& abs) const override; - void operator()(const basic_block_t& bb) override; - void operator()(const Undefined& s, location_t loc = boost::none) override; - void operator()(const Bin& s, location_t loc = boost::none) override; - void operator()(const Un& s, location_t loc = boost::none) override; - void operator()(const LoadMapFd& s, location_t loc = boost::none) override; - void operator()(const Call& s, location_t loc = boost::none) override; - void operator()(const Exit& s, location_t loc = boost::none) override; - void operator()(const Jmp& s, location_t loc = boost::none) override; - void operator()(const Mem& s, location_t loc = boost::none) override; - void operator()(const Packet& s, location_t loc = boost::none) override; - void operator()(const Assume& s, location_t loc = boost::none) override; - void operator()(const Assert& s, location_t loc = boost::none) override; + void operator()(const basic_block_t& bb, int print = 0) override; + void operator()(const Undefined& s, location_t loc = boost::none, int print = 0) override; + void operator()(const Bin& s, location_t loc = boost::none, int print = 0) override; + void operator()(const Un& s, location_t loc = boost::none, int print = 0) override; + void operator()(const LoadMapFd& s, location_t loc = boost::none, int print = 0) override; + void operator()(const Call& s, location_t loc = boost::none, int print = 0) override; + void operator()(const Exit& s, location_t loc = boost::none, int print = 0) override; + void operator()(const Jmp& s, location_t loc = boost::none, int print = 0) override; + void operator()(const Mem& s, location_t loc = boost::none, int print = 0) override; + void operator()(const Packet& s, location_t loc = boost::none, int print = 0) override; + void operator()(const Assume& s, location_t loc = boost::none, int print = 0) override; + void operator()(const Assert& s, location_t loc = boost::none, int print = 0) override; void write(std::ostream& os) const override; void initialize_loop_counter(const label_t) override; crab::bound_t get_loop_count_upper_bound() override; @@ -115,18 +115,18 @@ class abstract_domain_t { abstract_domain_t operator&(const abstract_domain_t& abs) const; abstract_domain_t widen(const abstract_domain_t& abs, bool); abstract_domain_t narrow(const abstract_domain_t& abs) const; - void operator()(const basic_block_t& bb); - void operator()(const Undefined& s, location_t loc = boost::none); - void operator()(const Bin& s, location_t loc = boost::none); - void operator()(const Un& s, location_t loc = boost::none); - void operator()(const LoadMapFd& s, location_t loc = boost::none); - void operator()(const Call& s, location_t loc = boost::none); - void operator()(const Exit& s, location_t loc = boost::none); - void operator()(const Jmp& s, location_t loc = boost::none); - void operator()(const Mem& s, location_t loc = boost::none); - void operator()(const Packet& s, location_t loc = boost::none); - void operator()(const Assume& s, location_t loc = boost::none); - void operator()(const Assert& s, location_t loc = boost::none); + void operator()(const basic_block_t& bb, int print = 0); + void operator()(const Undefined& s, location_t loc = boost::none, int print = 0); + void operator()(const Bin& s, location_t loc = boost::none, int print = 0); + void operator()(const Un& s, location_t loc = boost::none, int print = 0); + void operator()(const LoadMapFd& s, location_t loc = boost::none, int print = 0); + void operator()(const Call& s, location_t loc = boost::none, int print = 0); + void operator()(const Exit& s, location_t loc = boost::none, int print = 0); + void operator()(const Jmp& s, location_t loc = boost::none, int print = 0); + void operator()(const Mem& s, location_t loc = boost::none, int print = 0); + void operator()(const Packet& s, location_t loc = boost::none, int print = 0); + void operator()(const Assume& s, location_t loc = boost::none, int print = 0); + void operator()(const Assert& s, location_t loc = boost::none, int print = 0); void write(std::ostream& os) const; crab::bound_t get_loop_count_upper_bound(); void initialize_loop_counter(const label_t); diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index 822961689..5cb1738c3 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -1377,7 +1377,7 @@ void ebpf_domain_t::overflow_unsigned(NumAbsDomain& inv, variable_t lhs, int fin overflow_bounds(inv, lhs, span, finite_width, false); } -void ebpf_domain_t::operator()(const basic_block_t& bb) { +void ebpf_domain_t::operator()(const basic_block_t& bb, int print) { for (const Instruction& statement : bb) { std::visit(*this, statement); } @@ -1417,7 +1417,7 @@ void ebpf_domain_t::check_access_shared(NumAbsDomain& inv, const linear_expressi require(inv, ub <= region_size, std::string("Upper bound must be at most ") + region_size.name()); } -void ebpf_domain_t::operator()(const Assume& s, location_t loc) { +void ebpf_domain_t::operator()(const Assume& s, location_t loc, int print) { Condition cond = s.cond; auto dst = reg_pack(cond.left); if (std::holds_alternative(cond.right)) { @@ -1450,9 +1450,9 @@ void ebpf_domain_t::operator()(const Assume& s, location_t loc) { } } -void ebpf_domain_t::operator()(const Undefined& a, location_t loc) {} +void ebpf_domain_t::operator()(const Undefined& a, location_t loc, int print) {} -void ebpf_domain_t::operator()(const Un& stmt, location_t loc) { +void ebpf_domain_t::operator()(const Un& stmt, location_t loc, int print) { auto dst = reg_pack(stmt.dst); auto swap_endianness = [&](variable_t v, auto input, const auto& be_or_le) { if (m_inv.entail(type_is_number(stmt.dst))) { @@ -1516,11 +1516,11 @@ void ebpf_domain_t::operator()(const Un& stmt, location_t loc) { } } -void ebpf_domain_t::operator()(const Exit& a, location_t loc) {} +void ebpf_domain_t::operator()(const Exit& a, location_t loc, int print) {} -void ebpf_domain_t::operator()(const Jmp& a, location_t loc) {} +void ebpf_domain_t::operator()(const Jmp& a, location_t loc, int print) {} -void ebpf_domain_t::operator()(const Comparable& s, location_t loc) { +void ebpf_domain_t::operator()(const Comparable& s, location_t loc, int print) { using namespace crab::dsl_syntax; if (type_inv.same_type(m_inv, s.r1, s.r2)) { // Same type. If both are numbers, that's okay. Otherwise: @@ -1541,12 +1541,12 @@ void ebpf_domain_t::operator()(const Comparable& s, location_t loc) { }; } -void ebpf_domain_t::operator()(const Addable& s, location_t loc) { +void ebpf_domain_t::operator()(const Addable& s, location_t loc, int print) { if (!type_inv.implies_type(m_inv, type_is_pointer(reg_pack(s.ptr)), type_is_number(s.num))) require(m_inv, linear_constraint_t::FALSE(), "Only numbers can be added to pointers"); } -void ebpf_domain_t::operator()(const ValidDivisor& s, location_t loc) { +void ebpf_domain_t::operator()(const ValidDivisor& s, location_t loc, int print) { using namespace crab::dsl_syntax; auto reg = reg_pack(s.reg); if (!type_inv.implies_type(m_inv, type_is_pointer(reg), type_is_number(s.reg))) @@ -1557,12 +1557,12 @@ void ebpf_domain_t::operator()(const ValidDivisor& s, location_t loc) { } } -void ebpf_domain_t::operator()(const ValidStore& s, location_t loc) { +void ebpf_domain_t::operator()(const ValidStore& s, location_t loc, int print) { if (!type_inv.implies_type(m_inv, type_is_not_stack(reg_pack(s.mem)), type_is_number(s.val))) require(m_inv, linear_constraint_t::FALSE(), "Only numbers can be stored to externally-visible regions"); } -void ebpf_domain_t::operator()(const TypeConstraint& s, location_t loc) { +void ebpf_domain_t::operator()(const TypeConstraint& s, location_t loc, int print) { if (!type_inv.is_in_group(m_inv, s.reg, s.types)) require(m_inv, linear_constraint_t::FALSE(), "Invalid type"); } @@ -1589,7 +1589,7 @@ void ebpf_domain_t::operator()(const FuncConstraint& s) { require(m_inv, linear_constraint_t::FALSE(), "callx helper function id is not a valid singleton"); } -void ebpf_domain_t::operator()(const ValidSize& s, location_t loc) { +void ebpf_domain_t::operator()(const ValidSize& s, location_t loc, int print) { using namespace crab::dsl_syntax; auto r = reg_pack(s.reg); require(m_inv, s.can_be_zero ? r.svalue >= 0 : r.svalue > 0, "Invalid size"); @@ -1698,7 +1698,7 @@ crab::interval_t ebpf_domain_t::get_map_max_entries(const Reg& map_fd_reg) const return result; } -void ebpf_domain_t::operator()(const ValidMapKeyValue& s, location_t loc) { +void ebpf_domain_t::operator()(const ValidMapKeyValue& s, location_t loc, int print) { using namespace crab::dsl_syntax; auto fd_type = get_map_type(s.map_fd_reg); @@ -1764,7 +1764,7 @@ void ebpf_domain_t::operator()(const ValidMapKeyValue& s, location_t loc) { }); } -void ebpf_domain_t::operator()(const ValidAccess& s, location_t loc) { +void ebpf_domain_t::operator()(const ValidAccess& s, location_t loc, int print) { using namespace crab::dsl_syntax; bool is_comparison_check = s.width == (Value)Imm{0}; @@ -1850,13 +1850,13 @@ void ebpf_domain_t::operator()(const ValidAccess& s, location_t loc) { }); } -void ebpf_domain_t::operator()(const ZeroCtxOffset& s, location_t loc) { +void ebpf_domain_t::operator()(const ZeroCtxOffset& s, location_t loc, int print) { using namespace crab::dsl_syntax; auto reg = reg_pack(s.reg); require(m_inv, reg.ctx_offset == 0, "Nonzero context offset"); } -void ebpf_domain_t::operator()(const Assert& stmt, location_t loc) { +void ebpf_domain_t::operator()(const Assert& stmt, location_t loc, int print) { if (check_require || thread_local_options.assume_assertions) { this->current_assertion = to_string(stmt.cst); std::visit(*this, stmt.cst); @@ -1864,7 +1864,7 @@ void ebpf_domain_t::operator()(const Assert& stmt, location_t loc) { } } -void ebpf_domain_t::operator()(const Packet& a, location_t loc) { +void ebpf_domain_t::operator()(const Packet& a, location_t loc, int print) { auto reg = reg_pack(R0_RETURN_VALUE); Reg r0_reg{(uint8_t)R0_RETURN_VALUE}; type_inv.assign_type(m_inv, r0_reg, T_NUM); @@ -2119,7 +2119,7 @@ void ebpf_domain_t::do_store_stack(NumAbsDomain& inv, const number_t& width, con } } -void ebpf_domain_t::operator()(const Mem& b, location_t loc) { +void ebpf_domain_t::operator()(const Mem& b, location_t loc, int print) { if (m_inv.is_bottom()) return; if (std::holds_alternative(b.value)) { @@ -2175,7 +2175,7 @@ static Bin atomic_to_bin(const Atomic& a) { return bin; } -void ebpf_domain_t::operator()(const Atomic& a, location_t loc) { +void ebpf_domain_t::operator()(const Atomic& a, location_t loc, int print) { if (m_inv.is_bottom()) return; if (!m_inv.entail(type_is_pointer(reg_pack(a.access.basereg))) || @@ -2224,7 +2224,7 @@ void ebpf_domain_t::operator()(const Atomic& a, location_t loc) { type_inv.havoc_type(m_inv, r11); } -void ebpf_domain_t::operator()(const Call& call, location_t loc) { +void ebpf_domain_t::operator()(const Call& call, location_t loc, int print) { using namespace crab::dsl_syntax; if (m_inv.is_bottom()) return; @@ -2321,7 +2321,7 @@ void ebpf_domain_t::operator()(const Call& call, location_t loc) { } } -void ebpf_domain_t::operator()(const Callx& callx, location_t loc) { +void ebpf_domain_t::operator()(const Callx& callx, location_t loc, int print) { using namespace crab::dsl_syntax; if (m_inv.is_bottom()) return; @@ -2356,7 +2356,7 @@ void ebpf_domain_t::do_load_mapfd(const Reg& dst_reg, int mapfd, bool maybe_null assign_valid_ptr(dst_reg, maybe_null); } -void ebpf_domain_t::operator()(const LoadMapFd& ins, location_t loc) { do_load_mapfd(ins.dst, ins.mapfd, false); } +void ebpf_domain_t::operator()(const LoadMapFd& ins, location_t loc, int print) { do_load_mapfd(ins.dst, ins.mapfd, false); } void ebpf_domain_t::assign_valid_ptr(const Reg& dst_reg, bool maybe_null) { using namespace crab::dsl_syntax; @@ -2578,7 +2578,7 @@ void ebpf_domain_t::ashr(const Reg& dst_reg, const linear_expression_t& right_sv havoc_offsets(dst_reg); } -void ebpf_domain_t::operator()(const Bin& bin, location_t loc) { +void ebpf_domain_t::operator()(const Bin& bin, location_t loc, int print) { using namespace crab::dsl_syntax; auto dst = reg_pack(bin.dst); @@ -3010,7 +3010,7 @@ bound_t ebpf_domain_t::get_loop_count_upper_bound() { return ub; } -void ebpf_domain_t::operator()(const IncrementLoopCounter& ins, location_t loc) { +void ebpf_domain_t::operator()(const IncrementLoopCounter& ins, location_t loc, int print) { this->add(variable_t::loop_counter(to_string(ins.name)), 1); } } // namespace crab diff --git a/src/crab/ebpf_domain.hpp b/src/crab/ebpf_domain.hpp index b3147f395..620764bc6 100644 --- a/src/crab/ebpf_domain.hpp +++ b/src/crab/ebpf_domain.hpp @@ -55,32 +55,32 @@ class ebpf_domain_t final { string_invariant to_set(); // abstract transformers - void operator()(const basic_block_t& bb); - - void operator()(const Addable&, location_t loc = boost::none); - void operator()(const Assert&, location_t loc = boost::none); - void operator()(const Assume&, location_t loc = boost::none); - void operator()(const Bin&, location_t loc = boost::none); - void operator()(const Call&, location_t loc = boost::none); - void operator()(const Callx&, location_t loc = boost::none); - void operator()(const Comparable&, location_t loc = boost::none); - void operator()(const Exit&, location_t loc = boost::none); + void operator()(const basic_block_t& bb, int print = 0); + + void operator()(const Addable&, location_t loc = boost::none, int print = 0); + void operator()(const Assert&, location_t loc = boost::none, int print = 0); + void operator()(const Assume&, location_t loc = boost::none, int print = 0); + void operator()(const Bin&, location_t loc = boost::none, int print = 0); + void operator()(const Call&, location_t loc = boost::none, int print = 0); + void operator()(const Callx&, location_t loc = boost::none, int print = 0); + void operator()(const Comparable&, location_t loc = boost::none, int print = 0); + void operator()(const Exit&, location_t loc = boost::none, int print = 0); void operator()(const FuncConstraint&); - void operator()(const Jmp&, location_t loc = boost::none); - void operator()(const LoadMapFd&, location_t loc = boost::none); - void operator()(const Atomic&, location_t loc = boost::none); - void operator()(const Mem&, location_t loc = boost::none); - void operator()(const ValidDivisor&, location_t loc = boost::none); - void operator()(const Packet&, location_t loc = boost::none); - void operator()(const TypeConstraint&, location_t loc = boost::none); - void operator()(const Un&, location_t loc = boost::none); - void operator()(const Undefined&, location_t loc = boost::none); - void operator()(const ValidAccess&, location_t loc = boost::none); - void operator()(const ValidMapKeyValue&, location_t loc = boost::none); - void operator()(const ValidSize&, location_t loc = boost::none); - void operator()(const ValidStore&, location_t loc = boost::none); - void operator()(const ZeroCtxOffset&, location_t loc = boost::none); - void operator()(const IncrementLoopCounter&, location_t loc = boost::none); + void operator()(const Jmp&, location_t loc = boost::none, int print = 0); + void operator()(const LoadMapFd&, location_t loc = boost::none, int print = 0); + void operator()(const Atomic&, location_t loc = boost::none, int print = 0); + void operator()(const Mem&, location_t loc = boost::none, int print = 0); + void operator()(const ValidDivisor&, location_t loc = boost::none, int print = 0); + void operator()(const Packet&, location_t loc = boost::none, int print = 0); + void operator()(const TypeConstraint&, location_t loc = boost::none, int print = 0); + void operator()(const Un&, location_t loc = boost::none, int print = 0); + void operator()(const Undefined&, location_t loc = boost::none, int print = 0); + void operator()(const ValidAccess&, location_t loc = boost::none, int print = 0); + void operator()(const ValidMapKeyValue&, location_t loc = boost::none, int print = 0); + void operator()(const ValidSize&, location_t loc = boost::none, int print = 0); + void operator()(const ValidStore&, location_t loc = boost::none, int print = 0); + void operator()(const ZeroCtxOffset&, location_t loc = boost::none, int print = 0); + void operator()(const IncrementLoopCounter&, location_t loc = boost::none, int print = 0); // write operation is important to keep in ebpf_domain_t because of the parametric abstract domain void write(std::ostream& o) const; diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 23ea9b9b4..a37c8640f 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -78,6 +78,12 @@ void print_annotated(Call const& call, const ptr_t& p, std::ostream& os_) { os_ << " = " << call.name << ":" << call.func << "(...)\n"; } +void print_annotated(Bin const& b, const ptr_t& p, std::ostream& os_) { + os_ << " "; + print_type(b.dst.v, p); + os_ << " = r" << static_cast(std::get(b.v).v) << ";"; +} + namespace crab { inline std::string get_reg_ptr(const region& r) { @@ -162,7 +168,7 @@ std::ostream& operator<<(std::ostream& o, const stack_t& st) { std::ostream& operator<<(std::ostream& o, const ctx_t& _ctx) { - o << "type of context: " << (_ctx.m_packet_ptrs.empty() ? "_|_" : "") << "\n"; + o << "type of context: " << (_ctx.m_packet_ptrs.empty() ? "top" : "") << "\n"; for (const auto& it : _ctx.m_packet_ptrs) { o << " stores at " << it.first << ": " << it.second << "\n"; } @@ -317,10 +323,12 @@ std::optional stack_t::find(int key) const { } bool type_domain_t::is_bottom() const { + if (m_is_bottom) return true; return (m_stack.is_bottom() || m_types.is_bottom()); } bool type_domain_t::is_top() const { + if (m_is_bottom) return false; return (m_stack.is_top() && m_types.is_top()); } @@ -331,6 +339,7 @@ type_domain_t type_domain_t::bottom() { } void type_domain_t::set_to_bottom() { + m_is_bottom = true; m_types.set_to_bottom(); } @@ -408,77 +417,90 @@ string_invariant type_domain_t::to_set() { return string_invariant{}; } -void type_domain_t::operator()(const Undefined & u, location_t loc) { - std::cout << " " << u << ";\n"; +void type_domain_t::operator()(const Undefined & u, location_t loc, int print) { + if (is_bottom()) return; + if (print > 0) + std::cout << " " << u << ";\n"; } -void type_domain_t::operator()(const Un &u, location_t loc) { - std::cout << " " << u << ";\n"; +void type_domain_t::operator()(const Un &u, location_t loc, int print) { + if (is_bottom()) return; + if (print > 0) + std::cout << " " << u << ";\n"; } -void type_domain_t::operator()(const LoadMapFd &u, location_t loc) { - std::cout << " " << u << ";\n"; +void type_domain_t::operator()(const LoadMapFd &u, location_t loc, int print) { + if (is_bottom()) return; + if (print > 0) { + std::cout << " " << u << ";\n"; + return; + } m_types -= u.dst.v; } -void type_domain_t::operator()(const Atomic &u, location_t loc) { +void type_domain_t::operator()(const Atomic &u, location_t loc, int print) { // WARNING: Not implemented yet } -void type_domain_t::operator()(const IncrementLoopCounter &u, location_t loc) { +void type_domain_t::operator()(const IncrementLoopCounter &u, location_t loc, int print) { // WARNING: Not implemented yet } -void type_domain_t::operator()(const Call &u, location_t loc) { +void type_domain_t::operator()(const Call &u, location_t loc, int print) { + if (is_bottom()) return; register_t r0_reg{R0_RETURN_VALUE}; + auto r0 = reg_with_loc_t(r0_reg, loc); + if (print > 0) { + if (u.is_map_lookup) { + auto it = m_types.find(r0); + if (it) { + print_annotated(u, it.value(), std::cout); + } + } + else + std::cout << " " << u << ";\n"; + return; + } if (u.is_map_lookup) { - auto r0 = reg_with_loc_t(r0_reg, loc); auto type = ptr_no_off_t(crab::region::T_SHARED); m_types.insert(r0_reg, r0, type); - print_annotated(u, type, std::cout); } else { m_types -= r0_reg; - std::cout << " " << u << ";\n"; } } -void type_domain_t::operator()(const Callx &u, location_t loc) { +void type_domain_t::operator()(const Callx &u, location_t loc, int print) { // WARNING: Not implemented yet } -void type_domain_t::operator()(const Exit &u, location_t loc) { - std::cout << " " << u << ";\n"; +void type_domain_t::operator()(const Exit &u, location_t loc, int print) { + if (is_bottom()) return; + if (print > 0) + std::cout << " " << u << ";\n"; } -void type_domain_t::operator()(const Jmp &u, location_t loc) { - std::cout << " " << u << ";\n"; +void type_domain_t::operator()(const Jmp &u, location_t loc, int print) { + if (is_bottom()) return; + if (print > 0) + std::cout << " " << u << ";\n"; } -void type_domain_t::operator()(const Packet & u, location_t loc) { - std::cout << " " << u << ";\n"; - //CRAB_ERROR("type_error: loading from packet region not allowed"); +void type_domain_t::operator()(const Packet & u, location_t loc, int print) { + if (is_bottom()) return; + if (print > 0) { + std::cout << " " << u << ";\n"; + return; + } m_types -= register_t{0}; } -void type_domain_t::operator()(const Assume &u, location_t loc) { - std::cout << " " << u << ";\n"; -} -void type_domain_t::operator()(const Assert &u, location_t loc) { - std::cout << " " << u << ";\n"; +void type_domain_t::operator()(const Assume &u, location_t loc, int print) { + if (is_bottom()) return; + if (print > 0) + std::cout << " " << u << ";\n"; } - -void print_info() { - std::cout << "\nhow to interpret:\n"; - std::cout << " packet_p = packet pointer\n"; - std::cout << " shared_p = shared pointer\n"; - std::cout << " stack_p = stack pointer at offset n\n"; - std::cout << " ctx_p = context pointer at offset n\n"; - std::cout << " 'context = _|_' means context contains no elements stored\n"; - std::cout << " when invoked with print invariants option\n"; - std::cout << " 'r@n in bb : p_type' means register 'r' in basic block 'bb' at offset 'n' has type 'p_type'\n\n"; - std::cout << "**************************************************************\n\n"; +void type_domain_t::operator()(const Assert &u, location_t loc, int print) { + if (is_bottom()) return; + if (print > 0) + std::cout << " " << u << ";\n"; } type_domain_t type_domain_t::setup_entry() { - print_info(); - std::shared_ptr ctx = std::make_shared(global_program_info.get().type.context_descriptor); std::shared_ptr all_types = std::make_shared(); - std::cout << *ctx << "\n"; - live_registers_t vars; register_types_t typ(std::move(vars), all_types); @@ -488,26 +510,27 @@ type_domain_t type_domain_t::setup_entry() { typ.insert(R1_ARG, r1, ptr_with_off_t(crab::region::T_CTX, 0)); typ.insert(R10_STACK_POINTER, r10, ptr_with_off_t(crab::region::T_STACK, 512)); - std::cout << "Initial register types:\n"; - auto it = typ.find(R1_ARG); - if (it) { - std::cout << " "; - print_type(R1_ARG, it.value()); - std::cout << "\n"; - } - auto it2 = typ.find(R10_STACK_POINTER); - if (it2) { - std::cout << " "; - print_type(R10_STACK_POINTER, it2.value()); - std::cout << "\n"; - } - std::cout << "\n"; - type_domain_t inv(std::move(typ), crab::stack_t::top(), ctx); return inv; } -void type_domain_t::operator()(const Bin& bin, location_t loc) { +void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { + if (is_bottom()) return; + if (print > 0) { + if (print == 2) { + if (std::holds_alternative(bin.v) && bin.op == Bin::Op::MOV) { + auto reg_with_loc = reg_with_loc_t(bin.dst.v, loc); + auto it = m_types.find(reg_with_loc); + if (it) { + print_annotated(bin, it.value(), std::cout); + std::cout << "\n"; + return; + } + } + } + std::cout << " " << bin << ";\n"; + return; + } if (std::holds_alternative(bin.v)) { Reg src = std::get(bin.v); switch (bin.op) @@ -515,8 +538,7 @@ void type_domain_t::operator()(const Bin& bin, location_t loc) { case Bin::Op::MOV: { auto it = m_types.find(src.v); if (!it) { - //std::cout << " " << bin << "\n"; - //CRAB_ERROR("type error: assigning an unknown pointer or a number - r", (int)src.v); + //std::cout << "type_error: assigning an unknown pointer or a number - r" << (int)src.v << "\n"; m_types -= bin.dst.v; break; } @@ -534,24 +556,33 @@ void type_domain_t::operator()(const Bin& bin, location_t loc) { else { m_types -= bin.dst.v; } - std::cout << " " << bin << ";\n"; } -void type_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc) { +void type_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc, int print) { + if (print > 0) { + auto target_reg_loc = reg_with_loc_t(target_reg.v, loc); + auto it = m_types.find(target_reg_loc); + if (it) + print_annotated(b, it.value(), std::cout); + else + std::cout << " " << b << ";\n"; + return; + } int offset = b.access.offset; Reg basereg = b.access.basereg; auto it = m_types.find(basereg.v); if (!it) { - std::cout << " " << b << "\n"; - CRAB_ERROR("type_error: loading from an unknown pointer, or from number - r", (int)basereg.v); + error_location = loc; + std::cout << "type_error: loading from an unknown pointer, or from number - r" << (int)basereg.v << "\n"; + set_to_bottom(); + return; } ptr_t type_basereg = it.value(); if (std::holds_alternative(type_basereg)) { - std::cout << " " << b << "\n"; - //CRAB_ERROR("type_error: loading from either packet or shared region not allowed - r", (int)basereg.v); + //std::cout << "type_error: loading from either packet or shared region not allowed - r" << (int)basereg.v << "\n"; m_types -= target_reg.v; return; } @@ -565,8 +596,7 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc) auto it = m_stack.find(load_at); if (!it) { - std::cout << " " << b << "\n"; - //CRAB_ERROR("type_error: no field at loaded offset ", load_at, " in stack"); + //std::cout << "type_error: no field at loaded offset " << load_at << " in stack\n"; m_types -= target_reg.v; return; } @@ -576,13 +606,11 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc) ptr_with_off_t type_loaded_with_off = std::get(type_loaded); auto reg = reg_with_loc_t(target_reg.v, loc); m_types.insert(target_reg.v, reg, type_loaded_with_off); - print_annotated(b, type_loaded_with_off, std::cout); } else { ptr_no_off_t type_loaded_no_off = std::get(type_loaded); auto reg = reg_with_loc_t(target_reg.v, loc); m_types.insert(target_reg.v, reg, type_loaded_no_off); - print_annotated(b, type_loaded_no_off, std::cout); } break; @@ -592,8 +620,7 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc) auto it = m_ctx->find(load_at); if (!it) { - std::cout << " " << b << "\n"; - //CRAB_ERROR("type_error: no field at loaded offset ", load_at, " in context"); + //std::cout << "type_error: no field at loaded offset " << load_at << " in context\n"; m_types -= target_reg.v; return; } @@ -601,7 +628,6 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc) auto reg = reg_with_loc_t(target_reg.v, loc); m_types.insert(target_reg.v, reg, type_loaded); - print_annotated(b, type_loaded, std::cout); break; } @@ -611,16 +637,22 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc) } } -void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t loc) { +void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t loc, int print) { - std::cout << " " << b << ";\n"; + if (print > 0) { + std::cout << " " << b << ";\n"; + return; + } int offset = b.access.offset; Reg basereg = b.access.basereg; int width = b.access.width; auto it = m_types.find(basereg.v); if (!it) { - CRAB_ERROR("type_error: storing at an unknown pointer, or from number - r", (int)basereg.v); + error_location = loc; + std::cout << "type_error: storing at an unknown pointer, or from number - r" << (int)basereg.v << "\n"; + set_to_bottom(); + return; } ptr_t type_basereg = it.value(); @@ -634,7 +666,7 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t if (type_basereg_with_off.r == crab::region::T_STACK) { // type of basereg is STACK_P if (!it2) { - //CRAB_ERROR("type_error: storing either a number or an unknown pointer - r", (int)target_reg.v); + //std::cout << "type_error: storing either a number or an unknown pointer - r" << (int)target_reg.v << "\n"; m_stack -= store_at; return; } @@ -642,21 +674,30 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t auto type_to_store = it2.value(); if (std::holds_alternative(type_to_store) && std::get(type_to_store).r == crab::region::T_STACK) { - CRAB_ERROR("type_error: we cannot store stack pointer, r", (int)target_reg.v, ", into stack"); + error_location = loc; + std::cout << "type_error: we cannot store stack pointer, r" << (int)target_reg.v << ", into stack\n"; + set_to_bottom(); + return; } else { for (auto i = store_at; i < store_at+width; i++) { auto it3 = m_stack.find(i); if (it3) { - CRAB_ERROR("type_error: type being stored into stack at ", store_at, " is overlapping with already stored\ - at", i); + error_location = loc; + std::cout << "type_error: type being stored into stack at " << store_at << " is overlapping with already stored\ + at" << i << "\n"; + set_to_bottom(); + return; } } auto it4 = m_stack.find(store_at); if (it4) { auto type_in_stack = it4.value(); if (type_to_store != type_in_stack) { - CRAB_ERROR("type_error: type being stored at offset ", store_at, " is not the same as stored already in stack"); + error_location = loc; + std::cout << "type_error: type being stored at offset " << store_at << " is not the same as stored already in stack\n"; + set_to_bottom(); + return; } } else { @@ -668,7 +709,10 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t else if (type_basereg_with_off.r == crab::region::T_CTX) { // type of basereg is CTX_P if (it2) { - CRAB_ERROR("type_error: we cannot store pointer, r", (int)target_reg.v, ", into ctx"); + error_location = loc; + std::cout << "type_error: we cannot store pointer, r" << (int)target_reg.v << ", into ctx\n"; + set_to_bottom(); + return; } } else @@ -677,47 +721,89 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t else { // base register type is either PACKET_P or SHARED_P if (it2) { - CRAB_ERROR("type_error: we cannot store pointer, r", (int)target_reg.v, ", into packet or shared"); + error_location = loc; + std::cout << "type_error: we cannot store pointer, r" << (int)target_reg.v << ", into packet or shared\n"; + set_to_bottom(); + return; } } } -void type_domain_t::operator()(const Mem& b, location_t loc) { +void type_domain_t::operator()(const Mem& b, location_t loc, int print) { + if (is_bottom()) return; if (std::holds_alternative(b.value)) { if (b.is_load) { - do_load(b, std::get(b.value), loc); + do_load(b, std::get(b.value), loc, print); } else { - do_mem_store(b, std::get(b.value), loc); + do_mem_store(b, std::get(b.value), loc, print); } } else { - CRAB_ERROR("Either loading to a number (not allowed) or storing a number (not allowed yet) - ", std::get(b.value).v); + error_location = loc; + std::cout << "type_error: Either loading to a number (not allowed) or storing a number (not allowed yet) - " << std::get(b.value).v << "\n"; + set_to_bottom(); + return; } } -void type_domain_t::operator()(const basic_block_t& bb) { - uint32_t curr_pos = 0; +void type_domain_t::print_initial_types() { + auto label = label_t::entry; + location_t loc = location_t(std::make_pair(label, 0)); + std::cout << "\n" << *m_ctx << "\n"; + std::cout << m_stack << "\n"; + + std::cout << "Initial register types:\n"; + auto r1_with_loc = reg_with_loc_t(R1_ARG, loc); + auto it = m_types.find(r1_with_loc); + if (it) { + std::cout << " "; + print_type(R1_ARG, it.value()); + std::cout << "\n"; + } + auto r10_with_loc = reg_with_loc_t(R10_STACK_POINTER, loc); + auto it2 = m_types.find(r10_with_loc); + if (it2) { + std::cout << " "; + print_type(R10_STACK_POINTER, it2.value()); + std::cout << "\n"; + } + std::cout << "\n"; +} + +void type_domain_t::operator()(const basic_block_t& bb, int print) { auto label = bb.label(); - std::cout << label << ":\n"; + uint32_t curr_pos = 0; + location_t loc; + if (print > 0) { + if (label == label_t::entry) { + print_initial_types(); + } + std::cout << label << ":\n"; + } + for (const Instruction& statement : bb) { - location_t loc = location_t(std::make_pair(label, ++curr_pos)); - std::visit([this, loc](const auto& v) { std::apply(*this, std::make_pair(v, loc)); }, statement); - } - auto [it, et] = bb.next_blocks(); - if (it != et) { - std::cout << " " - << "goto "; - for (; it != et;) { - std::cout << *it; - ++it; - if (it == et) { - std::cout << ";"; - } else { - std::cout << ","; + loc = location_t(std::make_pair(label, ++curr_pos)); + std::visit([this, loc, print](const auto& v) { std::apply(*this, std::make_tuple(v, loc, print)); }, statement); + if (print > 0 && error_location->first == loc->first && error_location->second == loc->second) CRAB_ERROR("type_error"); + } + + if (print > 0) { + auto [it, et] = bb.next_blocks(); + if (it != et) { + std::cout << " " + << "goto "; + for (; it != et;) { + std::cout << *it; + ++it; + if (it == et) { + std::cout << ";"; + } else { + std::cout << ","; + } } } + std::cout << "\n\n"; } - std::cout << "\n\n"; } void type_domain_t::set_require_check(check_require_func_t f) {} diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index ec918b5c1..81ce3007a 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -130,6 +130,8 @@ class register_types_t { class type_domain_t final { + bool m_is_bottom = false; + location_t error_location = boost::none; crab::stack_t m_stack; crab::register_types_t m_types; std::shared_ptr m_ctx; @@ -168,21 +170,21 @@ class type_domain_t final { void operator-=(crab::variable_t var); //// abstract transformers - void operator()(const Undefined &, location_t loc = boost::none); - void operator()(const Bin &, location_t loc = boost::none); - void operator()(const Un &, location_t loc = boost::none); - void operator()(const LoadMapFd &, location_t loc = boost::none); - void operator()(const Atomic&, location_t loc = boost::none); - void operator()(const Call &, location_t loc = boost::none); - void operator()(const Callx&, location_t loc = boost::none); - void operator()(const Exit &, location_t loc = boost::none); - void operator()(const Jmp &, location_t loc = boost::none); - void operator()(const Mem &, location_t loc = boost::none); - void operator()(const Packet &, location_t loc = boost::none); - void operator()(const Assume &, location_t loc = boost::none); - void operator()(const Assert &, location_t loc = boost::none); - void operator()(const IncrementLoopCounter&, location_t loc = boost::none); - void operator()(const basic_block_t& bb); + void operator()(const Undefined &, location_t loc = boost::none, int print = 0); + void operator()(const Bin &, location_t loc = boost::none, int print = 0); + void operator()(const Un &, location_t loc = boost::none, int print = 0); + void operator()(const LoadMapFd &, location_t loc = boost::none, int print = 0); + void operator()(const Atomic&, location_t loc = boost::none, int print = 0); + void operator()(const Call &, location_t loc = boost::none, int print = 0); + void operator()(const Callx&, location_t loc = boost::none, int print = 0); + void operator()(const Exit &, location_t loc = boost::none, int print = 0); + void operator()(const Jmp &, location_t loc = boost::none, int print = 0); + void operator()(const Mem &, location_t loc = boost::none, int print = 0); + void operator()(const Packet &, location_t loc = boost::none, int print = 0); + void operator()(const Assume &, location_t loc = boost::none, int print = 0); + void operator()(const Assert &, location_t loc = boost::none, int print = 0); + void operator()(const IncrementLoopCounter&, location_t loc = boost::none, int print = 0); + void operator()(const basic_block_t& bb, int print = 0); void write(std::ostream& os) const; void initialize_loop_counter(label_t label); crab::bound_t get_loop_count_upper_bound(); @@ -191,7 +193,8 @@ class type_domain_t final { private: - void do_load(const Mem&, const Reg&, location_t); - void do_mem_store(const Mem&, const Reg&, location_t); + void do_load(const Mem&, const Reg&, location_t, int print = 0); + void do_mem_store(const Mem&, const Reg&, location_t, int print = 0); + void print_initial_types(); }; // end type_domain_t diff --git a/src/crab_verifier.cpp b/src/crab_verifier.cpp index c8c82ec8b..972b5b6f6 100644 --- a/src/crab_verifier.cpp +++ b/src/crab_verifier.cpp @@ -107,7 +107,13 @@ static checks_db get_analysis_report(std::ostream& s, cfg_t& cfg, crab::invarian crab::invariant_table_t& post_invariants) { // Analyze the control-flow graph. checks_db db = generate_report(cfg, pre_invariants, post_invariants); - if (thread_local_options.print_invariants) { + if (thread_local_options.abstract_domain == abstract_domain_kind::TYPE_DOMAIN) { + auto state = post_invariants.at(label_t::exit); + for (const label_t& label : cfg.sorted_labels()) { + state(cfg.get_node(label), thread_local_options.print_invariants ? 2 : 1); + } + } + else if (thread_local_options.print_invariants) { for (const label_t& label : cfg.sorted_labels()) { s << "\nPre-invariant : " << pre_invariants.at(label) << "\n"; s << cfg.get_node(label); From 56244d46be0b8769b2e18ee513e1505c6de75b2f Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Wed, 27 Apr 2022 13:04:23 -0400 Subject: [PATCH 092/373] Resetting bottom flag for type domain when pringing types; refactoring Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 62 +++++++++++++++++++++++----------------- src/crab/type_domain.hpp | 1 + 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index a37c8640f..8cefd7473 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -340,7 +340,6 @@ type_domain_t type_domain_t::bottom() { void type_domain_t::set_to_bottom() { m_is_bottom = true; - m_types.set_to_bottom(); } void type_domain_t::set_to_top() { @@ -514,6 +513,13 @@ type_domain_t type_domain_t::setup_entry() { return inv; } +void type_domain_t::report_type_error(std::string s, location_t loc) { + std::cout << "type_error at line " << loc->second << " in bb " << loc->first << "\n"; + std::cout << s; + error_location = loc; + set_to_bottom(); +} + void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { if (is_bottom()) return; if (print > 0) { @@ -574,9 +580,9 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc, auto it = m_types.find(basereg.v); if (!it) { - error_location = loc; - std::cout << "type_error: loading from an unknown pointer, or from number - r" << (int)basereg.v << "\n"; - set_to_bottom(); + std::string s = std::to_string(static_cast(basereg.v)); + std::string desc = std::string("\tloading from an unknown pointer, or from number - r") + s + "\n"; + report_type_error(desc, loc); return; } ptr_t type_basereg = it.value(); @@ -649,9 +655,9 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t auto it = m_types.find(basereg.v); if (!it) { - error_location = loc; - std::cout << "type_error: storing at an unknown pointer, or from number - r" << (int)basereg.v << "\n"; - set_to_bottom(); + std::string s = std::to_string(static_cast(basereg.v)); + std::string desc = std::string("\tstoring at an unknown pointer, or from number - r") + s + "\n"; + report_type_error(desc, loc); return; } ptr_t type_basereg = it.value(); @@ -674,19 +680,19 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t auto type_to_store = it2.value(); if (std::holds_alternative(type_to_store) && std::get(type_to_store).r == crab::region::T_STACK) { - error_location = loc; - std::cout << "type_error: we cannot store stack pointer, r" << (int)target_reg.v << ", into stack\n"; - set_to_bottom(); + std::string s = std::to_string(static_cast(target_reg.v)); + std::string desc = std::string("\twe cannot store stack pointer, - r") + s + ", into stack\n"; + report_type_error(desc, loc); return; } else { for (auto i = store_at; i < store_at+width; i++) { auto it3 = m_stack.find(i); if (it3) { - error_location = loc; - std::cout << "type_error: type being stored into stack at " << store_at << " is overlapping with already stored\ - at" << i << "\n"; - set_to_bottom(); + std::string s = std::to_string(store_at); + std::string s1 = std::to_string(i); + std::string desc = std::string("\ttype being stored into stack at ") + s + " is overlapping with already stored at " + s1 + "\n"; + report_type_error(desc, loc); return; } } @@ -694,9 +700,9 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t if (it4) { auto type_in_stack = it4.value(); if (type_to_store != type_in_stack) { - error_location = loc; - std::cout << "type_error: type being stored at offset " << store_at << " is not the same as stored already in stack\n"; - set_to_bottom(); + std::string s = std::to_string(store_at); + std::string desc = std::string("\ttype being stored at offset ") + s + " is not the same as already stored in stack\n"; + report_type_error(desc, loc); return; } } @@ -709,9 +715,9 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t else if (type_basereg_with_off.r == crab::region::T_CTX) { // type of basereg is CTX_P if (it2) { - error_location = loc; - std::cout << "type_error: we cannot store pointer, r" << (int)target_reg.v << ", into ctx\n"; - set_to_bottom(); + std::string s = std::to_string(static_cast(target_reg.v)); + std::string desc = std::string("\twe cannot store a pointer, r") + s + ", into ctx\n"; + report_type_error(desc, loc); return; } } @@ -721,9 +727,9 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t else { // base register type is either PACKET_P or SHARED_P if (it2) { - error_location = loc; - std::cout << "type_error: we cannot store pointer, r" << (int)target_reg.v << ", into packet or shared\n"; - set_to_bottom(); + std::string s = std::to_string(static_cast(target_reg.v)); + std::string desc = std::string("\twe cannot store a pointer, r") + s + ", into packet or shared\n"; + report_type_error(desc, loc); return; } } @@ -739,9 +745,9 @@ void type_domain_t::operator()(const Mem& b, location_t loc, int print) { do_mem_store(b, std::get(b.value), loc, print); } } else { - error_location = loc; - std::cout << "type_error: Either loading to a number (not allowed) or storing a number (not allowed yet) - " << std::get(b.value).v << "\n"; - set_to_bottom(); + std::string s = std::to_string(static_cast(std::get(b.value).v)); + std::string desc = std::string("\tEither loading to a number (not allowed) or storing a number (not allowed yet) - ") + s + "\n"; + report_type_error(desc, loc); return; } } @@ -777,14 +783,16 @@ void type_domain_t::operator()(const basic_block_t& bb, int print) { if (print > 0) { if (label == label_t::entry) { print_initial_types(); + m_is_bottom = false; } std::cout << label << ":\n"; } for (const Instruction& statement : bb) { loc = location_t(std::make_pair(label, ++curr_pos)); + if (print > 0) std::cout << " " << curr_pos << "."; std::visit([this, loc, print](const auto& v) { std::apply(*this, std::make_tuple(v, loc, print)); }, statement); - if (print > 0 && error_location->first == loc->first && error_location->second == loc->second) CRAB_ERROR("type_error"); + if (print > 0 && error_location->first == loc->first && error_location->second == loc->second) std::cout << "type_error\n"; } if (print > 0) { diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 81ce3007a..ee7f3a117 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -196,5 +196,6 @@ class type_domain_t final { void do_load(const Mem&, const Reg&, location_t, int print = 0); void do_mem_store(const Mem&, const Reg&, location_t, int print = 0); void print_initial_types(); + void report_type_error(std::string, location_t); }; // end type_domain_t From 153ba80d8860adb22760a81c795663612f74bcb3 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Wed, 11 May 2022 17:21:47 -0400 Subject: [PATCH 093/373] Added support for pointer arithmetic removed conservative type errors, like storing a stack pointer into stack Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 110 +++++++++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 16 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 8cefd7473..f6875c490 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -81,7 +81,17 @@ void print_annotated(Call const& call, const ptr_t& p, std::ostream& os_) { void print_annotated(Bin const& b, const ptr_t& p, std::ostream& os_) { os_ << " "; print_type(b.dst.v, p); - os_ << " = r" << static_cast(std::get(b.v).v) << ";"; + // add better checks as we add more support + if (std::holds_alternative(b.v)) { + if (b.op == Bin::Op::MOV) + os_ << " = r" << static_cast(std::get(b.v).v) << ";"; + else if (b.op == Bin::Op::ADD) + os_ << " += r" << static_cast(std::get(b.v).v) << ";"; + } + else { + if (b.op == Bin::Op::ADD) + os_ << " += " << static_cast(std::get(b.v).v) << ";"; + } } namespace crab { @@ -524,7 +534,8 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { if (is_bottom()) return; if (print > 0) { if (print == 2) { - if (std::holds_alternative(bin.v) && bin.op == Bin::Op::MOV) { + if ((std::holds_alternative(bin.v) && (bin.op == Bin::Op::MOV || bin.op == Bin::Op::ADD)) || + (std::holds_alternative(bin.v) && bin.op == Bin::Op::ADD)) { auto reg_with_loc = reg_with_loc_t(bin.dst.v, loc); auto it = m_types.find(reg_with_loc); if (it) { @@ -537,30 +548,86 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { std::cout << " " << bin << ";\n"; return; } + + ptr_t dst_reg; + if (bin.op == Bin::Op::ADD) { + auto it = m_types.find(bin.dst.v); + if (!it) { + m_types -= bin.dst.v; + return; + } + dst_reg = it.value(); + } + if (std::holds_alternative(bin.v)) { Reg src = std::get(bin.v); switch (bin.op) { case Bin::Op::MOV: { - auto it = m_types.find(src.v); - if (!it) { + auto it1 = m_types.find(src.v); + if (!it1) { //std::cout << "type_error: assigning an unknown pointer or a number - r" << (int)src.v << "\n"; m_types -= bin.dst.v; break; } - auto reg = reg_with_loc_t(bin.dst.v, loc); - m_types.insert(bin.dst.v, reg, it.value()); + m_types.insert(bin.dst.v, reg, it1.value()); + break; + } + case Bin::Op::ADD: { + auto it1 = m_types.find(src.v); + if (it1) { + std::string s = std::to_string(static_cast(src.v)); + std::string s1 = std::to_string(static_cast(bin.dst.v)); + std::string desc = std::string("\taddition of two pointers, r") + s + " and r" + s1 + " not allowed\n"; + report_type_error(desc, loc); + return; + } + else { + if (std::holds_alternative(dst_reg)) { + /* + std::string s = std::to_string(static_cast(bin.dst.v)); + std::string desc = std::string("\toffset of the pointer r") + s + " unknown\n"; + report_type_error(desc, loc); + return; + */ + m_stack.set_to_top(); + m_stack -= std::get(dst_reg).offset; + } + else { + auto reg = reg_with_loc_t(bin.dst.v, loc); + m_types.insert(bin.dst.v, reg, dst_reg); + } + } break; } - default: m_types -= bin.dst.v; break; } } else { - m_types -= bin.dst.v; + int imm = static_cast(std::get(bin.v).v); + switch (bin.op) + { + case Bin::Op::ADD: { + if (std::holds_alternative(dst_reg)) { + auto ptr_with_off = std::get(dst_reg); + ptr_with_off.offset = ptr_with_off.offset + imm; + auto reg = reg_with_loc_t(bin.dst.v, loc); + m_types.insert(bin.dst.v, reg, ptr_with_off); + } + else { + auto reg = reg_with_loc_t(bin.dst.v, loc); + m_types.insert(bin.dst.v, reg, dst_reg); + } + break; + } + default: { + m_types -= bin.dst.v; + break; + } + } } } @@ -665,28 +732,30 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t auto it2 = m_types.find(target_reg.v); if (std::holds_alternative(type_basereg)) { - // we know base register is either CTX_P or STACK_P + // base register is either CTX_P or STACK_P ptr_with_off_t type_basereg_with_off = std::get(type_basereg); int store_at = offset+type_basereg_with_off.offset; if (type_basereg_with_off.r == crab::region::T_STACK) { // type of basereg is STACK_P if (!it2) { - //std::cout << "type_error: storing either a number or an unknown pointer - r" << (int)target_reg.v << "\n"; + // std::cout << "type_error: storing either a number or an unknown pointer - r" << (int)target_reg.v << "\n"; m_stack -= store_at; return; } else { auto type_to_store = it2.value(); + /* if (std::holds_alternative(type_to_store) && std::get(type_to_store).r == crab::region::T_STACK) { std::string s = std::to_string(static_cast(target_reg.v)); - std::string desc = std::string("\twe cannot store stack pointer, - r") + s + ", into stack\n"; - report_type_error(desc, loc); + std::string desc = std::string("\twe cannot store stack pointer, r") + s + ", into stack\n"; + //report_type_error(desc, loc); return; } else { - for (auto i = store_at; i < store_at+width; i++) { + */ + for (auto i = store_at+1; i < store_at+width; i++) { auto it3 = m_stack.find(i); if (it3) { std::string s = std::to_string(store_at); @@ -696,6 +765,10 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t return; } } + m_stack.insert(store_at, type_to_store); + // revise: code below checks if there is already something stored at same location, the type should be the same -- it is very restricted and not required. + // However, when we support storing info like width of type, we need more checks + /* auto it4 = m_stack.find(store_at); if (it4) { auto type_in_stack = it4.value(); @@ -709,7 +782,8 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t else { m_stack.insert(store_at, type_to_store); } - } + */ + //} } } else if (type_basereg_with_off.r == crab::region::T_CTX) { @@ -725,8 +799,12 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t assert(false); } else { - // base register type is either PACKET_P or SHARED_P - if (it2) { + // base register type is either PACKET_P, SHARED_P or STACK_P without known offset + ptr_no_off_t type_basereg_no_off = std::get(type_basereg); + + // if basereg is a stack_p with no offset, we do not store anything, and no type errors + // if we later load with that pointer, we read nothing -- load is no-op + if (it2 && type_basereg_no_off.r != crab::region::T_STACK) { std::string s = std::to_string(static_cast(target_reg.v)); std::string desc = std::string("\twe cannot store a pointer, r") + s + ", into packet or shared\n"; report_type_error(desc, loc); From 6e62c4cb0ed4e18da9e1d8162e81edc1ef560416 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Fri, 13 May 2022 10:14:29 -0400 Subject: [PATCH 094/373] Handling metadata offset properly Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index f6875c490..c976c9b22 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -191,6 +191,8 @@ ctx_t::ctx_t(const ebpf_context_descriptor_t* desc) m_packet_ptrs[desc->data] = crab::ptr_no_off_t(crab::region::T_PACKET); if (desc->end != -1) m_packet_ptrs[desc->end] = crab::ptr_no_off_t(crab::region::T_PACKET); + if (desc->meta != -1) + m_packet_ptrs[desc->meta] = crab::ptr_no_off_t(crab::region::T_PACKET); } std::optional ctx_t::find(int key) const { From b01ce5f39418c9f1271c184bfda40f2be9875801 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Fri, 3 Jun 2022 07:36:28 -0700 Subject: [PATCH 095/373] Refactoring Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 206 +++++++++++++++++++++++---------------- src/crab/type_domain.hpp | 59 ++++++----- 2 files changed, 160 insertions(+), 105 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index c976c9b22..2ea3b7f0e 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -25,6 +25,7 @@ namespace std { }; // does not seem to work for me + /* template <> struct equal_to { constexpr bool operator()(const crab::ptr_t& p1, const crab::ptr_t& p2) const { @@ -32,19 +33,34 @@ namespace std { if (std::holds_alternative(p1)) { auto ptr_no_off1 = std::get(p1); auto ptr_no_off2 = std::get(p2); - return (ptr_no_off1.r == ptr_no_off2.r); + return (ptr_no_off1.get_region() == ptr_no_off2.get_region()); } else { auto ptr_with_off1 = std::get(p1); auto ptr_with_off2 = std::get(p2); - return (ptr_with_off1.r == ptr_with_off2.r && ptr_with_off1.offset == ptr_with_off2.offset); + return (ptr_with_off1.get_region() == ptr_with_off2.get_region() && ptr_with_off1.get_offset() == ptr_with_off2.get_offset()); } } }; + + template <> + struct equal_to { + constexpr bool operator()(const crab::ptr_with_off_t& p1, const crab::ptr_with_off_t& p2) const { + return (p1.get_region() == p2.get_region() && p1.get_offset() == p2.get_offset()); + } + }; + + template <> + struct equal_to { + constexpr bool operator()(const crab::ptr_no_off_t& p1, const crab::ptr_no_off_t& p2) const { + return (p1.get_region() == p2.get_region()); + } + }; + */ } -void print_ptr_type(const ptr_t& p) { +static void print_ptr_type(const ptr_t& p) { if (std::holds_alternative(p)) { auto t = std::get(p); std::cout << t; @@ -55,12 +71,12 @@ void print_ptr_type(const ptr_t& p) { } } -void print_type(register_t r, const ptr_t& p) { +static void print_type(register_t r, const ptr_t& p) { std::cout << "r" << static_cast(r) << " : "; print_ptr_type(p); } -void print_annotated(Mem const& b, const ptr_t& p, std::ostream& os_) { +static void print_annotated(Mem const& b, const ptr_t& p, std::ostream& os_) { if (b.is_load) { os_ << " "; print_type(std::get(b.value).v, p); @@ -72,13 +88,13 @@ void print_annotated(Mem const& b, const ptr_t& p, std::ostream& os_) { os_ << "(" << b.access.basereg << sign << offset << ")\n"; } -void print_annotated(Call const& call, const ptr_t& p, std::ostream& os_) { +static void print_annotated(Call const& call, const ptr_t& p, std::ostream& os_) { os_ << " "; print_type(0, p); os_ << " = " << call.name << ":" << call.func << "(...)\n"; } -void print_annotated(Bin const& b, const ptr_t& p, std::ostream& os_) { +static void print_annotated(Bin const& b, const ptr_t& p, std::ostream& os_) { os_ << " "; print_type(b.dst.v, p); // add better checks as we add more support @@ -115,47 +131,72 @@ inline std::ostream& operator<<(std::ostream& o, const region& t) { } bool operator==(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { - return (p1.r == p2.r && p1.offset == p2.offset); + return (p1.get_region() == p2.get_region() && p1.get_offset() == p2.get_offset()); } -bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { - return !(p1 == p2); +bool ptr_with_off_t::operator!=(const ptr_with_off_t& p2) { + return !(*this == p2); +} + +void ptr_with_off_t::write(std::ostream& o) const { + o << get_reg_ptr(m_r) << "<" << m_offset << ">"; } std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p) { - o << get_reg_ptr(p.r) << "<" << p.offset << ">"; + p.write(o); return o; } +void ptr_with_off_t::set_offset(int off) { m_offset = off; } + +constexpr int ptr_with_off_t::get_offset() const { return m_offset; } + +void ptr_with_off_t::set_region(region r) { m_r = r; } + +constexpr region ptr_with_off_t::get_region() const { return m_r; } + bool operator==(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { - return (p1.r == p2.r); + return (p1.get_region() == p2.get_region()); +} + +bool ptr_no_off_t::operator!=(const ptr_no_off_t& p2) { + return !(*this == p2); } -bool operator!=(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { - return !(p1 == p2); +void ptr_no_off_t::write(std::ostream& o) const { + o << get_reg_ptr(get_region()); } std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p) { - return o << get_reg_ptr(p.r); + p.write(o); + return o; +} + +void ptr_no_off_t::set_region(region r) { m_r = r; } + +constexpr region ptr_no_off_t::get_region() const { return m_r; } + +void reg_with_loc_t::write(std::ostream& o) const { + o << "r" << static_cast(m_reg) << "@" << m_loc->second << " in " << m_loc->first << " "; } std::ostream& operator<<(std::ostream& o, const reg_with_loc_t& reg) { - o << "r" << static_cast(reg.r) << "@" << reg.loc->second << " in " << reg.loc->first << " "; + reg.write(o); return o; } bool reg_with_loc_t::operator==(const reg_with_loc_t& other) const { - return (r == other.r && loc == other.loc); + return (m_reg == other.m_reg && m_loc == other.m_loc); } std::size_t reg_with_loc_t::hash() const { // Similar to boost::hash_combine using std::hash; - std::size_t seed = hash()(r); - seed ^= hash()(loc->first.from) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - seed ^= hash()(loc->first.to) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - seed ^= hash()(loc->second) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + std::size_t seed = hash()(m_reg); + seed ^= hash()(m_loc->first.from) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= hash()(m_loc->first.to) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= hash()(m_loc->second) + 0x9e3779b9 + (seed << 6) + (seed >> 2); return seed; } @@ -206,7 +247,7 @@ std::ostream& operator<<(std::ostream& o, const register_types_t& typ) { if (typ.is_bottom()) o << "_|_\n"; else { - for (const auto& v : *(typ.m_all_types)) { + for (const auto& v : *(typ.m_reg_type_env)) { o << v.first << ": "; print_ptr_type(v.second); o << "\n"; @@ -222,32 +263,32 @@ register_types_t register_types_t::operator|(const register_types_t& other) cons return *this; } live_registers_t out_vars; - for (size_t i = 0; i < m_vars.size(); i++) { - if (m_vars[i] == nullptr || other.m_vars[i] == nullptr) continue; - auto it1 = find(*(m_vars[i])); - auto it2 = other.find(*(other.m_vars[i])); + for (size_t i = 0; i < m_cur_def.size(); i++) { + if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; + auto it1 = find(*(m_cur_def[i])); + auto it2 = other.find(*(other.m_cur_def[i])); if (it1 && it2 && it1.value() == it2.value()) { - out_vars[i] = m_vars[i]; + out_vars[i] = m_cur_def[i]; } } - return register_types_t(std::move(out_vars), m_all_types, false); + return register_types_t(std::move(out_vars), m_reg_type_env, false); } void register_types_t::operator-=(register_t var) { if (is_bottom()) { return; } - m_vars[var] = nullptr; + m_cur_def[var] = nullptr; } void register_types_t::set_to_bottom() { - m_vars = live_registers_t{nullptr}; + m_cur_def = live_registers_t{nullptr}; m_is_bottom = true; } void register_types_t::set_to_top() { - m_vars = live_registers_t{nullptr}; + m_cur_def = live_registers_t{nullptr}; m_is_bottom = false; } @@ -255,27 +296,27 @@ bool register_types_t::is_bottom() const { return m_is_bottom; } bool register_types_t::is_top() const { if (m_is_bottom) { return false; } - if (m_all_types == nullptr) return true; - for (auto it : m_vars) { + if (m_reg_type_env == nullptr) return true; + for (auto it : m_cur_def) { if (it != nullptr) return false; } return true; } void register_types_t::insert(register_t reg, const reg_with_loc_t& reg_with_loc, const ptr_t& type) { - (*m_all_types)[reg_with_loc] = type; - m_vars[reg] = std::make_shared(reg_with_loc); + (*m_reg_type_env)[reg_with_loc] = type; + m_cur_def[reg] = std::make_shared(reg_with_loc); } std::optional register_types_t::find(reg_with_loc_t reg) const { - auto it = m_all_types->find(reg); - if (it == m_all_types->end()) return {}; + auto it = m_reg_type_env->find(reg); + if (it == m_reg_type_env->end()) return {}; return it->second; } std::optional register_types_t::find(register_t key) const { - if (m_vars[key] == nullptr) return {}; - const reg_with_loc_t& reg = *(m_vars[key]); + if (m_cur_def[key] == nullptr) return {}; + const reg_with_loc_t& reg = *(m_cur_def[key]); return find(reg); } @@ -336,12 +377,12 @@ std::optional stack_t::find(int key) const { bool type_domain_t::is_bottom() const { if (m_is_bottom) return true; - return (m_stack.is_bottom() || m_types.is_bottom()); + return (m_stack.is_bottom() || m_registers.is_bottom()); } bool type_domain_t::is_top() const { if (m_is_bottom) return false; - return (m_stack.is_top() && m_types.is_top()); + return (m_stack.is_top() && m_registers.is_top()); } type_domain_t type_domain_t::bottom() { @@ -356,10 +397,11 @@ void type_domain_t::set_to_bottom() { void type_domain_t::set_to_top() { m_stack.set_to_top(); - m_types.set_to_top(); + m_registers.set_to_top(); } bool type_domain_t::operator<=(const type_domain_t& abs) const { + /* WARNING: The operation is not implemented yet.*/ return true; } @@ -389,7 +431,7 @@ type_domain_t type_domain_t::operator|(const type_domain_t& other) const { else if (other.is_bottom() || is_top()) { return *this; } - return type_domain_t(m_types | other.m_types, m_stack | other.m_stack, other.m_ctx); + return type_domain_t(m_registers | other.m_registers, m_stack | other.m_stack, other.m_ctx); } type_domain_t type_domain_t::operator|(type_domain_t&& other) const { @@ -399,7 +441,7 @@ type_domain_t type_domain_t::operator|(type_domain_t&& other) const { else if (other.is_bottom() || is_top()) { return *this; } - return type_domain_t(m_types | std::move(other.m_types), m_stack | std::move(other.m_stack), other.m_ctx); + return type_domain_t(m_registers | std::move(other.m_registers), m_stack | std::move(other.m_stack), other.m_ctx); } type_domain_t type_domain_t::operator&(const type_domain_t& abs) const { @@ -411,7 +453,7 @@ type_domain_t type_domain_t::narrow(const type_domain_t& other) const { } void type_domain_t::write(std::ostream& os) const { - os << m_types; + os << m_registers; os << m_stack << "\n"; } @@ -444,7 +486,7 @@ void type_domain_t::operator()(const LoadMapFd &u, location_t loc, int print) { std::cout << " " << u << ";\n"; return; } - m_types -= u.dst.v; + m_registers -= u.dst.v; } void type_domain_t::operator()(const Atomic &u, location_t loc, int print) { // WARNING: Not implemented yet @@ -458,7 +500,7 @@ void type_domain_t::operator()(const Call &u, location_t loc, int print) { auto r0 = reg_with_loc_t(r0_reg, loc); if (print > 0) { if (u.is_map_lookup) { - auto it = m_types.find(r0); + auto it = m_registers.find(r0); if (it) { print_annotated(u, it.value(), std::cout); } @@ -469,10 +511,10 @@ void type_domain_t::operator()(const Call &u, location_t loc, int print) { } if (u.is_map_lookup) { auto type = ptr_no_off_t(crab::region::T_SHARED); - m_types.insert(r0_reg, r0, type); + m_registers.insert(r0_reg, r0, type); } else { - m_types -= r0_reg; + m_registers -= r0_reg; } } void type_domain_t::operator()(const Callx &u, location_t loc, int print) { @@ -494,7 +536,7 @@ void type_domain_t::operator()(const Packet & u, location_t loc, int print) { std::cout << " " << u << ";\n"; return; } - m_types -= register_t{0}; + m_registers -= register_t{0}; } void type_domain_t::operator()(const Assume &u, location_t loc, int print) { if (is_bottom()) return; @@ -539,7 +581,7 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { if ((std::holds_alternative(bin.v) && (bin.op == Bin::Op::MOV || bin.op == Bin::Op::ADD)) || (std::holds_alternative(bin.v) && bin.op == Bin::Op::ADD)) { auto reg_with_loc = reg_with_loc_t(bin.dst.v, loc); - auto it = m_types.find(reg_with_loc); + auto it = m_registers.find(reg_with_loc); if (it) { print_annotated(bin, it.value(), std::cout); std::cout << "\n"; @@ -553,9 +595,9 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { ptr_t dst_reg; if (bin.op == Bin::Op::ADD) { - auto it = m_types.find(bin.dst.v); + auto it = m_registers.find(bin.dst.v); if (!it) { - m_types -= bin.dst.v; + m_registers -= bin.dst.v; return; } dst_reg = it.value(); @@ -566,18 +608,18 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { switch (bin.op) { case Bin::Op::MOV: { - auto it1 = m_types.find(src.v); + auto it1 = m_registers.find(src.v); if (!it1) { //std::cout << "type_error: assigning an unknown pointer or a number - r" << (int)src.v << "\n"; - m_types -= bin.dst.v; + m_registers -= bin.dst.v; break; } auto reg = reg_with_loc_t(bin.dst.v, loc); - m_types.insert(bin.dst.v, reg, it1.value()); + m_registers.insert(bin.dst.v, reg, it1.value()); break; } case Bin::Op::ADD: { - auto it1 = m_types.find(src.v); + auto it1 = m_registers.find(src.v); if (it1) { std::string s = std::to_string(static_cast(src.v)); std::string s1 = std::to_string(static_cast(bin.dst.v)); @@ -594,17 +636,17 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { return; */ m_stack.set_to_top(); - m_stack -= std::get(dst_reg).offset; + m_stack -= std::get(dst_reg).get_offset(); } else { auto reg = reg_with_loc_t(bin.dst.v, loc); - m_types.insert(bin.dst.v, reg, dst_reg); + m_registers.insert(bin.dst.v, reg, dst_reg); } } break; } default: - m_types -= bin.dst.v; + m_registers -= bin.dst.v; break; } } @@ -615,18 +657,18 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { case Bin::Op::ADD: { if (std::holds_alternative(dst_reg)) { auto ptr_with_off = std::get(dst_reg); - ptr_with_off.offset = ptr_with_off.offset + imm; + ptr_with_off.set_offset(ptr_with_off.get_offset() + imm); auto reg = reg_with_loc_t(bin.dst.v, loc); - m_types.insert(bin.dst.v, reg, ptr_with_off); + m_registers.insert(bin.dst.v, reg, ptr_with_off); } else { auto reg = reg_with_loc_t(bin.dst.v, loc); - m_types.insert(bin.dst.v, reg, dst_reg); + m_registers.insert(bin.dst.v, reg, dst_reg); } break; } default: { - m_types -= bin.dst.v; + m_registers -= bin.dst.v; break; } } @@ -637,7 +679,7 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc, if (print > 0) { auto target_reg_loc = reg_with_loc_t(target_reg.v, loc); - auto it = m_types.find(target_reg_loc); + auto it = m_registers.find(target_reg_loc); if (it) print_annotated(b, it.value(), std::cout); else @@ -647,7 +689,7 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc, int offset = b.access.offset; Reg basereg = b.access.basereg; - auto it = m_types.find(basereg.v); + auto it = m_registers.find(basereg.v); if (!it) { std::string s = std::to_string(static_cast(basereg.v)); std::string desc = std::string("\tloading from an unknown pointer, or from number - r") + s + "\n"; @@ -658,21 +700,21 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc, if (std::holds_alternative(type_basereg)) { //std::cout << "type_error: loading from either packet or shared region not allowed - r" << (int)basereg.v << "\n"; - m_types -= target_reg.v; + m_registers -= target_reg.v; return; } ptr_with_off_t type_with_off = std::get(type_basereg); - int load_at = offset+type_with_off.offset; + int load_at = offset+type_with_off.get_offset(); - switch (type_with_off.r) { + switch (type_with_off.get_region()) { case crab::region::T_STACK: { auto it = m_stack.find(load_at); if (!it) { //std::cout << "type_error: no field at loaded offset " << load_at << " in stack\n"; - m_types -= target_reg.v; + m_registers -= target_reg.v; return; } ptr_t type_loaded = it.value(); @@ -680,12 +722,12 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc, if (std::holds_alternative(type_loaded)) { ptr_with_off_t type_loaded_with_off = std::get(type_loaded); auto reg = reg_with_loc_t(target_reg.v, loc); - m_types.insert(target_reg.v, reg, type_loaded_with_off); + m_registers.insert(target_reg.v, reg, type_loaded_with_off); } else { ptr_no_off_t type_loaded_no_off = std::get(type_loaded); auto reg = reg_with_loc_t(target_reg.v, loc); - m_types.insert(target_reg.v, reg, type_loaded_no_off); + m_registers.insert(target_reg.v, reg, type_loaded_no_off); } break; @@ -696,13 +738,13 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc, if (!it) { //std::cout << "type_error: no field at loaded offset " << load_at << " in context\n"; - m_types -= target_reg.v; + m_registers -= target_reg.v; return; } ptr_no_off_t type_loaded = it.value(); auto reg = reg_with_loc_t(target_reg.v, loc); - m_types.insert(target_reg.v, reg, type_loaded); + m_registers.insert(target_reg.v, reg, type_loaded); break; } @@ -722,7 +764,7 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t Reg basereg = b.access.basereg; int width = b.access.width; - auto it = m_types.find(basereg.v); + auto it = m_registers.find(basereg.v); if (!it) { std::string s = std::to_string(static_cast(basereg.v)); std::string desc = std::string("\tstoring at an unknown pointer, or from number - r") + s + "\n"; @@ -731,14 +773,14 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t } ptr_t type_basereg = it.value(); - auto it2 = m_types.find(target_reg.v); + auto it2 = m_registers.find(target_reg.v); if (std::holds_alternative(type_basereg)) { // base register is either CTX_P or STACK_P ptr_with_off_t type_basereg_with_off = std::get(type_basereg); - int store_at = offset+type_basereg_with_off.offset; - if (type_basereg_with_off.r == crab::region::T_STACK) { + int store_at = offset+type_basereg_with_off.get_offset(); + if (type_basereg_with_off.get_region() == crab::region::T_STACK) { // type of basereg is STACK_P if (!it2) { // std::cout << "type_error: storing either a number or an unknown pointer - r" << (int)target_reg.v << "\n"; @@ -788,7 +830,7 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t //} } } - else if (type_basereg_with_off.r == crab::region::T_CTX) { + else if (type_basereg_with_off.get_region() == crab::region::T_CTX) { // type of basereg is CTX_P if (it2) { std::string s = std::to_string(static_cast(target_reg.v)); @@ -806,7 +848,7 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t // if basereg is a stack_p with no offset, we do not store anything, and no type errors // if we later load with that pointer, we read nothing -- load is no-op - if (it2 && type_basereg_no_off.r != crab::region::T_STACK) { + if (it2 && type_basereg_no_off.get_region() != crab::region::T_STACK) { std::string s = std::to_string(static_cast(target_reg.v)); std::string desc = std::string("\twe cannot store a pointer, r") + s + ", into packet or shared\n"; report_type_error(desc, loc); @@ -840,14 +882,14 @@ void type_domain_t::print_initial_types() { std::cout << "Initial register types:\n"; auto r1_with_loc = reg_with_loc_t(R1_ARG, loc); - auto it = m_types.find(r1_with_loc); + auto it = m_registers.find(r1_with_loc); if (it) { std::cout << " "; print_type(R1_ARG, it.value()); std::cout << "\n"; } auto r10_with_loc = reg_with_loc_t(R10_STACK_POINTER, loc); - auto it2 = m_types.find(r10_with_loc); + auto it2 = m_registers.find(r10_with_loc); if (it2) { std::cout << " "; print_type(R10_STACK_POINTER, it2.value()); diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index ee7f3a117..67ce96114 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -19,49 +19,62 @@ enum class region { T_SHARED }; -struct ptr_no_off_t { - region r; +class ptr_no_off_t { + region m_r; + + public: ptr_no_off_t() = default; ptr_no_off_t(const ptr_no_off_t &) = default; ptr_no_off_t(ptr_no_off_t &&) = default; ptr_no_off_t &operator=(const ptr_no_off_t &) = default; ptr_no_off_t &operator=(ptr_no_off_t &&) = default; - ptr_no_off_t(region _r) : r(_r) {} + ptr_no_off_t(region _r) : m_r(_r) {} + constexpr region get_region() const; + void set_region(region); + void write(std::ostream&) const; friend std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p); - friend bool operator==(const ptr_no_off_t& p1, const ptr_no_off_t& p2); - friend bool operator!=(const ptr_no_off_t& p1, const ptr_no_off_t& p2); + bool operator==(const ptr_no_off_t& p2); + bool operator!=(const ptr_no_off_t& p2); }; -struct ptr_with_off_t { - region r; - int offset; +class ptr_with_off_t { + region m_r; + int m_offset; + public: ptr_with_off_t() = default; ptr_with_off_t(const ptr_with_off_t &) = default; ptr_with_off_t(ptr_with_off_t &&) = default; ptr_with_off_t &operator=(const ptr_with_off_t &) = default; ptr_with_off_t &operator=(ptr_with_off_t &&) = default; - ptr_with_off_t(region _r, int _off) : r(_r), offset(_off) {} + ptr_with_off_t(region _r, int _off) : m_r(_r), m_offset(_off) {} + constexpr int get_offset() const; + void set_offset(int); + constexpr region get_region() const; + void set_region(region); + void write(std::ostream&) const; friend std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p); - friend bool operator==(const ptr_with_off_t& p1, const ptr_with_off_t& p2); - friend bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2); + bool operator==(const ptr_with_off_t& p2); + bool operator!=(const ptr_with_off_t& p2); }; using ptr_t = std::variant; using register_t = uint8_t; using location_t = boost::optional>; -struct reg_with_loc_t { - register_t r; - location_t loc; +class reg_with_loc_t { + register_t m_reg; + location_t m_loc; - reg_with_loc_t(register_t _r, location_t _loc) : r(_r), loc(_loc) {} + public: + reg_with_loc_t(register_t _r, location_t _loc) : m_reg(_r), m_loc(_loc) {} bool operator==(const reg_with_loc_t& other) const; std::size_t hash() const; friend std::ostream& operator<<(std::ostream& o, const reg_with_loc_t& reg); + void write(std::ostream& ) const; }; class ctx_t { @@ -104,14 +117,14 @@ using live_registers_t = std::array, 11>; using global_type_env_t = std::unordered_map; class register_types_t { - live_registers_t m_vars; - std::shared_ptr m_all_types; + live_registers_t m_cur_def; + std::shared_ptr m_reg_type_env; bool m_is_bottom = false; public: - register_types_t(bool is_bottom = false) : m_all_types(nullptr), m_is_bottom(is_bottom) {} - explicit register_types_t(live_registers_t&& vars, std::shared_ptr all_types, bool is_bottom = false) - : m_vars(std::move(vars)), m_all_types(all_types), m_is_bottom(is_bottom) {} + register_types_t(bool is_bottom = false) : m_reg_type_env(nullptr), m_is_bottom(is_bottom) {} + explicit register_types_t(live_registers_t&& vars, std::shared_ptr reg_type_env, bool is_bottom = false) + : m_cur_def(std::move(vars)), m_reg_type_env(reg_type_env), m_is_bottom(is_bottom) {} register_types_t operator|(const register_types_t& other) const; void operator-=(register_t var); @@ -122,7 +135,7 @@ class register_types_t { void insert(register_t reg, const reg_with_loc_t& reg_with_loc, const ptr_t& type); std::optional find(reg_with_loc_t reg) const; std::optional find(register_t key) const; - const live_registers_t &get_vars() { return m_vars; } + const live_registers_t &get_vars() { return m_cur_def; } friend std::ostream& operator<<(std::ostream& o, const register_types_t& p); }; @@ -133,7 +146,7 @@ class type_domain_t final { bool m_is_bottom = false; location_t error_location = boost::none; crab::stack_t m_stack; - crab::register_types_t m_types; + crab::register_types_t m_registers; std::shared_ptr m_ctx; public: @@ -144,7 +157,7 @@ class type_domain_t final { type_domain_t& operator=(type_domain_t&& o) = default; type_domain_t& operator=(const type_domain_t& o) = default; type_domain_t(crab::register_types_t&& _types, crab::stack_t&& _st, std::shared_ptr _ctx) - : m_stack(std::move(_st)), m_types(std::move(_types)), m_ctx(_ctx) {} + : m_stack(std::move(_st)), m_registers(std::move(_types)), m_ctx(_ctx) {} // eBPF initialization: R1 points to ctx, R10 to stack, etc. static type_domain_t setup_entry(); // bottom/top From 7d45c2dc3bf646d450345a61c71513e7f43d2db4 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 14 Jun 2022 15:50:35 -0400 Subject: [PATCH 096/373] Fix for the automatic build Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 8 ++++---- src/crab/type_domain.hpp | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 2ea3b7f0e..bab04f9a9 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -134,8 +134,8 @@ bool operator==(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { return (p1.get_region() == p2.get_region() && p1.get_offset() == p2.get_offset()); } -bool ptr_with_off_t::operator!=(const ptr_with_off_t& p2) { - return !(*this == p2); +bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { + return !(p1 == p2); } void ptr_with_off_t::write(std::ostream& o) const { @@ -159,8 +159,8 @@ bool operator==(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { return (p1.get_region() == p2.get_region()); } -bool ptr_no_off_t::operator!=(const ptr_no_off_t& p2) { - return !(*this == p2); +bool operator!=(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { + return !(p1 == p2); } void ptr_no_off_t::write(std::ostream& o) const { diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 67ce96114..0624e4f32 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -35,8 +35,8 @@ class ptr_no_off_t { void set_region(region); void write(std::ostream&) const; friend std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p); - bool operator==(const ptr_no_off_t& p2); - bool operator!=(const ptr_no_off_t& p2); + //bool operator==(const ptr_no_off_t& p2); + //bool operator!=(const ptr_no_off_t& p2); }; class ptr_with_off_t { @@ -57,8 +57,8 @@ class ptr_with_off_t { void set_region(region); void write(std::ostream&) const; friend std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p); - bool operator==(const ptr_with_off_t& p2); - bool operator!=(const ptr_with_off_t& p2); + //bool operator==(const ptr_with_off_t& p2); + //bool operator!=(const ptr_with_off_t& p2); }; using ptr_t = std::variant; From 07c5891fbde9209afe70763cc56721012ca481d2 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Fri, 17 Jun 2022 10:32:04 -0400 Subject: [PATCH 097/373] A small change to implement forgetting of offsets in ctx and stack separately; does not affect functionality, as the case for ctx is empty yet; Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index bab04f9a9..e20dc0fb7 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -629,14 +629,23 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { } else { if (std::holds_alternative(dst_reg)) { + // register to be added is not a pointer, but a number. its value is unknown /* std::string s = std::to_string(static_cast(bin.dst.v)); std::string desc = std::string("\toffset of the pointer r") + s + " unknown\n"; report_type_error(desc, loc); return; */ - m_stack.set_to_top(); - m_stack -= std::get(dst_reg).get_offset(); + ptr_with_off_t dst_reg_with_off = std::get(dst_reg); + if (dst_reg_with_off.get_region() == crab::region::T_STACK) { + m_stack.set_to_top(); + m_stack -= dst_reg_with_off.get_offset(); + return; + } + else { + // currently, we do not read any other pointers from CTX except the ones already stored + // in case we add the functionality, we will have to implement forgetting of CTX offsets + } } else { auto reg = reg_with_loc_t(bin.dst.v, loc); From 42ebdeadd75b0c048b121e41fab1f575e79f6435 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Sun, 9 Jul 2023 16:34:57 -0400 Subject: [PATCH 098/373] Initial setting to incorporate region domain into type domain Signed-off-by: Ameer Hamza --- src/config.hpp | 2 +- src/crab/abstract_domain.cpp | 2 + src/crab/region_domain.cpp | 950 +++++++++++++++++++++++++++++++++++ src/crab/region_domain.hpp | 215 ++++++++ src/crab/type_domain.cpp | 829 +----------------------------- src/crab/type_domain.hpp | 137 ----- src/crab_verifier.cpp | 9 +- src/main/check.cpp | 9 +- 8 files changed, 1187 insertions(+), 966 deletions(-) create mode 100644 src/crab/region_domain.cpp create mode 100644 src/crab/region_domain.hpp diff --git a/src/config.hpp b/src/config.hpp index c425d8019..e0da39ee2 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MIT #pragma once -enum class abstract_domain_kind { EBPF_DOMAIN, TYPE_DOMAIN }; +enum class abstract_domain_kind { EBPF_DOMAIN, TYPE_DOMAIN, REGION_DOMAIN }; struct ebpf_verifier_options_t { bool check_termination; diff --git a/src/crab/abstract_domain.cpp b/src/crab/abstract_domain.cpp index 8a5fb1c7f..cc20d4189 100644 --- a/src/crab/abstract_domain.cpp +++ b/src/crab/abstract_domain.cpp @@ -1,6 +1,7 @@ #include "abstract_domain.hpp" #include "ebpf_domain.hpp" #include "type_domain.hpp" +#include "region_domain.hpp" template abstract_domain_t::abstract_domain_model::abstract_domain_model(Domain abs_val) @@ -285,3 +286,4 @@ std::ostream& operator<<(std::ostream& o, const abstract_domain_t& dom) { // REQUIRED: instantiation for supported domains template abstract_domain_t::abstract_domain_t(crab::ebpf_domain_t); template abstract_domain_t::abstract_domain_t(type_domain_t); +template abstract_domain_t::abstract_domain_t(region_domain_t); diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp new file mode 100644 index 000000000..cfaa59a57 --- /dev/null +++ b/src/crab/region_domain.cpp @@ -0,0 +1,950 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT + +#include + +#include "crab/region_domain.hpp" + +using crab::___print___; +using crab::ptr_t; +using crab::ptr_with_off_t; +using crab::ptr_no_off_t; +using crab::ctx_t; +using crab::global_type_env_t; +using crab::reg_with_loc_t; +using crab::live_registers_t; +using crab::register_types_t; + +static std::string size(int w) { return std::string("u") + std::to_string(w * 8); } + + +namespace std { + template <> + struct hash { + size_t operator()(const crab::reg_with_loc_t& reg) const { return reg.hash(); } + }; + + // does not seem to work for me + /* + template <> + struct equal_to { + constexpr bool operator()(const crab::ptr_t& p1, const crab::ptr_t& p2) const { + if (p1.index() != p2.index()) return false; + if (std::holds_alternative(p1)) { + auto ptr_no_off1 = std::get(p1); + auto ptr_no_off2 = std::get(p2); + return (ptr_no_off1.get_region() == ptr_no_off2.get_region()); + } + else { + auto ptr_with_off1 = std::get(p1); + auto ptr_with_off2 = std::get(p2); + return (ptr_with_off1.get_region() == ptr_with_off2.get_region() && ptr_with_off1.get_offset() == ptr_with_off2.get_offset()); + } + } + }; + + template <> + struct equal_to { + constexpr bool operator()(const crab::ptr_with_off_t& p1, const crab::ptr_with_off_t& p2) const { + return (p1.get_region() == p2.get_region() && p1.get_offset() == p2.get_offset()); + } + }; + + template <> + struct equal_to { + constexpr bool operator()(const crab::ptr_no_off_t& p1, const crab::ptr_no_off_t& p2) const { + return (p1.get_region() == p2.get_region()); + } + }; + */ +} + + +static void print_ptr_type(const ptr_t& p) { + if (std::holds_alternative(p)) { + auto t = std::get(p); + std::cout << t; + } + else { + auto t = std::get(p); + std::cout << t; + } +} + +static void print_type(register_t r, const ptr_t& p) { + std::cout << "r" << static_cast(r) << " : "; + print_ptr_type(p); +} + +static void print_annotated(Mem const& b, const ptr_t& p, std::ostream& os_) { + if (b.is_load) { + os_ << " "; + print_type(std::get(b.value).v, p); + os_ << " = "; + } + std::string sign = b.access.offset < 0 ? " - " : " + "; + int offset = std::abs(b.access.offset); + os_ << "*(" << size(b.access.width) << " *)"; + os_ << "(" << b.access.basereg << sign << offset << ")\n"; +} + +static void print_annotated(Call const& call, const ptr_t& p, std::ostream& os_) { + os_ << " "; + print_type(0, p); + os_ << " = " << call.name << ":" << call.func << "(...)\n"; +} + +static void print_annotated(Bin const& b, const ptr_t& p, std::ostream& os_) { + os_ << " "; + print_type(b.dst.v, p); + // add better checks as we add more support + if (std::holds_alternative(b.v)) { + if (b.op == Bin::Op::MOV) + os_ << " = r" << static_cast(std::get(b.v).v) << ";"; + else if (b.op == Bin::Op::ADD) + os_ << " += r" << static_cast(std::get(b.v).v) << ";"; + } + else { + if (b.op == Bin::Op::ADD) + os_ << " += " << static_cast(std::get(b.v).v) << ";"; + } +} + +namespace crab { + +inline std::string get_reg_ptr(const region& r) { + switch (r) { + case region::T_CTX: + return "ctx_p"; + case region::T_STACK: + return "stack_p"; + case region::T_PACKET: + return "packet_p"; + default: + return "shared_p"; + } +} + +inline std::ostream& operator<<(std::ostream& o, const region& t) { + o << static_cast::type>(t); + return o; +} + +bool operator==(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { + return (p1.get_region() == p2.get_region() && p1.get_offset() == p2.get_offset()); +} + +bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { + return !(p1 == p2); +} + +void ptr_with_off_t::write(std::ostream& o) const { + o << get_reg_ptr(m_r) << "<" << m_offset << ">"; +} + +std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p) { + p.write(o); + return o; +} + +void ptr_with_off_t::set_offset(int off) { m_offset = off; } + +constexpr int ptr_with_off_t::get_offset() const { return m_offset; } + +void ptr_with_off_t::set_region(region r) { m_r = r; } + +constexpr region ptr_with_off_t::get_region() const { return m_r; } + +bool operator==(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { + return (p1.get_region() == p2.get_region()); +} + +bool operator!=(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { + return !(p1 == p2); +} + +void ptr_no_off_t::write(std::ostream& o) const { + o << get_reg_ptr(get_region()); +} + +std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p) { + p.write(o); + return o; +} + +void ptr_no_off_t::set_region(region r) { m_r = r; } + +constexpr region ptr_no_off_t::get_region() const { return m_r; } + +void reg_with_loc_t::write(std::ostream& o) const { + o << "r" << static_cast(m_reg) << "@" << m_loc->second << " in " << m_loc->first << " "; +} + +std::ostream& operator<<(std::ostream& o, const reg_with_loc_t& reg) { + reg.write(o); + return o; +} + +bool reg_with_loc_t::operator==(const reg_with_loc_t& other) const { + return (m_reg == other.m_reg && m_loc == other.m_loc); +} + +std::size_t reg_with_loc_t::hash() const { + // Similar to boost::hash_combine + using std::hash; + + std::size_t seed = hash()(m_reg); + seed ^= hash()(m_loc->first.from) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= hash()(m_loc->first.to) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= hash()(m_loc->second) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + + return seed; +} + +std::ostream& operator<<(std::ostream& o, const stack_t& st) { + o << "Stack: "; + if (st.is_bottom()) + o << "_|_\n"; + else { + o << "{"; + for (auto s : st.m_ptrs) { + o << s.first << ": "; + print_ptr_type(s.second); + o << ", "; + } + o << "}"; + } + return o; +} + +std::ostream& operator<<(std::ostream& o, const ctx_t& _ctx) { + + o << "type of context: " << (_ctx.m_packet_ptrs.empty() ? "top" : "") << "\n"; + for (const auto& it : _ctx.m_packet_ptrs) { + o << " stores at " << it.first << ": " << it.second << "\n"; + } + return o; +} + +ctx_t::ctx_t(const ebpf_context_descriptor_t* desc) +{ + if (desc->data != -1) + m_packet_ptrs[desc->data] = crab::ptr_no_off_t(crab::region::T_PACKET); + if (desc->end != -1) + m_packet_ptrs[desc->end] = crab::ptr_no_off_t(crab::region::T_PACKET); + if (desc->meta != -1) + m_packet_ptrs[desc->meta] = crab::ptr_no_off_t(crab::region::T_PACKET); +} + +std::optional ctx_t::find(int key) const { + auto it = m_packet_ptrs.find(key); + if (it == m_packet_ptrs.end()) return {}; + return it->second; +} + + +std::ostream& operator<<(std::ostream& o, const register_types_t& typ) { + if (typ.is_bottom()) + o << "_|_\n"; + else { + for (const auto& v : *(typ.m_reg_type_env)) { + o << v.first << ": "; + print_ptr_type(v.second); + o << "\n"; + } + } + return o; +} + +register_types_t register_types_t::operator|(const register_types_t& other) const { + if (is_bottom() || other.is_top()) { + return other; + } else if (other.is_bottom() || is_top()) { + return *this; + } + live_registers_t out_vars; + for (size_t i = 0; i < m_cur_def.size(); i++) { + if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; + auto it1 = find(*(m_cur_def[i])); + auto it2 = other.find(*(other.m_cur_def[i])); + if (it1 && it2 && it1.value() == it2.value()) { + out_vars[i] = m_cur_def[i]; + } + } + + return register_types_t(std::move(out_vars), m_reg_type_env, false); +} + +void register_types_t::operator-=(register_t var) { + if (is_bottom()) { + return; + } + m_cur_def[var] = nullptr; +} + +void register_types_t::set_to_bottom() { + m_cur_def = live_registers_t{nullptr}; + m_is_bottom = true; +} + +void register_types_t::set_to_top() { + m_cur_def = live_registers_t{nullptr}; + m_is_bottom = false; +} + +bool register_types_t::is_bottom() const { return m_is_bottom; } + +bool register_types_t::is_top() const { + if (m_is_bottom) { return false; } + if (m_reg_type_env == nullptr) return true; + for (auto it : m_cur_def) { + if (it != nullptr) return false; + } + return true; +} + +void register_types_t::insert(register_t reg, const reg_with_loc_t& reg_with_loc, const ptr_t& type) { + (*m_reg_type_env)[reg_with_loc] = type; + m_cur_def[reg] = std::make_shared(reg_with_loc); +} + +std::optional register_types_t::find(reg_with_loc_t reg) const { + auto it = m_reg_type_env->find(reg); + if (it == m_reg_type_env->end()) return {}; + return it->second; +} + +std::optional register_types_t::find(register_t key) const { + if (m_cur_def[key] == nullptr) return {}; + const reg_with_loc_t& reg = *(m_cur_def[key]); + return find(reg); +} + +stack_t stack_t::operator|(const stack_t& other) const { + if (is_bottom() || other.is_top()) { + return other; + } else if (other.is_bottom() || is_top()) { + return *this; + } + offset_to_ptr_t out_ptrs; + for (auto const&kv: m_ptrs) { + auto it = other.find(kv.first); + if (it && kv.second == it.value()) + out_ptrs.insert(kv); + } + return stack_t(std::move(out_ptrs), false); +} + +void stack_t::operator-=(int key) { + auto it = find(key); + if (it) + m_ptrs.erase(key); +} + +void stack_t::set_to_bottom() { + m_ptrs.clear(); + m_is_bottom = true; +} + +void stack_t::set_to_top() { + m_ptrs.clear(); + m_is_bottom = false; +} + +stack_t stack_t::bottom() { return stack_t(true); } + +stack_t stack_t::top() { return stack_t(false); } + +bool stack_t::is_bottom() const { return m_is_bottom; } + +bool stack_t::is_top() const { + if (m_is_bottom) + return false; + return m_ptrs.empty(); +} + +void stack_t::insert(int key, ptr_t value) { + m_ptrs[key] = value; +} + +std::optional stack_t::find(int key) const { + auto it = m_ptrs.find(key); + if (it == m_ptrs.end()) return {}; + return it->second; +} + +} + +bool region_domain_t::is_bottom() const { + if (m_is_bottom) return true; + return (m_stack.is_bottom() || m_registers.is_bottom()); +} + +bool region_domain_t::is_top() const { + if (m_is_bottom) return false; + return (m_stack.is_top() && m_registers.is_top()); +} + +region_domain_t region_domain_t::bottom() { + region_domain_t typ; + typ.set_to_bottom(); + return typ; +} + +void region_domain_t::set_to_bottom() { + m_is_bottom = true; +} + +void region_domain_t::set_to_top() { + m_stack.set_to_top(); + m_registers.set_to_top(); +} + +bool region_domain_t::operator<=(const region_domain_t& abs) const { + /* WARNING: The operation is not implemented yet.*/ + return true; +} + +void region_domain_t::operator|=(const region_domain_t& abs) { + region_domain_t tmp{abs}; + operator|=(std::move(tmp)); +} + +void region_domain_t::operator|=(region_domain_t&& abs) { + if (is_bottom()) { + *this = abs; + return; + } + *this = *this | std::move(abs); +} + +region_domain_t region_domain_t::operator|(const region_domain_t& other) const { + if (is_bottom() || other.is_top()) { + return other; + } + else if (other.is_bottom() || is_top()) { + return *this; + } + return region_domain_t(m_registers | other.m_registers, m_stack | other.m_stack, other.m_ctx); +} + +region_domain_t region_domain_t::operator|(region_domain_t&& other) const { + if (is_bottom() || other.is_top()) { + return std::move(other); + } + else if (other.is_bottom() || is_top()) { + return *this; + } + return region_domain_t(m_registers | std::move(other.m_registers), m_stack | std::move(other.m_stack), other.m_ctx); +} + +region_domain_t region_domain_t::operator&(const region_domain_t& abs) const { + return abs; +} + +region_domain_t region_domain_t::widen(const region_domain_t& abs, bool to_constants) { + return abs; +} + +region_domain_t region_domain_t::narrow(const region_domain_t& other) const { + return other; +} + +void region_domain_t::write(std::ostream& os) const { + os << m_registers; + os << m_stack << "\n"; +} + +std::string region_domain_t::domain_name() const { + return "type_domain"; +} + +crab::bound_t region_domain_t::get_loop_count_upper_bound() { + // WARNING: Not implemented yet. + return crab::bound_t{crab::number_t{0}}; +} + +void region_domain_t::initialize_loop_counter(const label_t& label) { + // WARNING: Not implemented yet. +} + +string_invariant region_domain_t::to_set() { + return string_invariant{}; +} + +void region_domain_t::operator()(const Undefined & u, location_t loc, int print) { + if (is_bottom()) return; + if (print > 0) + std::cout << " " << u << ";\n"; +} +void region_domain_t::operator()(const Un &u, location_t loc, int print) { + if (is_bottom()) return; + if (print > 0) + std::cout << " " << u << ";\n"; +} +void region_domain_t::operator()(const LoadMapFd &u, location_t loc, int print) { + if (is_bottom()) return; + if (print > 0) { + std::cout << " " << u << ";\n"; + return; + } + m_registers -= u.dst.v; +} +void region_domain_t::operator()(const Call &u, location_t loc, int print) { + if (is_bottom()) return; + register_t r0_reg{R0_RETURN_VALUE}; + auto r0 = reg_with_loc_t(r0_reg, loc); + if (print > 0) { + if (u.is_map_lookup) { + auto it = m_registers.find(r0); + if (it) { + print_annotated(u, it.value(), std::cout); + } + } + else + std::cout << " " << u << ";\n"; + return; + } + if (u.is_map_lookup) { + auto type = ptr_no_off_t(crab::region::T_SHARED); + m_registers.insert(r0_reg, r0, type); + } + else { + m_registers -= r0_reg; + } +} +void region_domain_t::operator()(const Callx &u, location_t loc, int print) { + // WARNING: Not implemented yet. +} +void region_domain_t::operator()(const IncrementLoopCounter &u, location_t loc, int print) { + // WARNING: Not implemented yet. +} +void region_domain_t::operator()(const Atomic &u, location_t loc, int print) { + // WARNING: Not implemented yet. +} +void region_domain_t::operator()(const Exit &u, location_t loc, int print) { + if (is_bottom()) return; + if (print > 0) + std::cout << " " << u << ";\n"; +} +void region_domain_t::operator()(const Jmp &u, location_t loc, int print) { + if (is_bottom()) return; + if (print > 0) + std::cout << " " << u << ";\n"; +} +void region_domain_t::operator()(const Packet & u, location_t loc, int print) { + if (is_bottom()) return; + if (print > 0) { + std::cout << " " << u << ";\n"; + return; + } + m_registers -= register_t{0}; +} +void region_domain_t::operator()(const Assume &u, location_t loc, int print) { + if (is_bottom()) return; + if (print > 0) + std::cout << " " << u << ";\n"; +} +void region_domain_t::operator()(const Assert &u, location_t loc, int print) { + if (is_bottom()) return; + if (print > 0) + std::cout << " " << u << ";\n"; +} + +region_domain_t region_domain_t::setup_entry() { + + std::shared_ptr ctx = std::make_shared(global_program_info.get().type.context_descriptor); + std::shared_ptr all_types = std::make_shared(); + + live_registers_t vars; + register_types_t typ(std::move(vars), all_types); + + auto r1 = reg_with_loc_t(R1_ARG, std::make_pair(label_t::entry, static_cast(0))); + auto r10 = reg_with_loc_t(R10_STACK_POINTER, std::make_pair(label_t::entry, static_cast(0))); + + typ.insert(R1_ARG, r1, ptr_with_off_t(crab::region::T_CTX, 0)); + typ.insert(R10_STACK_POINTER, r10, ptr_with_off_t(crab::region::T_STACK, 512)); + + region_domain_t inv(std::move(typ), crab::stack_t::top(), ctx); + return inv; +} + +void region_domain_t::report_type_error(std::string s, location_t loc) { + std::cout << "type_error at line " << loc->second << " in bb " << loc->first << "\n"; + std::cout << s; + error_location = loc; + set_to_bottom(); +} + +void region_domain_t::operator()(const Bin& bin, location_t loc, int print) { + if (is_bottom()) return; + if (print > 0) { + if (print == 2) { + if ((std::holds_alternative(bin.v) && (bin.op == Bin::Op::MOV || bin.op == Bin::Op::ADD)) || + (std::holds_alternative(bin.v) && bin.op == Bin::Op::ADD)) { + auto reg_with_loc = reg_with_loc_t(bin.dst.v, loc); + auto it = m_registers.find(reg_with_loc); + if (it) { + print_annotated(bin, it.value(), std::cout); + std::cout << "\n"; + return; + } + } + } + std::cout << " " << bin << ";\n"; + return; + } + + ptr_t dst_reg; + if (bin.op == Bin::Op::ADD) { + auto it = m_registers.find(bin.dst.v); + if (!it) { + m_registers -= bin.dst.v; + return; + } + dst_reg = it.value(); + } + + if (std::holds_alternative(bin.v)) { + Reg src = std::get(bin.v); + switch (bin.op) + { + case Bin::Op::MOV: { + auto it1 = m_registers.find(src.v); + if (!it1) { + //std::cout << "type_error: assigning an unknown pointer or a number - r" << (int)src.v << "\n"; + m_registers -= bin.dst.v; + break; + } + auto reg = reg_with_loc_t(bin.dst.v, loc); + m_registers.insert(bin.dst.v, reg, it1.value()); + break; + } + case Bin::Op::ADD: { + auto it1 = m_registers.find(src.v); + if (it1) { + std::string s = std::to_string(static_cast(src.v)); + std::string s1 = std::to_string(static_cast(bin.dst.v)); + std::string desc = std::string("\taddition of two pointers, r") + s + " and r" + s1 + " not allowed\n"; + report_type_error(desc, loc); + return; + } + else { + if (std::holds_alternative(dst_reg)) { + // register to be added is not a pointer, but a number. its value is unknown + /* + std::string s = std::to_string(static_cast(bin.dst.v)); + std::string desc = std::string("\toffset of the pointer r") + s + " unknown\n"; + report_type_error(desc, loc); + return; + */ + ptr_with_off_t dst_reg_with_off = std::get(dst_reg); + if (dst_reg_with_off.get_region() == crab::region::T_STACK) { + m_stack.set_to_top(); + m_stack -= dst_reg_with_off.get_offset(); + return; + } + else { + // currently, we do not read any other pointers from CTX except the ones already stored + // in case we add the functionality, we will have to implement forgetting of CTX offsets + } + } + else { + auto reg = reg_with_loc_t(bin.dst.v, loc); + m_registers.insert(bin.dst.v, reg, dst_reg); + } + } + break; + } + default: + m_registers -= bin.dst.v; + break; + } + } + else { + int imm = static_cast(std::get(bin.v).v); + switch (bin.op) + { + case Bin::Op::ADD: { + if (std::holds_alternative(dst_reg)) { + auto ptr_with_off = std::get(dst_reg); + ptr_with_off.set_offset(ptr_with_off.get_offset() + imm); + auto reg = reg_with_loc_t(bin.dst.v, loc); + m_registers.insert(bin.dst.v, reg, ptr_with_off); + } + else { + auto reg = reg_with_loc_t(bin.dst.v, loc); + m_registers.insert(bin.dst.v, reg, dst_reg); + } + break; + } + default: { + m_registers -= bin.dst.v; + break; + } + } + } +} + +void region_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc, int print) { + + if (print > 0) { + auto target_reg_loc = reg_with_loc_t(target_reg.v, loc); + auto it = m_registers.find(target_reg_loc); + if (it) + print_annotated(b, it.value(), std::cout); + else + std::cout << " " << b << ";\n"; + return; + } + int offset = b.access.offset; + Reg basereg = b.access.basereg; + + auto it = m_registers.find(basereg.v); + if (!it) { + std::string s = std::to_string(static_cast(basereg.v)); + std::string desc = std::string("\tloading from an unknown pointer, or from number - r") + s + "\n"; + report_type_error(desc, loc); + return; + } + ptr_t type_basereg = it.value(); + + if (std::holds_alternative(type_basereg)) { + //std::cout << "type_error: loading from either packet or shared region not allowed - r" << (int)basereg.v << "\n"; + m_registers -= target_reg.v; + return; + } + + ptr_with_off_t type_with_off = std::get(type_basereg); + int load_at = offset+type_with_off.get_offset(); + + switch (type_with_off.get_region()) { + case crab::region::T_STACK: { + + auto it = m_stack.find(load_at); + + if (!it) { + //std::cout << "type_error: no field at loaded offset " << load_at << " in stack\n"; + m_registers -= target_reg.v; + return; + } + ptr_t type_loaded = it.value(); + + if (std::holds_alternative(type_loaded)) { + ptr_with_off_t type_loaded_with_off = std::get(type_loaded); + auto reg = reg_with_loc_t(target_reg.v, loc); + m_registers.insert(target_reg.v, reg, type_loaded_with_off); + } + else { + ptr_no_off_t type_loaded_no_off = std::get(type_loaded); + auto reg = reg_with_loc_t(target_reg.v, loc); + m_registers.insert(target_reg.v, reg, type_loaded_no_off); + } + + break; + } + case crab::region::T_CTX: { + + auto it = m_ctx->find(load_at); + + if (!it) { + //std::cout << "type_error: no field at loaded offset " << load_at << " in context\n"; + m_registers -= target_reg.v; + return; + } + ptr_no_off_t type_loaded = it.value(); + + auto reg = reg_with_loc_t(target_reg.v, loc); + m_registers.insert(target_reg.v, reg, type_loaded); + break; + } + + default: { + assert(false); + } + } +} + +void region_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t loc, int print) { + + if (print > 0) { + std::cout << " " << b << ";\n"; + return; + } + int offset = b.access.offset; + Reg basereg = b.access.basereg; + int width = b.access.width; + + auto it = m_registers.find(basereg.v); + if (!it) { + std::string s = std::to_string(static_cast(basereg.v)); + std::string desc = std::string("\tstoring at an unknown pointer, or from number - r") + s + "\n"; + report_type_error(desc, loc); + return; + } + ptr_t type_basereg = it.value(); + + auto it2 = m_registers.find(target_reg.v); + + if (std::holds_alternative(type_basereg)) { + // base register is either CTX_P or STACK_P + ptr_with_off_t type_basereg_with_off = std::get(type_basereg); + + int store_at = offset+type_basereg_with_off.get_offset(); + if (type_basereg_with_off.get_region() == crab::region::T_STACK) { + // type of basereg is STACK_P + if (!it2) { + // std::cout << "type_error: storing either a number or an unknown pointer - r" << (int)target_reg.v << "\n"; + m_stack -= store_at; + return; + } + else { + auto type_to_store = it2.value(); + /* + if (std::holds_alternative(type_to_store) && + std::get(type_to_store).r == crab::region::T_STACK) { + std::string s = std::to_string(static_cast(target_reg.v)); + std::string desc = std::string("\twe cannot store stack pointer, r") + s + ", into stack\n"; + //report_type_error(desc, loc); + return; + } + else { + */ + for (auto i = store_at+1; i < store_at+width; i++) { + auto it3 = m_stack.find(i); + if (it3) { + std::string s = std::to_string(store_at); + std::string s1 = std::to_string(i); + std::string desc = std::string("\ttype being stored into stack at ") + s + " is overlapping with already stored at " + s1 + "\n"; + report_type_error(desc, loc); + return; + } + } + m_stack.insert(store_at, type_to_store); + // revise: code below checks if there is already something stored at same location, the type should be the same -- it is very restricted and not required. + // However, when we support storing info like width of type, we need more checks + /* + auto it4 = m_stack.find(store_at); + if (it4) { + auto type_in_stack = it4.value(); + if (type_to_store != type_in_stack) { + std::string s = std::to_string(store_at); + std::string desc = std::string("\ttype being stored at offset ") + s + " is not the same as already stored in stack\n"; + report_type_error(desc, loc); + return; + } + } + else { + m_stack.insert(store_at, type_to_store); + } + */ + //} + } + } + else if (type_basereg_with_off.get_region() == crab::region::T_CTX) { + // type of basereg is CTX_P + if (it2) { + std::string s = std::to_string(static_cast(target_reg.v)); + std::string desc = std::string("\twe cannot store a pointer, r") + s + ", into ctx\n"; + report_type_error(desc, loc); + return; + } + } + else + assert(false); + } + else { + // base register type is either PACKET_P, SHARED_P or STACK_P without known offset + ptr_no_off_t type_basereg_no_off = std::get(type_basereg); + + // if basereg is a stack_p with no offset, we do not store anything, and no type errors + // if we later load with that pointer, we read nothing -- load is no-op + if (it2 && type_basereg_no_off.get_region() != crab::region::T_STACK) { + std::string s = std::to_string(static_cast(target_reg.v)); + std::string desc = std::string("\twe cannot store a pointer, r") + s + ", into packet or shared\n"; + report_type_error(desc, loc); + return; + } + } +} + +void region_domain_t::operator()(const Mem& b, location_t loc, int print) { + if (is_bottom()) return; + + if (std::holds_alternative(b.value)) { + if (b.is_load) { + do_load(b, std::get(b.value), loc, print); + } else { + do_mem_store(b, std::get(b.value), loc, print); + } + } else { + std::string s = std::to_string(static_cast(std::get(b.value).v)); + std::string desc = std::string("\tEither loading to a number (not allowed) or storing a number (not allowed yet) - ") + s + "\n"; + report_type_error(desc, loc); + return; + } +} + +void region_domain_t::print_initial_types() { + auto label = label_t::entry; + location_t loc = location_t(std::make_pair(label, 0)); + std::cout << "\n" << *m_ctx << "\n"; + std::cout << m_stack << "\n"; + + std::cout << "Initial register types:\n"; + auto r1_with_loc = reg_with_loc_t(R1_ARG, loc); + auto it = m_registers.find(r1_with_loc); + if (it) { + std::cout << " "; + print_type(R1_ARG, it.value()); + std::cout << "\n"; + } + auto r10_with_loc = reg_with_loc_t(R10_STACK_POINTER, loc); + auto it2 = m_registers.find(r10_with_loc); + if (it2) { + std::cout << " "; + print_type(R10_STACK_POINTER, it2.value()); + std::cout << "\n"; + } + std::cout << "\n"; +} + +void region_domain_t::operator()(const basic_block_t& bb, bool check_termination, int print) { + auto label = bb.label(); + uint32_t curr_pos = 0; + location_t loc; + if (print > 0) { + if (label == label_t::entry) { + print_initial_types(); + m_is_bottom = false; + } + std::cout << label << ":\n"; + } + + for (const Instruction& statement : bb) { + loc = location_t(std::make_pair(label, ++curr_pos)); + if (print > 0) std::cout << " " << curr_pos << "."; + std::visit([this, loc, print](const auto& v) { std::apply(*this, std::make_tuple(v, loc, print)); }, statement); + if (print > 0 && error_location->first == loc->first && error_location->second == loc->second) std::cout << "type_error\n"; + } + + if (print > 0) { + auto [it, et] = bb.next_blocks(); + if (it != et) { + std::cout << " " + << "goto "; + for (; it != et;) { + std::cout << *it; + ++it; + if (it == et) { + std::cout << ";"; + } else { + std::cout << ","; + } + } + } + std::cout << "\n\n"; + } +} + +void region_domain_t::set_require_check(check_require_func_t f) {} diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp new file mode 100644 index 000000000..8c72cb874 --- /dev/null +++ b/src/crab/region_domain.hpp @@ -0,0 +1,215 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +#include "crab/abstract_domain.hpp" +#include "crab/cfg.hpp" +#include "linear_constraint.hpp" +#include "string_constraints.hpp" + +namespace crab { + +enum class region { + T_CTX, + T_STACK, + T_PACKET, + T_SHARED +}; + + +class ptr_no_off_t { + region m_r; + + public: + ptr_no_off_t() = default; + ptr_no_off_t(const ptr_no_off_t &) = default; + ptr_no_off_t(ptr_no_off_t &&) = default; + ptr_no_off_t &operator=(const ptr_no_off_t &) = default; + ptr_no_off_t &operator=(ptr_no_off_t &&) = default; + ptr_no_off_t(region _r) : m_r(_r) {} + + constexpr region get_region() const; + void set_region(region); + void write(std::ostream&) const; + friend std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p); + //bool operator==(const ptr_no_off_t& p2); + //bool operator!=(const ptr_no_off_t& p2); +}; + +class ptr_with_off_t { + region m_r; + int m_offset; + + public: + ptr_with_off_t() = default; + ptr_with_off_t(const ptr_with_off_t &) = default; + ptr_with_off_t(ptr_with_off_t &&) = default; + ptr_with_off_t &operator=(const ptr_with_off_t &) = default; + ptr_with_off_t &operator=(ptr_with_off_t &&) = default; + ptr_with_off_t(region _r, int _off) : m_r(_r), m_offset(_off) {} + + constexpr int get_offset() const; + void set_offset(int); + constexpr region get_region() const; + void set_region(region); + void write(std::ostream&) const; + friend std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p); + //bool operator==(const ptr_with_off_t& p2); + //bool operator!=(const ptr_with_off_t& p2); +}; + +using ptr_t = std::variant; +using register_t = uint8_t; +using location_t = boost::optional>; + +class reg_with_loc_t { + register_t m_reg; + location_t m_loc; + + public: + reg_with_loc_t(register_t _r, location_t _loc) : m_reg(_r), m_loc(_loc) {} + bool operator==(const reg_with_loc_t& other) const; + std::size_t hash() const; + friend std::ostream& operator<<(std::ostream& o, const reg_with_loc_t& reg); + void write(std::ostream& ) const; +}; + +class ctx_t { + using offset_to_ptr_no_off_t = std::unordered_map; + + offset_to_ptr_no_off_t m_packet_ptrs; + + public: + ctx_t(const ebpf_context_descriptor_t* desc); + std::optional find(int key) const; + friend std::ostream& operator<<(std::ostream& o, const ctx_t& _ctx); +}; + +class stack_t { + using offset_to_ptr_t = std::unordered_map; + + offset_to_ptr_t m_ptrs; + bool m_is_bottom; + + public: + stack_t(bool is_bottom = false) : m_is_bottom(is_bottom) {} + stack_t(offset_to_ptr_t && ptrs, bool is_bottom) + : m_ptrs(std::move(ptrs)) , m_is_bottom(is_bottom) {} + + stack_t operator|(const stack_t& other) const; + void operator-=(int); + void set_to_bottom(); + void set_to_top(); + static stack_t bottom(); + static stack_t top(); + bool is_bottom() const; + bool is_top() const; + const offset_to_ptr_t &get_ptrs() { return m_ptrs; } + void insert(int key, ptr_t value); + std::optional find(int key) const; + friend std::ostream& operator<<(std::ostream& o, const stack_t& st); +}; + +using live_registers_t = std::array, 11>; +using global_type_env_t = std::unordered_map; + +class register_types_t { + live_registers_t m_cur_def; + std::shared_ptr m_reg_type_env; + bool m_is_bottom = false; + + public: + register_types_t(bool is_bottom = false) : m_reg_type_env(nullptr), m_is_bottom(is_bottom) {} + explicit register_types_t(live_registers_t&& vars, std::shared_ptr reg_type_env, bool is_bottom = false) + : m_cur_def(std::move(vars)), m_reg_type_env(reg_type_env), m_is_bottom(is_bottom) {} + + register_types_t operator|(const register_types_t& other) const; + void operator-=(register_t var); + void set_to_bottom(); + void set_to_top(); + bool is_bottom() const; + bool is_top() const; + void insert(register_t reg, const reg_with_loc_t& reg_with_loc, const ptr_t& type); + std::optional find(reg_with_loc_t reg) const; + std::optional find(register_t key) const; + const live_registers_t &get_vars() { return m_cur_def; } + friend std::ostream& operator<<(std::ostream& o, const register_types_t& p); +}; + +} + +class region_domain_t final { + + bool m_is_bottom = false; + location_t error_location = boost::none; + crab::stack_t m_stack; + crab::register_types_t m_registers; + std::shared_ptr m_ctx; + + public: + + region_domain_t() = default; + region_domain_t(region_domain_t&& o) = default; + region_domain_t(const region_domain_t& o) = default; + region_domain_t& operator=(region_domain_t&& o) = default; + region_domain_t& operator=(const region_domain_t& o) = default; + region_domain_t(crab::register_types_t&& _types, crab::stack_t&& _st, std::shared_ptr _ctx) + : m_stack(std::move(_st)), m_registers(std::move(_types)), m_ctx(_ctx) {} + // eBPF initialization: R1 points to ctx, R10 to stack, etc. + static region_domain_t setup_entry(); + // bottom/top + static region_domain_t bottom(); + void set_to_top(); + void set_to_bottom(); + bool is_bottom() const; + bool is_top() const; + // inclusion + bool operator<=(const region_domain_t& other) const; + // join + void operator|=(const region_domain_t& abs); + void operator|=(region_domain_t&& abs); + region_domain_t operator|(const region_domain_t& other) const; + region_domain_t operator|(region_domain_t&& abs) const; + // meet + region_domain_t operator&(const region_domain_t& other) const; + // widening + region_domain_t widen(const region_domain_t& other, bool); + // narrowing + region_domain_t narrow(const region_domain_t& other) const; + //forget + void operator-=(crab::variable_t var); + + //// abstract transformers + void operator()(const Undefined &, location_t loc = boost::none, int print = 0); + void operator()(const Bin &, location_t loc = boost::none, int print = 0); + void operator()(const Un &, location_t loc = boost::none, int print = 0); + void operator()(const LoadMapFd &, location_t loc = boost::none, int print = 0); + void operator()(const Atomic&, location_t loc = boost::none, int print = 0); + void operator()(const Call &, location_t loc = boost::none, int print = 0); + void operator()(const Callx&, location_t loc = boost::none, int print = 0); + void operator()(const Exit &, location_t loc = boost::none, int print = 0); + void operator()(const Jmp &, location_t loc = boost::none, int print = 0); + void operator()(const Mem &, location_t loc = boost::none, int print = 0); + void operator()(const Packet &, location_t loc = boost::none, int print = 0); + void operator()(const Assume &, location_t loc = boost::none, int print = 0); + void operator()(const Assert &, location_t loc = boost::none, int print = 0); + void operator()(const IncrementLoopCounter&, location_t loc = boost::none, int print = 0); + void operator()(const basic_block_t& bb, bool check_termination, int print = 0); + void write(std::ostream& os) const; + std::string domain_name() const; + crab::bound_t get_loop_count_upper_bound(); + void initialize_loop_counter(const label_t&); + string_invariant to_set(); + void set_require_check(check_require_func_t f); + + private: + + void do_load(const Mem&, const Reg&, location_t, int print = 0); + void do_mem_store(const Mem&, const Reg&, location_t, int print = 0); + void print_initial_types(); + void report_type_error(std::string, location_t); + +}; // end region_domain_t diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index e20dc0fb7..05528508f 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -5,384 +5,15 @@ #include "crab/type_domain.hpp" -using crab::___print___; -using crab::ptr_t; -using crab::ptr_with_off_t; -using crab::ptr_no_off_t; -using crab::ctx_t; -using crab::global_type_env_t; -using crab::reg_with_loc_t; -using crab::live_registers_t; -using crab::register_types_t; - static std::string size(int w) { return std::string("u") + std::to_string(w * 8); } -namespace std { - template <> - struct hash { - size_t operator()(const crab::reg_with_loc_t& reg) const { return reg.hash(); } - }; - - // does not seem to work for me - /* - template <> - struct equal_to { - constexpr bool operator()(const crab::ptr_t& p1, const crab::ptr_t& p2) const { - if (p1.index() != p2.index()) return false; - if (std::holds_alternative(p1)) { - auto ptr_no_off1 = std::get(p1); - auto ptr_no_off2 = std::get(p2); - return (ptr_no_off1.get_region() == ptr_no_off2.get_region()); - } - else { - auto ptr_with_off1 = std::get(p1); - auto ptr_with_off2 = std::get(p2); - return (ptr_with_off1.get_region() == ptr_with_off2.get_region() && ptr_with_off1.get_offset() == ptr_with_off2.get_offset()); - } - } - }; - - template <> - struct equal_to { - constexpr bool operator()(const crab::ptr_with_off_t& p1, const crab::ptr_with_off_t& p2) const { - return (p1.get_region() == p2.get_region() && p1.get_offset() == p2.get_offset()); - } - }; - - template <> - struct equal_to { - constexpr bool operator()(const crab::ptr_no_off_t& p1, const crab::ptr_no_off_t& p2) const { - return (p1.get_region() == p2.get_region()); - } - }; - */ -} - - -static void print_ptr_type(const ptr_t& p) { - if (std::holds_alternative(p)) { - auto t = std::get(p); - std::cout << t; - } - else { - auto t = std::get(p); - std::cout << t; - } -} - -static void print_type(register_t r, const ptr_t& p) { - std::cout << "r" << static_cast(r) << " : "; - print_ptr_type(p); -} - -static void print_annotated(Mem const& b, const ptr_t& p, std::ostream& os_) { - if (b.is_load) { - os_ << " "; - print_type(std::get(b.value).v, p); - os_ << " = "; - } - std::string sign = b.access.offset < 0 ? " - " : " + "; - int offset = std::abs(b.access.offset); - os_ << "*(" << size(b.access.width) << " *)"; - os_ << "(" << b.access.basereg << sign << offset << ")\n"; -} - -static void print_annotated(Call const& call, const ptr_t& p, std::ostream& os_) { - os_ << " "; - print_type(0, p); - os_ << " = " << call.name << ":" << call.func << "(...)\n"; -} - -static void print_annotated(Bin const& b, const ptr_t& p, std::ostream& os_) { - os_ << " "; - print_type(b.dst.v, p); - // add better checks as we add more support - if (std::holds_alternative(b.v)) { - if (b.op == Bin::Op::MOV) - os_ << " = r" << static_cast(std::get(b.v).v) << ";"; - else if (b.op == Bin::Op::ADD) - os_ << " += r" << static_cast(std::get(b.v).v) << ";"; - } - else { - if (b.op == Bin::Op::ADD) - os_ << " += " << static_cast(std::get(b.v).v) << ";"; - } -} - -namespace crab { - -inline std::string get_reg_ptr(const region& r) { - switch (r) { - case region::T_CTX: - return "ctx_p"; - case region::T_STACK: - return "stack_p"; - case region::T_PACKET: - return "packet_p"; - default: - return "shared_p"; - } -} - -inline std::ostream& operator<<(std::ostream& o, const region& t) { - o << static_cast::type>(t); - return o; -} - -bool operator==(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { - return (p1.get_region() == p2.get_region() && p1.get_offset() == p2.get_offset()); -} - -bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { - return !(p1 == p2); -} - -void ptr_with_off_t::write(std::ostream& o) const { - o << get_reg_ptr(m_r) << "<" << m_offset << ">"; -} - -std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p) { - p.write(o); - return o; -} - -void ptr_with_off_t::set_offset(int off) { m_offset = off; } - -constexpr int ptr_with_off_t::get_offset() const { return m_offset; } - -void ptr_with_off_t::set_region(region r) { m_r = r; } - -constexpr region ptr_with_off_t::get_region() const { return m_r; } - -bool operator==(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { - return (p1.get_region() == p2.get_region()); -} - -bool operator!=(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { - return !(p1 == p2); -} - -void ptr_no_off_t::write(std::ostream& o) const { - o << get_reg_ptr(get_region()); -} - -std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p) { - p.write(o); - return o; -} - -void ptr_no_off_t::set_region(region r) { m_r = r; } - -constexpr region ptr_no_off_t::get_region() const { return m_r; } - -void reg_with_loc_t::write(std::ostream& o) const { - o << "r" << static_cast(m_reg) << "@" << m_loc->second << " in " << m_loc->first << " "; -} - -std::ostream& operator<<(std::ostream& o, const reg_with_loc_t& reg) { - reg.write(o); - return o; -} - -bool reg_with_loc_t::operator==(const reg_with_loc_t& other) const { - return (m_reg == other.m_reg && m_loc == other.m_loc); -} - -std::size_t reg_with_loc_t::hash() const { - // Similar to boost::hash_combine - using std::hash; - - std::size_t seed = hash()(m_reg); - seed ^= hash()(m_loc->first.from) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - seed ^= hash()(m_loc->first.to) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - seed ^= hash()(m_loc->second) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - - return seed; -} - -std::ostream& operator<<(std::ostream& o, const stack_t& st) { - o << "Stack: "; - if (st.is_bottom()) - o << "_|_\n"; - else { - o << "{"; - for (auto s : st.m_ptrs) { - o << s.first << ": "; - print_ptr_type(s.second); - o << ", "; - } - o << "}"; - } - return o; -} - -std::ostream& operator<<(std::ostream& o, const ctx_t& _ctx) { - - o << "type of context: " << (_ctx.m_packet_ptrs.empty() ? "top" : "") << "\n"; - for (const auto& it : _ctx.m_packet_ptrs) { - o << " stores at " << it.first << ": " << it.second << "\n"; - } - return o; -} - -ctx_t::ctx_t(const ebpf_context_descriptor_t* desc) -{ - if (desc->data != -1) - m_packet_ptrs[desc->data] = crab::ptr_no_off_t(crab::region::T_PACKET); - if (desc->end != -1) - m_packet_ptrs[desc->end] = crab::ptr_no_off_t(crab::region::T_PACKET); - if (desc->meta != -1) - m_packet_ptrs[desc->meta] = crab::ptr_no_off_t(crab::region::T_PACKET); -} - -std::optional ctx_t::find(int key) const { - auto it = m_packet_ptrs.find(key); - if (it == m_packet_ptrs.end()) return {}; - return it->second; -} - - -std::ostream& operator<<(std::ostream& o, const register_types_t& typ) { - if (typ.is_bottom()) - o << "_|_\n"; - else { - for (const auto& v : *(typ.m_reg_type_env)) { - o << v.first << ": "; - print_ptr_type(v.second); - o << "\n"; - } - } - return o; -} - -register_types_t register_types_t::operator|(const register_types_t& other) const { - if (is_bottom() || other.is_top()) { - return other; - } else if (other.is_bottom() || is_top()) { - return *this; - } - live_registers_t out_vars; - for (size_t i = 0; i < m_cur_def.size(); i++) { - if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; - auto it1 = find(*(m_cur_def[i])); - auto it2 = other.find(*(other.m_cur_def[i])); - if (it1 && it2 && it1.value() == it2.value()) { - out_vars[i] = m_cur_def[i]; - } - } - - return register_types_t(std::move(out_vars), m_reg_type_env, false); -} - -void register_types_t::operator-=(register_t var) { - if (is_bottom()) { - return; - } - m_cur_def[var] = nullptr; -} - -void register_types_t::set_to_bottom() { - m_cur_def = live_registers_t{nullptr}; - m_is_bottom = true; -} - -void register_types_t::set_to_top() { - m_cur_def = live_registers_t{nullptr}; - m_is_bottom = false; -} - -bool register_types_t::is_bottom() const { return m_is_bottom; } - -bool register_types_t::is_top() const { - if (m_is_bottom) { return false; } - if (m_reg_type_env == nullptr) return true; - for (auto it : m_cur_def) { - if (it != nullptr) return false; - } - return true; -} - -void register_types_t::insert(register_t reg, const reg_with_loc_t& reg_with_loc, const ptr_t& type) { - (*m_reg_type_env)[reg_with_loc] = type; - m_cur_def[reg] = std::make_shared(reg_with_loc); -} - -std::optional register_types_t::find(reg_with_loc_t reg) const { - auto it = m_reg_type_env->find(reg); - if (it == m_reg_type_env->end()) return {}; - return it->second; -} - -std::optional register_types_t::find(register_t key) const { - if (m_cur_def[key] == nullptr) return {}; - const reg_with_loc_t& reg = *(m_cur_def[key]); - return find(reg); -} - -stack_t stack_t::operator|(const stack_t& other) const { - if (is_bottom() || other.is_top()) { - return other; - } else if (other.is_bottom() || is_top()) { - return *this; - } - offset_to_ptr_t out_ptrs; - for (auto const&kv: m_ptrs) { - auto it = other.find(kv.first); - if (it && kv.second == it.value()) - out_ptrs.insert(kv); - } - return stack_t(std::move(out_ptrs), false); -} - -void stack_t::operator-=(int key) { - auto it = find(key); - if (it) - m_ptrs.erase(key); -} - -void stack_t::set_to_bottom() { - m_ptrs.clear(); - m_is_bottom = true; -} - -void stack_t::set_to_top() { - m_ptrs.clear(); - m_is_bottom = false; -} - -stack_t stack_t::bottom() { return stack_t(true); } - -stack_t stack_t::top() { return stack_t(false); } - -bool stack_t::is_bottom() const { return m_is_bottom; } - -bool stack_t::is_top() const { - if (m_is_bottom) - return false; - return m_ptrs.empty(); -} - -void stack_t::insert(int key, ptr_t value) { - m_ptrs[key] = value; -} - -std::optional stack_t::find(int key) const { - auto it = m_ptrs.find(key); - if (it == m_ptrs.end()) return {}; - return it->second; -} - -} - bool type_domain_t::is_bottom() const { - if (m_is_bottom) return true; - return (m_stack.is_bottom() || m_registers.is_bottom()); + return false; } bool type_domain_t::is_top() const { - if (m_is_bottom) return false; - return (m_stack.is_top() && m_registers.is_top()); + return false; } type_domain_t type_domain_t::bottom() { @@ -396,8 +27,6 @@ void type_domain_t::set_to_bottom() { } void type_domain_t::set_to_top() { - m_stack.set_to_top(); - m_registers.set_to_top(); } bool type_domain_t::operator<=(const type_domain_t& abs) const { @@ -406,42 +35,22 @@ bool type_domain_t::operator<=(const type_domain_t& abs) const { } type_domain_t type_domain_t::widen(const type_domain_t& other, bool to_constants) { - // WARNING: Not implemented yet - type_domain_t res{m_types | other.m_types, m_stack | other.m_stack, other.m_ctx}; + type_domain_t res{}; return res; } void type_domain_t::operator|=(const type_domain_t& abs) { - type_domain_t tmp{abs}; - operator|=(std::move(tmp)); } void type_domain_t::operator|=(type_domain_t&& abs) { - if (is_bottom()) { - *this = abs; - return; - } - *this = *this | std::move(abs); } type_domain_t type_domain_t::operator|(const type_domain_t& other) const { - if (is_bottom() || other.is_top()) { - return other; - } - else if (other.is_bottom() || is_top()) { - return *this; - } - return type_domain_t(m_registers | other.m_registers, m_stack | other.m_stack, other.m_ctx); + return other; } type_domain_t type_domain_t::operator|(type_domain_t&& other) const { - if (is_bottom() || other.is_top()) { - return std::move(other); - } - else if (other.is_bottom() || is_top()) { - return *this; - } - return type_domain_t(m_registers | std::move(other.m_registers), m_stack | std::move(other.m_stack), other.m_ctx); + return other; } type_domain_t type_domain_t::operator&(const type_domain_t& abs) const { @@ -453,8 +62,6 @@ type_domain_t type_domain_t::narrow(const type_domain_t& other) const { } void type_domain_t::write(std::ostream& os) const { - os << m_registers; - os << m_stack << "\n"; } void type_domain_t::initialize_loop_counter(label_t label) { @@ -471,22 +78,10 @@ string_invariant type_domain_t::to_set() { } void type_domain_t::operator()(const Undefined & u, location_t loc, int print) { - if (is_bottom()) return; - if (print > 0) - std::cout << " " << u << ";\n"; } void type_domain_t::operator()(const Un &u, location_t loc, int print) { - if (is_bottom()) return; - if (print > 0) - std::cout << " " << u << ";\n"; } void type_domain_t::operator()(const LoadMapFd &u, location_t loc, int print) { - if (is_bottom()) return; - if (print > 0) { - std::cout << " " << u << ";\n"; - return; - } - m_registers -= u.dst.v; } void type_domain_t::operator()(const Atomic &u, location_t loc, int print) { // WARNING: Not implemented yet @@ -495,454 +90,42 @@ void type_domain_t::operator()(const IncrementLoopCounter &u, location_t loc, in // WARNING: Not implemented yet } void type_domain_t::operator()(const Call &u, location_t loc, int print) { - if (is_bottom()) return; - register_t r0_reg{R0_RETURN_VALUE}; - auto r0 = reg_with_loc_t(r0_reg, loc); - if (print > 0) { - if (u.is_map_lookup) { - auto it = m_registers.find(r0); - if (it) { - print_annotated(u, it.value(), std::cout); - } - } - else - std::cout << " " << u << ";\n"; - return; - } - if (u.is_map_lookup) { - auto type = ptr_no_off_t(crab::region::T_SHARED); - m_registers.insert(r0_reg, r0, type); - } - else { - m_registers -= r0_reg; - } } void type_domain_t::operator()(const Callx &u, location_t loc, int print) { // WARNING: Not implemented yet } void type_domain_t::operator()(const Exit &u, location_t loc, int print) { - if (is_bottom()) return; - if (print > 0) - std::cout << " " << u << ";\n"; } void type_domain_t::operator()(const Jmp &u, location_t loc, int print) { - if (is_bottom()) return; - if (print > 0) - std::cout << " " << u << ";\n"; } void type_domain_t::operator()(const Packet & u, location_t loc, int print) { - if (is_bottom()) return; - if (print > 0) { - std::cout << " " << u << ";\n"; - return; - } - m_registers -= register_t{0}; } void type_domain_t::operator()(const Assume &u, location_t loc, int print) { - if (is_bottom()) return; - if (print > 0) - std::cout << " " << u << ";\n"; } void type_domain_t::operator()(const Assert &u, location_t loc, int print) { - if (is_bottom()) return; - if (print > 0) - std::cout << " " << u << ";\n"; } type_domain_t type_domain_t::setup_entry() { - - std::shared_ptr ctx = std::make_shared(global_program_info.get().type.context_descriptor); - std::shared_ptr all_types = std::make_shared(); - - live_registers_t vars; - register_types_t typ(std::move(vars), all_types); - - auto r1 = reg_with_loc_t(R1_ARG, std::make_pair(label_t::entry, static_cast(0))); - auto r10 = reg_with_loc_t(R10_STACK_POINTER, std::make_pair(label_t::entry, static_cast(0))); - - typ.insert(R1_ARG, r1, ptr_with_off_t(crab::region::T_CTX, 0)); - typ.insert(R10_STACK_POINTER, r10, ptr_with_off_t(crab::region::T_STACK, 512)); - - type_domain_t inv(std::move(typ), crab::stack_t::top(), ctx); + type_domain_t inv; return inv; } void type_domain_t::report_type_error(std::string s, location_t loc) { - std::cout << "type_error at line " << loc->second << " in bb " << loc->first << "\n"; - std::cout << s; - error_location = loc; - set_to_bottom(); } void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { - if (is_bottom()) return; - if (print > 0) { - if (print == 2) { - if ((std::holds_alternative(bin.v) && (bin.op == Bin::Op::MOV || bin.op == Bin::Op::ADD)) || - (std::holds_alternative(bin.v) && bin.op == Bin::Op::ADD)) { - auto reg_with_loc = reg_with_loc_t(bin.dst.v, loc); - auto it = m_registers.find(reg_with_loc); - if (it) { - print_annotated(bin, it.value(), std::cout); - std::cout << "\n"; - return; - } - } - } - std::cout << " " << bin << ";\n"; - return; - } - - ptr_t dst_reg; - if (bin.op == Bin::Op::ADD) { - auto it = m_registers.find(bin.dst.v); - if (!it) { - m_registers -= bin.dst.v; - return; - } - dst_reg = it.value(); - } - - if (std::holds_alternative(bin.v)) { - Reg src = std::get(bin.v); - switch (bin.op) - { - case Bin::Op::MOV: { - auto it1 = m_registers.find(src.v); - if (!it1) { - //std::cout << "type_error: assigning an unknown pointer or a number - r" << (int)src.v << "\n"; - m_registers -= bin.dst.v; - break; - } - auto reg = reg_with_loc_t(bin.dst.v, loc); - m_registers.insert(bin.dst.v, reg, it1.value()); - break; - } - case Bin::Op::ADD: { - auto it1 = m_registers.find(src.v); - if (it1) { - std::string s = std::to_string(static_cast(src.v)); - std::string s1 = std::to_string(static_cast(bin.dst.v)); - std::string desc = std::string("\taddition of two pointers, r") + s + " and r" + s1 + " not allowed\n"; - report_type_error(desc, loc); - return; - } - else { - if (std::holds_alternative(dst_reg)) { - // register to be added is not a pointer, but a number. its value is unknown - /* - std::string s = std::to_string(static_cast(bin.dst.v)); - std::string desc = std::string("\toffset of the pointer r") + s + " unknown\n"; - report_type_error(desc, loc); - return; - */ - ptr_with_off_t dst_reg_with_off = std::get(dst_reg); - if (dst_reg_with_off.get_region() == crab::region::T_STACK) { - m_stack.set_to_top(); - m_stack -= dst_reg_with_off.get_offset(); - return; - } - else { - // currently, we do not read any other pointers from CTX except the ones already stored - // in case we add the functionality, we will have to implement forgetting of CTX offsets - } - } - else { - auto reg = reg_with_loc_t(bin.dst.v, loc); - m_registers.insert(bin.dst.v, reg, dst_reg); - } - } - break; - } - default: - m_registers -= bin.dst.v; - break; - } - } - else { - int imm = static_cast(std::get(bin.v).v); - switch (bin.op) - { - case Bin::Op::ADD: { - if (std::holds_alternative(dst_reg)) { - auto ptr_with_off = std::get(dst_reg); - ptr_with_off.set_offset(ptr_with_off.get_offset() + imm); - auto reg = reg_with_loc_t(bin.dst.v, loc); - m_registers.insert(bin.dst.v, reg, ptr_with_off); - } - else { - auto reg = reg_with_loc_t(bin.dst.v, loc); - m_registers.insert(bin.dst.v, reg, dst_reg); - } - break; - } - default: { - m_registers -= bin.dst.v; - break; - } - } - } } void type_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc, int print) { - - if (print > 0) { - auto target_reg_loc = reg_with_loc_t(target_reg.v, loc); - auto it = m_registers.find(target_reg_loc); - if (it) - print_annotated(b, it.value(), std::cout); - else - std::cout << " " << b << ";\n"; - return; - } - int offset = b.access.offset; - Reg basereg = b.access.basereg; - - auto it = m_registers.find(basereg.v); - if (!it) { - std::string s = std::to_string(static_cast(basereg.v)); - std::string desc = std::string("\tloading from an unknown pointer, or from number - r") + s + "\n"; - report_type_error(desc, loc); - return; - } - ptr_t type_basereg = it.value(); - - if (std::holds_alternative(type_basereg)) { - //std::cout << "type_error: loading from either packet or shared region not allowed - r" << (int)basereg.v << "\n"; - m_registers -= target_reg.v; - return; - } - - ptr_with_off_t type_with_off = std::get(type_basereg); - int load_at = offset+type_with_off.get_offset(); - - switch (type_with_off.get_region()) { - case crab::region::T_STACK: { - - auto it = m_stack.find(load_at); - - if (!it) { - //std::cout << "type_error: no field at loaded offset " << load_at << " in stack\n"; - m_registers -= target_reg.v; - return; - } - ptr_t type_loaded = it.value(); - - if (std::holds_alternative(type_loaded)) { - ptr_with_off_t type_loaded_with_off = std::get(type_loaded); - auto reg = reg_with_loc_t(target_reg.v, loc); - m_registers.insert(target_reg.v, reg, type_loaded_with_off); - } - else { - ptr_no_off_t type_loaded_no_off = std::get(type_loaded); - auto reg = reg_with_loc_t(target_reg.v, loc); - m_registers.insert(target_reg.v, reg, type_loaded_no_off); - } - - break; - } - case crab::region::T_CTX: { - - auto it = m_ctx->find(load_at); - - if (!it) { - //std::cout << "type_error: no field at loaded offset " << load_at << " in context\n"; - m_registers -= target_reg.v; - return; - } - ptr_no_off_t type_loaded = it.value(); - - auto reg = reg_with_loc_t(target_reg.v, loc); - m_registers.insert(target_reg.v, reg, type_loaded); - break; - } - - default: { - assert(false); - } - } } void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t loc, int print) { - - if (print > 0) { - std::cout << " " << b << ";\n"; - return; - } - int offset = b.access.offset; - Reg basereg = b.access.basereg; - int width = b.access.width; - - auto it = m_registers.find(basereg.v); - if (!it) { - std::string s = std::to_string(static_cast(basereg.v)); - std::string desc = std::string("\tstoring at an unknown pointer, or from number - r") + s + "\n"; - report_type_error(desc, loc); - return; - } - ptr_t type_basereg = it.value(); - - auto it2 = m_registers.find(target_reg.v); - - if (std::holds_alternative(type_basereg)) { - // base register is either CTX_P or STACK_P - ptr_with_off_t type_basereg_with_off = std::get(type_basereg); - - int store_at = offset+type_basereg_with_off.get_offset(); - if (type_basereg_with_off.get_region() == crab::region::T_STACK) { - // type of basereg is STACK_P - if (!it2) { - // std::cout << "type_error: storing either a number or an unknown pointer - r" << (int)target_reg.v << "\n"; - m_stack -= store_at; - return; - } - else { - auto type_to_store = it2.value(); - /* - if (std::holds_alternative(type_to_store) && - std::get(type_to_store).r == crab::region::T_STACK) { - std::string s = std::to_string(static_cast(target_reg.v)); - std::string desc = std::string("\twe cannot store stack pointer, r") + s + ", into stack\n"; - //report_type_error(desc, loc); - return; - } - else { - */ - for (auto i = store_at+1; i < store_at+width; i++) { - auto it3 = m_stack.find(i); - if (it3) { - std::string s = std::to_string(store_at); - std::string s1 = std::to_string(i); - std::string desc = std::string("\ttype being stored into stack at ") + s + " is overlapping with already stored at " + s1 + "\n"; - report_type_error(desc, loc); - return; - } - } - m_stack.insert(store_at, type_to_store); - // revise: code below checks if there is already something stored at same location, the type should be the same -- it is very restricted and not required. - // However, when we support storing info like width of type, we need more checks - /* - auto it4 = m_stack.find(store_at); - if (it4) { - auto type_in_stack = it4.value(); - if (type_to_store != type_in_stack) { - std::string s = std::to_string(store_at); - std::string desc = std::string("\ttype being stored at offset ") + s + " is not the same as already stored in stack\n"; - report_type_error(desc, loc); - return; - } - } - else { - m_stack.insert(store_at, type_to_store); - } - */ - //} - } - } - else if (type_basereg_with_off.get_region() == crab::region::T_CTX) { - // type of basereg is CTX_P - if (it2) { - std::string s = std::to_string(static_cast(target_reg.v)); - std::string desc = std::string("\twe cannot store a pointer, r") + s + ", into ctx\n"; - report_type_error(desc, loc); - return; - } - } - else - assert(false); - } - else { - // base register type is either PACKET_P, SHARED_P or STACK_P without known offset - ptr_no_off_t type_basereg_no_off = std::get(type_basereg); - - // if basereg is a stack_p with no offset, we do not store anything, and no type errors - // if we later load with that pointer, we read nothing -- load is no-op - if (it2 && type_basereg_no_off.get_region() != crab::region::T_STACK) { - std::string s = std::to_string(static_cast(target_reg.v)); - std::string desc = std::string("\twe cannot store a pointer, r") + s + ", into packet or shared\n"; - report_type_error(desc, loc); - return; - } - } } void type_domain_t::operator()(const Mem& b, location_t loc, int print) { - if (is_bottom()) return; - - if (std::holds_alternative(b.value)) { - if (b.is_load) { - do_load(b, std::get(b.value), loc, print); - } else { - do_mem_store(b, std::get(b.value), loc, print); - } - } else { - std::string s = std::to_string(static_cast(std::get(b.value).v)); - std::string desc = std::string("\tEither loading to a number (not allowed) or storing a number (not allowed yet) - ") + s + "\n"; - report_type_error(desc, loc); - return; - } -} - -void type_domain_t::print_initial_types() { - auto label = label_t::entry; - location_t loc = location_t(std::make_pair(label, 0)); - std::cout << "\n" << *m_ctx << "\n"; - std::cout << m_stack << "\n"; - - std::cout << "Initial register types:\n"; - auto r1_with_loc = reg_with_loc_t(R1_ARG, loc); - auto it = m_registers.find(r1_with_loc); - if (it) { - std::cout << " "; - print_type(R1_ARG, it.value()); - std::cout << "\n"; - } - auto r10_with_loc = reg_with_loc_t(R10_STACK_POINTER, loc); - auto it2 = m_registers.find(r10_with_loc); - if (it2) { - std::cout << " "; - print_type(R10_STACK_POINTER, it2.value()); - std::cout << "\n"; - } - std::cout << "\n"; } void type_domain_t::operator()(const basic_block_t& bb, int print) { - auto label = bb.label(); - uint32_t curr_pos = 0; - location_t loc; - if (print > 0) { - if (label == label_t::entry) { - print_initial_types(); - m_is_bottom = false; - } - std::cout << label << ":\n"; - } - - for (const Instruction& statement : bb) { - loc = location_t(std::make_pair(label, ++curr_pos)); - if (print > 0) std::cout << " " << curr_pos << "."; - std::visit([this, loc, print](const auto& v) { std::apply(*this, std::make_tuple(v, loc, print)); }, statement); - if (print > 0 && error_location->first == loc->first && error_location->second == loc->second) std::cout << "type_error\n"; - } - - if (print > 0) { - auto [it, et] = bb.next_blocks(); - if (it != et) { - std::cout << " " - << "goto "; - for (; it != et;) { - std::cout << *it; - ++it; - if (it == et) { - std::cout << ";"; - } else { - std::cout << ","; - } - } - } - std::cout << "\n\n"; - } } void type_domain_t::set_require_check(check_require_func_t f) {} diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 0624e4f32..c0cd87bc7 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -10,144 +10,9 @@ #include "linear_constraint.hpp" #include "string_constraints.hpp" -namespace crab { - -enum class region { - T_CTX, - T_STACK, - T_PACKET, - T_SHARED -}; - - -class ptr_no_off_t { - region m_r; - - public: - ptr_no_off_t() = default; - ptr_no_off_t(const ptr_no_off_t &) = default; - ptr_no_off_t(ptr_no_off_t &&) = default; - ptr_no_off_t &operator=(const ptr_no_off_t &) = default; - ptr_no_off_t &operator=(ptr_no_off_t &&) = default; - ptr_no_off_t(region _r) : m_r(_r) {} - - constexpr region get_region() const; - void set_region(region); - void write(std::ostream&) const; - friend std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p); - //bool operator==(const ptr_no_off_t& p2); - //bool operator!=(const ptr_no_off_t& p2); -}; - -class ptr_with_off_t { - region m_r; - int m_offset; - - public: - ptr_with_off_t() = default; - ptr_with_off_t(const ptr_with_off_t &) = default; - ptr_with_off_t(ptr_with_off_t &&) = default; - ptr_with_off_t &operator=(const ptr_with_off_t &) = default; - ptr_with_off_t &operator=(ptr_with_off_t &&) = default; - ptr_with_off_t(region _r, int _off) : m_r(_r), m_offset(_off) {} - - constexpr int get_offset() const; - void set_offset(int); - constexpr region get_region() const; - void set_region(region); - void write(std::ostream&) const; - friend std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p); - //bool operator==(const ptr_with_off_t& p2); - //bool operator!=(const ptr_with_off_t& p2); -}; - -using ptr_t = std::variant; -using register_t = uint8_t; -using location_t = boost::optional>; - -class reg_with_loc_t { - register_t m_reg; - location_t m_loc; - - public: - reg_with_loc_t(register_t _r, location_t _loc) : m_reg(_r), m_loc(_loc) {} - bool operator==(const reg_with_loc_t& other) const; - std::size_t hash() const; - friend std::ostream& operator<<(std::ostream& o, const reg_with_loc_t& reg); - void write(std::ostream& ) const; -}; - -class ctx_t { - using offset_to_ptr_no_off_t = std::unordered_map; - - offset_to_ptr_no_off_t m_packet_ptrs; - - public: - ctx_t(const ebpf_context_descriptor_t* desc); - std::optional find(int key) const; - friend std::ostream& operator<<(std::ostream& o, const ctx_t& _ctx); -}; - -class stack_t { - using offset_to_ptr_t = std::unordered_map; - - offset_to_ptr_t m_ptrs; - bool m_is_bottom; - - public: - stack_t(bool is_bottom = false) : m_is_bottom(is_bottom) {} - stack_t(offset_to_ptr_t && ptrs, bool is_bottom) - : m_ptrs(std::move(ptrs)) , m_is_bottom(is_bottom) {} - - stack_t operator|(const stack_t& other) const; - void operator-=(int); - void set_to_bottom(); - void set_to_top(); - static stack_t bottom(); - static stack_t top(); - bool is_bottom() const; - bool is_top() const; - const offset_to_ptr_t &get_ptrs() { return m_ptrs; } - void insert(int key, ptr_t value); - std::optional find(int key) const; - friend std::ostream& operator<<(std::ostream& o, const stack_t& st); -}; - -using live_registers_t = std::array, 11>; -using global_type_env_t = std::unordered_map; - -class register_types_t { - live_registers_t m_cur_def; - std::shared_ptr m_reg_type_env; - bool m_is_bottom = false; - - public: - register_types_t(bool is_bottom = false) : m_reg_type_env(nullptr), m_is_bottom(is_bottom) {} - explicit register_types_t(live_registers_t&& vars, std::shared_ptr reg_type_env, bool is_bottom = false) - : m_cur_def(std::move(vars)), m_reg_type_env(reg_type_env), m_is_bottom(is_bottom) {} - - register_types_t operator|(const register_types_t& other) const; - void operator-=(register_t var); - void set_to_bottom(); - void set_to_top(); - bool is_bottom() const; - bool is_top() const; - void insert(register_t reg, const reg_with_loc_t& reg_with_loc, const ptr_t& type); - std::optional find(reg_with_loc_t reg) const; - std::optional find(register_t key) const; - const live_registers_t &get_vars() { return m_cur_def; } - friend std::ostream& operator<<(std::ostream& o, const register_types_t& p); -}; - -} - class type_domain_t final { bool m_is_bottom = false; - location_t error_location = boost::none; - crab::stack_t m_stack; - crab::register_types_t m_registers; - std::shared_ptr m_ctx; public: @@ -156,8 +21,6 @@ class type_domain_t final { type_domain_t(const type_domain_t& o) = default; type_domain_t& operator=(type_domain_t&& o) = default; type_domain_t& operator=(const type_domain_t& o) = default; - type_domain_t(crab::register_types_t&& _types, crab::stack_t&& _st, std::shared_ptr _ctx) - : m_stack(std::move(_st)), m_registers(std::move(_types)), m_ctx(_ctx) {} // eBPF initialization: R1 points to ctx, R10 to stack, etc. static type_domain_t setup_entry(); // bottom/top diff --git a/src/crab_verifier.cpp b/src/crab_verifier.cpp index 972b5b6f6..e2cebdf1e 100644 --- a/src/crab_verifier.cpp +++ b/src/crab_verifier.cpp @@ -15,6 +15,7 @@ #include "crab/abstract_domain.hpp" #include "crab/ebpf_domain.hpp" #include "crab/type_domain.hpp" +#include "crab/region_domain.hpp" #include "crab/fwd_analyzer.hpp" #include "crab_utils/lazy_allocator.hpp" @@ -106,8 +107,7 @@ static void print_report(std::ostream& os, const checks_db& db, const Instructio static checks_db get_analysis_report(std::ostream& s, cfg_t& cfg, crab::invariant_table_t& pre_invariants, crab::invariant_table_t& post_invariants) { // Analyze the control-flow graph. - checks_db db = generate_report(cfg, pre_invariants, post_invariants); - if (thread_local_options.abstract_domain == abstract_domain_kind::TYPE_DOMAIN) { + if (thread_local_options.abstract_domain == abstract_domain_kind::REGION_DOMAIN) { auto state = post_invariants.at(label_t::exit); for (const label_t& label : cfg.sorted_labels()) { state(cfg.get_node(label), thread_local_options.print_invariants ? 2 : 1); @@ -130,6 +130,10 @@ static abstract_domain_t make_initial(const ebpf_verifier_options_t* options) { ebpf_domain_t entry_inv = ebpf_domain_t::setup_entry(true); return abstract_domain_t(entry_inv); } + case abstract_domain_kind::REGION_DOMAIN: { + region_domain_t entry_inv = region_domain_t::setup_entry(); + return abstract_domain_t(entry_inv); + } case abstract_domain_kind::TYPE_DOMAIN: { type_domain_t entry_inv = type_domain_t::setup_entry(); return abstract_domain_t(entry_inv); @@ -176,6 +180,7 @@ crab_results get_ebpf_report(std::ostream& s, cfg_t& cfg, program_info info, con return crab_results(std::move(cfg), std::move(pre_invariants), std::move(post_invariants), std::move(get_analysis_report(s, cfg, pre_invariants, post_invariants))); + } catch (std::runtime_error& e) { // Convert verifier runtime_error exceptions to failure. checks_db db; diff --git a/src/main/check.cpp b/src/main/check.cpp index 968c02c9e..aac3713a4 100644 --- a/src/main/check.cpp +++ b/src/main/check.cpp @@ -55,7 +55,7 @@ int main(int argc, char** argv) { app.add_flag("-l", list, "List sections"); std::string domain = "zoneCrab"; - std::set doms{"stats", "linux", "zoneCrab", "types", "cfg"}; + std::set doms{"stats", "linux", "zoneCrab", "region", "offset", "cfg"}; app.add_set("-d,--dom,--domain", domain, doms, "Abstract domain")->type_name("DOMAIN"); app.add_flag("--termination", ebpf_verifier_options.check_termination, "Verify termination"); @@ -163,9 +163,12 @@ int main(int argc, char** argv) { print_map_descriptors(global_program_info->map_descriptors, out); } - if (domain == "zoneCrab" || domain == "types") { + if (domain == "zoneCrab" || domain == "region" || domain == "type") { ebpf_verifier_stats_t verifier_stats; - if (domain == "types") { + if (domain == "region") { + ebpf_verifier_options.abstract_domain = abstract_domain_kind::REGION_DOMAIN; + } + else if (domain == "type") { ebpf_verifier_options.abstract_domain = abstract_domain_kind::TYPE_DOMAIN; } auto [res, seconds] = timed_execution([&] { From eda405efb7df8a9fb3d62ae6fa90862f62b98cd5 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 11 Jul 2023 03:37:47 -0400 Subject: [PATCH 099/373] Implementation for multiple transformers for region domain Incorporate region domain into a super type domain; Implement load, store and binary operation transformers; Implement join; Print initial types in the type domain Signed-off-by: Ameer Hamza --- src/crab/ebpf_domain.cpp | 2 +- src/crab/ebpf_domain.hpp | 2 +- src/crab/region_domain.cpp | 257 +++++++++--- src/crab/region_domain.hpp | 56 ++- src/crab/type_domain.cpp | 167 +++++++- src/crab/type_domain.hpp | 26 +- src/crab_verifier.cpp | 4 +- src/main/check.cpp | 2 +- src/test/test_verify_type_domain.cpp | 577 +++++++++++++++++++++++++++ 9 files changed, 992 insertions(+), 101 deletions(-) create mode 100644 src/test/test_verify_type_domain.cpp diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index 5cb1738c3..039849bec 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -1567,7 +1567,7 @@ void ebpf_domain_t::operator()(const TypeConstraint& s, location_t loc, int prin require(m_inv, linear_constraint_t::FALSE(), "Invalid type"); } -void ebpf_domain_t::operator()(const FuncConstraint& s) { +void ebpf_domain_t::operator()(const FuncConstraint& s, location_t loc, int print) { // Look up the helper function id. const reg_pack_t& reg = reg_pack(s.reg); auto src_interval = m_inv.eval_interval(reg.svalue); diff --git a/src/crab/ebpf_domain.hpp b/src/crab/ebpf_domain.hpp index 620764bc6..83966e936 100644 --- a/src/crab/ebpf_domain.hpp +++ b/src/crab/ebpf_domain.hpp @@ -65,7 +65,7 @@ class ebpf_domain_t final { void operator()(const Callx&, location_t loc = boost::none, int print = 0); void operator()(const Comparable&, location_t loc = boost::none, int print = 0); void operator()(const Exit&, location_t loc = boost::none, int print = 0); - void operator()(const FuncConstraint&); + void operator()(const FuncConstraint&, location_t loc = boost::none, int print = 0); void operator()(const Jmp&, location_t loc = boost::none, int print = 0); void operator()(const LoadMapFd&, location_t loc = boost::none, int print = 0); void operator()(const Atomic&, location_t loc = boost::none, int print = 0); diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index cfaa59a57..7c3920e11 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -10,7 +10,7 @@ using crab::ptr_t; using crab::ptr_with_off_t; using crab::ptr_no_off_t; using crab::ctx_t; -using crab::global_type_env_t; +using crab::global_region_env_t; using crab::reg_with_loc_t; using crab::live_registers_t; using crab::register_types_t; @@ -149,12 +149,8 @@ std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p) { void ptr_with_off_t::set_offset(int off) { m_offset = off; } -constexpr int ptr_with_off_t::get_offset() const { return m_offset; } - void ptr_with_off_t::set_region(region r) { m_r = r; } -constexpr region ptr_with_off_t::get_region() const { return m_r; } - bool operator==(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { return (p1.get_region() == p2.get_region()); } @@ -174,8 +170,6 @@ std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p) { void ptr_no_off_t::set_region(region r) { m_r = r; } -constexpr region ptr_no_off_t::get_region() const { return m_r; } - void reg_with_loc_t::write(std::ostream& o) const { o << "r" << static_cast(m_reg) << "@" << m_loc->second << " in " << m_loc->first << " "; } @@ -228,12 +222,29 @@ std::ostream& operator<<(std::ostream& o, const ctx_t& _ctx) { ctx_t::ctx_t(const ebpf_context_descriptor_t* desc) { - if (desc->data != -1) + if (desc->data != -1) { m_packet_ptrs[desc->data] = crab::ptr_no_off_t(crab::region::T_PACKET); - if (desc->end != -1) + } + if (desc->end != -1) { m_packet_ptrs[desc->end] = crab::ptr_no_off_t(crab::region::T_PACKET); - if (desc->meta != -1) + } + if (desc->meta != -1) { m_packet_ptrs[desc->meta] = crab::ptr_no_off_t(crab::region::T_PACKET); + } +} + +size_t ctx_t::size() const { + return m_packet_ptrs.size(); +} + +std::vector ctx_t::get_keys() const { + std::vector keys; + keys.reserve(size()); + + for (auto const&kv : m_packet_ptrs) { + keys.push_back(kv.first); + } + return keys; } std::optional ctx_t::find(int key) const { @@ -247,7 +258,7 @@ std::ostream& operator<<(std::ostream& o, const register_types_t& typ) { if (typ.is_bottom()) o << "_|_\n"; else { - for (const auto& v : *(typ.m_reg_type_env)) { + for (const auto& v : *(typ.m_region_env)) { o << v.first << ": "; print_ptr_type(v.second); o << "\n"; @@ -267,12 +278,25 @@ register_types_t register_types_t::operator|(const register_types_t& other) cons if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; auto it1 = find(*(m_cur_def[i])); auto it2 = other.find(*(other.m_cur_def[i])); - if (it1 && it2 && it1.value() == it2.value()) { - out_vars[i] = m_cur_def[i]; + if (it1 && it2) { + ptr_t pt1 = it1.value(), pt2 = it2.value(); + if (pt1 == pt2) { + out_vars[i] = m_cur_def[i]; + } + // TODO + //else if (std::holds_alternative(pt1) + // && std::holds_alternative(pt2)) { + // auto pt_with_off1 = std::get(pt1); + // auto pt_with_off2 = std::get(pt2); + // if (pt_with_off1.get_region() == pt_with_off2.get_region()) { + // //out_vars[i] = std::make_shared(reg_with_loc); } std::optional register_types_t::find(reg_with_loc_t reg) const { - auto it = m_reg_type_env->find(reg); - if (it == m_reg_type_env->end()) return {}; + auto it = m_region_env->find(reg); + if (it == m_region_env->end()) return {}; return it->second; } @@ -320,6 +344,19 @@ std::optional register_types_t::find(register_t key) const { return find(reg); } +void register_types_t::print_types_at(location_t loc) const { + for (size_t i = 0; i < m_cur_def.size(); i++) { + auto reg_with_loc = reg_with_loc_t(i, loc); + auto it = find(reg_with_loc); + if (it) { + std::cout << " "; + print_type(i, it.value()); + std::cout << "\n"; + } + } + std::cout << "\n"; +} + stack_t stack_t::operator|(const stack_t& other) const { if (is_bottom() || other.is_top()) { return other; @@ -367,6 +404,21 @@ void stack_t::insert(int key, ptr_t value) { m_ptrs[key] = value; } +size_t stack_t::size() const { + return m_ptrs.size(); +} + +std::vector stack_t::get_keys() const { + std::vector keys; + keys.reserve(size()); + + for (auto const&kv : m_ptrs) { + keys.push_back(kv.first); + } + return keys; +} + + std::optional stack_t::find(int key) const { auto it = m_ptrs.find(key); if (it == m_ptrs.end()) return {}; @@ -375,6 +427,10 @@ std::optional stack_t::find(int key) const { } +std::optional region_domain_t::find_ptr_type(register_t reg) { + return m_registers.find(reg); +} + bool region_domain_t::is_bottom() const { if (m_is_bottom) return true; return (m_stack.is_bottom() || m_registers.is_bottom()); @@ -400,6 +456,26 @@ void region_domain_t::set_to_top() { m_registers.set_to_top(); } +size_t region_domain_t::ctx_size() const { + return m_ctx->size(); +} + +std::vector region_domain_t::get_ctx_keys() const { + return m_ctx->get_keys(); +} + +std::vector region_domain_t::get_stack_keys() const { + return m_stack.get_keys(); +} + +std::optional region_domain_t::find_in_ctx(int key) const { + return m_ctx->find(key); +} + +std::optional region_domain_t::find_in_stack(int key) const { + return m_stack.find(key); +} + bool region_domain_t::operator<=(const region_domain_t& abs) const { /* WARNING: The operation is not implemented yet.*/ return true; @@ -455,10 +531,6 @@ void region_domain_t::write(std::ostream& os) const { os << m_stack << "\n"; } -std::string region_domain_t::domain_name() const { - return "type_domain"; -} - crab::bound_t region_domain_t::get_loop_count_upper_bound() { // WARNING: Not implemented yet. return crab::bound_t{crab::number_t{0}}; @@ -545,19 +617,27 @@ void region_domain_t::operator()(const Assume &u, location_t loc, int print) { if (print > 0) std::cout << " " << u << ";\n"; } + +void region_domain_t::operator()(const TypeConstraint& s, location_t loc, int print) { + check_type_constraint(s); +} + void region_domain_t::operator()(const Assert &u, location_t loc, int print) { if (is_bottom()) return; - if (print > 0) + if (print > 0) { std::cout << " " << u << ";\n"; + return; + } + //std::cout << "assert: " << u << "\n"; + std::visit([this, loc, print](const auto& v) { std::apply(*this, std::make_tuple(v, loc, print)); }, u.cst); } region_domain_t region_domain_t::setup_entry() { std::shared_ptr ctx = std::make_shared(global_program_info.get().type.context_descriptor); - std::shared_ptr all_types = std::make_shared(); + std::shared_ptr all_types = std::make_shared(); - live_registers_t vars; - register_types_t typ(std::move(vars), all_types); + register_types_t typ(all_types); auto r1 = reg_with_loc_t(R1_ARG, std::make_pair(label_t::entry, static_cast(0))); auto r10 = reg_with_loc_t(R10_STACK_POINTER, std::make_pair(label_t::entry, static_cast(0))); @@ -576,7 +656,62 @@ void region_domain_t::report_type_error(std::string s, location_t loc) { set_to_bottom(); } -void region_domain_t::operator()(const Bin& bin, location_t loc, int print) { +void region_domain_t::check_type_constraint(const TypeConstraint& s) { + auto it = find_ptr_type(s.reg.v); + if (it) { + if (s.types == TypeGroup::pointer || s.types == TypeGroup::ptr_or_num + || s.types == TypeGroup::non_map_fd) return; + ptr_t p_type = it.value(); + if (std::holds_alternative(p_type)) { + ptr_with_off_t p_type_with_off = std::get(p_type); + if (p_type_with_off.get_region() == crab::region::T_CTX) { + if (s.types == TypeGroup::ctx) return; + } + else { + if (s.types == TypeGroup::stack || s.types == TypeGroup::mem + || s.types == TypeGroup::stack_or_packet || s.types == TypeGroup::mem_or_num) { + return; + } + } + } + else { + ptr_no_off_t p_type_no_off = std::get(p_type); + if (p_type_no_off.get_region() == crab::region::T_PACKET) { + if (s.types == TypeGroup::packet || s.types == TypeGroup::mem + || s.types == TypeGroup::mem_or_num) return; + } + else if (p_type_no_off.get_region() == crab::region::T_SHARED) { + if (s.types == TypeGroup::shared || s.types == TypeGroup::mem + || s.types == TypeGroup::mem_or_num) return; + } + else { + // we might have the case where we add an unknown number to stack or ctx's offset and we do not know the offset now + if (p_type_no_off.get_region() == crab::region::T_STACK) { + if (s.types == TypeGroup::stack || s.types == TypeGroup::stack_or_packet + || s.types == TypeGroup::mem || s.types == TypeGroup::mem_or_num) + return; + } + else { + if (s.types == TypeGroup::ctx) return; + } + } + } + } + else { + // map_fd, non_map_fd, map_fd_programs, and numbers, all should come here + // right now, pass any such cases, add more strict checks later + // TODO + if (s.types == TypeGroup::number || s.types == TypeGroup::ptr_or_num + || s.types == TypeGroup::map_fd_programs || s.types == TypeGroup::non_map_fd + || s.types == TypeGroup::ptr_or_num || s.types == TypeGroup::mem_or_num + || s.types == TypeGroup::map_fd) + return; + } + std::cout << "type constraint assert fail: " << s << "\n"; + //exit(1); +} + +void region_domain_t::do_bin(const Bin& bin, std::shared_ptr src_const_value, location_t loc, int print) { if (is_bottom()) return; if (print > 0) { if (print == 2) { @@ -609,10 +744,10 @@ void region_domain_t::operator()(const Bin& bin, location_t loc, int print) { Reg src = std::get(bin.v); switch (bin.op) { + // ra = rb case Bin::Op::MOV: { auto it1 = m_registers.find(src.v); if (!it1) { - //std::cout << "type_error: assigning an unknown pointer or a number - r" << (int)src.v << "\n"; m_registers -= bin.dst.v; break; } @@ -620,6 +755,7 @@ void region_domain_t::operator()(const Bin& bin, location_t loc, int print) { m_registers.insert(bin.dst.v, reg, it1.value()); break; } + // ra += rb case Bin::Op::ADD: { auto it1 = m_registers.find(src.v); if (it1) { @@ -629,31 +765,31 @@ void region_domain_t::operator()(const Bin& bin, location_t loc, int print) { report_type_error(desc, loc); return; } - else { - if (std::holds_alternative(dst_reg)) { - // register to be added is not a pointer, but a number. its value is unknown - /* - std::string s = std::to_string(static_cast(bin.dst.v)); - std::string desc = std::string("\toffset of the pointer r") + s + " unknown\n"; - report_type_error(desc, loc); - return; - */ - ptr_with_off_t dst_reg_with_off = std::get(dst_reg); - if (dst_reg_with_off.get_region() == crab::region::T_STACK) { + if (std::holds_alternative(dst_reg)) { + ptr_with_off_t dst_reg_with_off = std::get(dst_reg); + if (dst_reg_with_off.get_region() == crab::region::T_STACK) { + if (src_const_value) { + int updated_offset = dst_reg_with_off.get_offset()+(*src_const_value); + dst_reg_with_off.set_offset(updated_offset); + auto reg = reg_with_loc_t(bin.dst.v, loc); + m_registers.insert(bin.dst.v, reg, dst_reg_with_off); + + } + else { m_stack.set_to_top(); m_stack -= dst_reg_with_off.get_offset(); return; } - else { - // currently, we do not read any other pointers from CTX except the ones already stored - // in case we add the functionality, we will have to implement forgetting of CTX offsets - } } else { - auto reg = reg_with_loc_t(bin.dst.v, loc); - m_registers.insert(bin.dst.v, reg, dst_reg); + // currently, we do not read any other pointers from CTX except the ones already stored + // in case we add the functionality, we will have to implement forgetting of CTX offsets } } + else { + auto reg = reg_with_loc_t(bin.dst.v, loc); + m_registers.insert(bin.dst.v, reg, dst_reg); + } break; } default: @@ -686,6 +822,10 @@ void region_domain_t::operator()(const Bin& bin, location_t loc, int print) { } } +void region_domain_t::operator()(const Bin& bin, location_t loc, int print) { + do_bin(bin, nullptr, loc, print); +} + void region_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc, int print) { if (print > 0) { @@ -885,31 +1025,20 @@ void region_domain_t::operator()(const Mem& b, location_t loc, int print) { } } -void region_domain_t::print_initial_types() { +void region_domain_t::print_registers_at(location_t loc) const { + m_registers.print_types_at(loc); +} + +void region_domain_t::print_initial_types() const { auto label = label_t::entry; location_t loc = location_t(std::make_pair(label, 0)); std::cout << "\n" << *m_ctx << "\n"; std::cout << m_stack << "\n"; - std::cout << "Initial register types:\n"; - auto r1_with_loc = reg_with_loc_t(R1_ARG, loc); - auto it = m_registers.find(r1_with_loc); - if (it) { - std::cout << " "; - print_type(R1_ARG, it.value()); - std::cout << "\n"; - } - auto r10_with_loc = reg_with_loc_t(R10_STACK_POINTER, loc); - auto it2 = m_registers.find(r10_with_loc); - if (it2) { - std::cout << " "; - print_type(R10_STACK_POINTER, it2.value()); - std::cout << "\n"; - } - std::cout << "\n"; + print_registers_at(loc); } -void region_domain_t::operator()(const basic_block_t& bb, bool check_termination, int print) { +void region_domain_t::operator()(const basic_block_t& bb, int print) { auto label = bb.label(); uint32_t curr_pos = 0; location_t loc; @@ -946,5 +1075,3 @@ void region_domain_t::operator()(const basic_block_t& bb, bool check_termination std::cout << "\n\n"; } } - -void region_domain_t::set_require_check(check_require_func_t f) {} diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index 8c72cb874..c4d1998b3 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -9,6 +9,7 @@ #include "crab/cfg.hpp" #include "linear_constraint.hpp" #include "string_constraints.hpp" +#include namespace crab { @@ -31,7 +32,7 @@ class ptr_no_off_t { ptr_no_off_t &operator=(ptr_no_off_t &&) = default; ptr_no_off_t(region _r) : m_r(_r) {} - constexpr region get_region() const; + constexpr region get_region() const { return m_r; } void set_region(region); void write(std::ostream&) const; friend std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p); @@ -51,9 +52,9 @@ class ptr_with_off_t { ptr_with_off_t &operator=(ptr_with_off_t &&) = default; ptr_with_off_t(region _r, int _off) : m_r(_r), m_offset(_off) {} - constexpr int get_offset() const; + constexpr int get_offset() const { return m_offset; } void set_offset(int); - constexpr region get_region() const; + constexpr region get_region() const { return m_r; } void set_region(region); void write(std::ostream&) const; friend std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p); @@ -84,6 +85,8 @@ class ctx_t { public: ctx_t(const ebpf_context_descriptor_t* desc); + size_t size() const; + std::vector get_keys() const; std::optional find(int key) const; friend std::ostream& operator<<(std::ostream& o, const ctx_t& _ctx); }; @@ -110,21 +113,27 @@ class stack_t { const offset_to_ptr_t &get_ptrs() { return m_ptrs; } void insert(int key, ptr_t value); std::optional find(int key) const; + std::vector get_keys() const; + size_t size() const; friend std::ostream& operator<<(std::ostream& o, const stack_t& st); }; using live_registers_t = std::array, 11>; -using global_type_env_t = std::unordered_map; +using global_region_env_t = std::unordered_map; class register_types_t { + live_registers_t m_cur_def; - std::shared_ptr m_reg_type_env; + std::shared_ptr m_region_env; bool m_is_bottom = false; public: - register_types_t(bool is_bottom = false) : m_reg_type_env(nullptr), m_is_bottom(is_bottom) {} - explicit register_types_t(live_registers_t&& vars, std::shared_ptr reg_type_env, bool is_bottom = false) - : m_cur_def(std::move(vars)), m_reg_type_env(reg_type_env), m_is_bottom(is_bottom) {} + register_types_t(bool is_bottom = false) : m_region_env(nullptr), m_is_bottom(is_bottom) {} + explicit register_types_t(live_registers_t&& vars, std::shared_ptr reg_type_env, bool is_bottom = false) + : m_cur_def(std::move(vars)), m_region_env(reg_type_env), m_is_bottom(is_bottom) {} + + explicit register_types_t(std::shared_ptr reg_type_env, bool is_bottom = false) + : m_region_env(reg_type_env), m_is_bottom(is_bottom) {} register_types_t operator|(const register_types_t& other) const; void operator-=(register_t var); @@ -137,6 +146,7 @@ class register_types_t { std::optional find(register_t key) const; const live_registers_t &get_vars() { return m_cur_def; } friend std::ostream& operator<<(std::ostream& o, const register_types_t& p); + void print_types_at(location_t) const; }; } @@ -196,20 +206,36 @@ class region_domain_t final { void operator()(const Packet &, location_t loc = boost::none, int print = 0); void operator()(const Assume &, location_t loc = boost::none, int print = 0); void operator()(const Assert &, location_t loc = boost::none, int print = 0); + void operator()(const ValidAccess&, location_t loc = boost::none, int print = 0) {} + void operator()(const Comparable& s, location_t loc = boost::none, int print = 0) {} + void operator()(const Addable& s, location_t loc = boost::none, int print = 0) {} + void operator()(const ValidStore& s, location_t loc = boost::none, int print = 0) {} + void operator()(const TypeConstraint& s, location_t loc = boost::none, int print = 0); + void operator()(const ValidSize& s, location_t loc = boost::none, int print = 0) {} + void operator()(const ValidMapKeyValue& s, location_t loc = boost::none, int print = 0) {} + void operator()(const ZeroCtxOffset& s, location_t loc = boost::none, int print = 0) {} + void operator()(const ValidDivisor& s, location_t loc = boost::none, int print = 0) {} + void operator()(const FuncConstraint& s, location_t loc = boost::none, int print = 0) {}; void operator()(const IncrementLoopCounter&, location_t loc = boost::none, int print = 0); - void operator()(const basic_block_t& bb, bool check_termination, int print = 0); + void operator()(const basic_block_t& bb, int print = 0); void write(std::ostream& os) const; - std::string domain_name() const; crab::bound_t get_loop_count_upper_bound(); void initialize_loop_counter(const label_t&); string_invariant to_set(); - void set_require_check(check_require_func_t f); - - private: + void set_require_check(check_require_func_t f) {} void do_load(const Mem&, const Reg&, location_t, int print = 0); void do_mem_store(const Mem&, const Reg&, location_t, int print = 0); - void print_initial_types(); - void report_type_error(std::string, location_t); + void do_bin(const Bin&, std::shared_ptr, location_t, int print = 0); + void check_type_constraint(const TypeConstraint&); + void report_type_error(std::string, location_t); + std::optional find_ptr_type(register_t); + size_t ctx_size() const; + std::optional find_in_ctx(int key) const; + std::vector get_ctx_keys() const; + std::optional find_in_stack(int key) const; + std::vector get_stack_keys() const; + void print_registers_at(location_t) const; + void print_initial_types() const; }; // end region_domain_t diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 05528508f..46ff873fd 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -5,15 +5,13 @@ #include "crab/type_domain.hpp" -static std::string size(int w) { return std::string("u") + std::to_string(w * 8); } - - bool type_domain_t::is_bottom() const { - return false; + return m_is_bottom; } bool type_domain_t::is_top() const { - return false; + if (m_is_bottom) return false; + return (m_region.is_top()); } type_domain_t type_domain_t::bottom() { @@ -27,6 +25,7 @@ void type_domain_t::set_to_bottom() { } void type_domain_t::set_to_top() { + m_region.set_to_top(); } bool type_domain_t::operator<=(const type_domain_t& abs) const { @@ -40,17 +39,36 @@ type_domain_t type_domain_t::widen(const type_domain_t& other, bool to_constants } void type_domain_t::operator|=(const type_domain_t& abs) { + type_domain_t tmp{abs}; + operator|=(std::move(tmp)); } void type_domain_t::operator|=(type_domain_t&& abs) { + if (is_bottom()) { + *this = abs; + return; + } + *this = *this | std::move(abs); } type_domain_t type_domain_t::operator|(const type_domain_t& other) const { - return other; + if (is_bottom() || other.is_top()) { + return other; + } + else if (other.is_bottom() || is_top()) { + return *this; + } + return type_domain_t(m_region | other.m_region); } type_domain_t type_domain_t::operator|(type_domain_t&& other) const { - return other; + if (is_bottom() || other.is_top()) { + return std::move(other); + } + else if (other.is_bottom() || is_top()) { + return *this; + } + return type_domain_t(m_region | std::move(other.m_region)); } type_domain_t type_domain_t::operator&(const type_domain_t& abs) const { @@ -78,10 +96,16 @@ string_invariant type_domain_t::to_set() { } void type_domain_t::operator()(const Undefined & u, location_t loc, int print) { + if (is_bottom()) return; + m_region(u, loc, print); } void type_domain_t::operator()(const Un &u, location_t loc, int print) { + if (is_bottom()) return; + m_region(u, loc, print); } void type_domain_t::operator()(const LoadMapFd &u, location_t loc, int print) { + if (is_bottom()) return; + m_region(u, loc, print); } void type_domain_t::operator()(const Atomic &u, location_t loc, int print) { // WARNING: Not implemented yet @@ -90,42 +114,157 @@ void type_domain_t::operator()(const IncrementLoopCounter &u, location_t loc, in // WARNING: Not implemented yet } void type_domain_t::operator()(const Call &u, location_t loc, int print) { + if (is_bottom()) return; + m_region(u, loc, print); } void type_domain_t::operator()(const Callx &u, location_t loc, int print) { // WARNING: Not implemented yet } void type_domain_t::operator()(const Exit &u, location_t loc, int print) { + if (is_bottom()) return; + m_region(u, loc, print); } void type_domain_t::operator()(const Jmp &u, location_t loc, int print) { + if (is_bottom()) return; + m_region(u, loc, print); } void type_domain_t::operator()(const Packet & u, location_t loc, int print) { + if (is_bottom()) return; + m_region(u, loc, print); } void type_domain_t::operator()(const Assume &u, location_t loc, int print) { + if (is_bottom()) return; + m_region(u, loc, print); } -void type_domain_t::operator()(const Assert &u, location_t loc, int print) { + +void type_domain_t::operator()(const ValidAccess& s, location_t loc, int print) { } -type_domain_t type_domain_t::setup_entry() { - type_domain_t inv; - return inv; +void type_domain_t::operator()(const TypeConstraint& s, location_t loc, int print) { + m_region.check_type_constraint(s); } -void type_domain_t::report_type_error(std::string s, location_t loc) { +void type_domain_t::operator()(const Assert &u, location_t loc, int print) { + if (is_bottom()) return; + std::visit([this, loc, print](const auto& v) { std::apply(*this, std::make_tuple(v, loc, print)); }, u.cst); +} + +type_domain_t type_domain_t::setup_entry() { + region_domain_t reg = region_domain_t::setup_entry(); + type_domain_t typ(std::move(reg)); + return typ; } void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { + if (is_bottom()) return; + m_region.do_bin(bin, nullptr, loc, print); } void type_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc, int print) { + m_region.do_load(b, target_reg, loc, print); } void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t loc, int print) { + m_region.do_mem_store(b, target_reg, loc, print); } void type_domain_t::operator()(const Mem& b, location_t loc, int print) { + if (is_bottom()) return; + + if (std::holds_alternative(b.value)) { + if (b.is_load) { + do_load(b, std::get(b.value), loc, print); + } else { + do_mem_store(b, std::get(b.value), loc, print); + } + } } -void type_domain_t::operator()(const basic_block_t& bb, int print) { +static void print_ptr_type(ptr_t ptr) { + if (std::holds_alternative(ptr)) { + ptr_with_off_t ptr_with_off = std::get(ptr); + std::cout << ptr_with_off; + } + else { + ptr_no_off_t ptr_no_off = std::get(ptr); + std::cout << ptr_no_off; + } } -void type_domain_t::set_require_check(check_require_func_t f) {} +void type_domain_t::print_ctx() const { + std::vector ctx_keys = m_region.get_ctx_keys(); + std::cout << "ctx: {\n"; + for (auto const k : ctx_keys) { + std::optional ptr = m_region.find_in_ctx(k); + if (ptr) { + std::cout << " " << k << ": "; + print_ptr_type(ptr.value()); + std::cout << ",\n"; + } + } + std::cout << "}\n\n"; +} + +void type_domain_t::print_stack() const { + std::vector stack_keys = m_region.get_stack_keys(); + std::cout << "stack: {\n"; + for (auto const k : stack_keys) { + std::optional ptr = m_region.find_in_stack(k); + if (ptr) { + std::cout << " " << k << ": "; + print_ptr_type(ptr.value()); + std::cout << ",\n"; + } + } + std::cout << "}\n\n"; +} + +void type_domain_t::print_initial_registers() const { + auto label = label_t::entry; + location_t loc = location_t(std::make_pair(label, 0)); + std::cout << "Initial register types:\n"; + m_region.print_registers_at(loc); +} + +void type_domain_t::print_initial_types() const { + print_ctx(); + print_stack(); + print_initial_registers(); +} + +void type_domain_t::operator()(const basic_block_t& bb, int print) { + auto label = bb.label(); + uint32_t curr_pos = 0; + location_t loc; + if (print > 0) { + if (label == label_t::entry) { + print_initial_types(); + m_is_bottom = false; + } + std::cout << label << ":\n"; + } + + for (const Instruction& statement : bb) { + loc = location_t(std::make_pair(label, ++curr_pos)); + if (print > 0) std::cout << " " << curr_pos << "."; + std::visit([this, loc, print](const auto& v) { std::apply(*this, std::make_tuple(v, loc, print)); }, statement); + } + + if (print > 0) { + auto [it, et] = bb.next_blocks(); + if (it != et) { + std::cout << " " + << "goto "; + for (; it != et;) { + std::cout << *it; + ++it; + if (it == et) { + std::cout << ";"; + } else { + std::cout << ","; + } + } + } + std::cout << "\n\n"; + } +} diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index c0cd87bc7..2a3f5656e 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -6,12 +6,17 @@ #include #include "crab/abstract_domain.hpp" +#include "crab/region_domain.hpp" #include "crab/cfg.hpp" #include "linear_constraint.hpp" #include "string_constraints.hpp" -class type_domain_t final { +using crab::ptr_t; +using crab::ptr_with_off_t; +using crab::ptr_no_off_t; +class type_domain_t final { + region_domain_t m_region; bool m_is_bottom = false; public: @@ -19,6 +24,8 @@ class type_domain_t final { type_domain_t() = default; type_domain_t(type_domain_t&& o) = default; type_domain_t(const type_domain_t& o) = default; + explicit type_domain_t(region_domain_t&& reg, bool is_bottom = false) : + m_region(reg), m_is_bottom(is_bottom) {} type_domain_t& operator=(type_domain_t&& o) = default; type_domain_t& operator=(const type_domain_t& o) = default; // eBPF initialization: R1 points to ctx, R10 to stack, etc. @@ -59,19 +66,32 @@ class type_domain_t final { void operator()(const Packet &, location_t loc = boost::none, int print = 0); void operator()(const Assume &, location_t loc = boost::none, int print = 0); void operator()(const Assert &, location_t loc = boost::none, int print = 0); + void operator()(const ValidAccess&, location_t loc = boost::none, int print = 0); + void operator()(const Comparable& s, location_t loc = boost::none, int print = 0) {} + void operator()(const Addable& s, location_t loc = boost::none, int print = 0) {} + void operator()(const ValidStore& s, location_t loc = boost::none, int print = 0) {} + void operator()(const TypeConstraint& s, location_t loc = boost::none, int print = 0); + void operator()(const ValidSize& s, location_t loc = boost::none, int print = 0) {} + void operator()(const ValidMapKeyValue& s, location_t loc = boost::none, int print = 0) {} + void operator()(const ZeroCtxOffset& s, location_t loc = boost::none, int print = 0) {} + void operator()(const ValidDivisor& s, location_t loc = boost::none, int print = 0) {} + void operator()(const FuncConstraint& s, location_t loc = boost::none, int print = 0) {}; void operator()(const IncrementLoopCounter&, location_t loc = boost::none, int print = 0); void operator()(const basic_block_t& bb, int print = 0); void write(std::ostream& os) const; void initialize_loop_counter(label_t label); crab::bound_t get_loop_count_upper_bound(); string_invariant to_set(); - void set_require_check(check_require_func_t f); + void set_require_check(check_require_func_t f) {} private: void do_load(const Mem&, const Reg&, location_t, int print = 0); void do_mem_store(const Mem&, const Reg&, location_t, int print = 0); - void print_initial_types(); + void print_initial_types() const; void report_type_error(std::string, location_t); + void print_ctx() const; + void print_stack() const; + void print_initial_registers() const; }; // end type_domain_t diff --git a/src/crab_verifier.cpp b/src/crab_verifier.cpp index e2cebdf1e..d63066e22 100644 --- a/src/crab_verifier.cpp +++ b/src/crab_verifier.cpp @@ -107,7 +107,9 @@ static void print_report(std::ostream& os, const checks_db& db, const Instructio static checks_db get_analysis_report(std::ostream& s, cfg_t& cfg, crab::invariant_table_t& pre_invariants, crab::invariant_table_t& post_invariants) { // Analyze the control-flow graph. - if (thread_local_options.abstract_domain == abstract_domain_kind::REGION_DOMAIN) { + checks_db db = generate_report(cfg, pre_invariants, post_invariants); + if (thread_local_options.abstract_domain == abstract_domain_kind::TYPE_DOMAIN + || thread_local_options.abstract_domain == abstract_domain_kind::REGION_DOMAIN) { auto state = post_invariants.at(label_t::exit); for (const label_t& label : cfg.sorted_labels()) { state(cfg.get_node(label), thread_local_options.print_invariants ? 2 : 1); diff --git a/src/main/check.cpp b/src/main/check.cpp index aac3713a4..42c9a682e 100644 --- a/src/main/check.cpp +++ b/src/main/check.cpp @@ -55,7 +55,7 @@ int main(int argc, char** argv) { app.add_flag("-l", list, "List sections"); std::string domain = "zoneCrab"; - std::set doms{"stats", "linux", "zoneCrab", "region", "offset", "cfg"}; + std::set doms{"stats", "linux", "zoneCrab", "region", "cfg", "type"}; app.add_set("-d,--dom,--domain", domain, doms, "Abstract domain")->type_name("DOMAIN"); app.add_flag("--termination", ebpf_verifier_options.check_termination, "Verify termination"); diff --git a/src/test/test_verify_type_domain.cpp b/src/test/test_verify_type_domain.cpp new file mode 100644 index 000000000..cadc10f24 --- /dev/null +++ b/src/test/test_verify_type_domain.cpp @@ -0,0 +1,577 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT +#include +#include +#include "ebpf_verifier.hpp" + +#define FAIL_LOAD_ELF(dirname, filename, sectionname) \ + TEST_CASE("Try loading nonexisting program: " dirname "/" filename, "[elf]") { \ + try { \ + read_elf("ebpf-samples/" dirname "/" filename, sectionname, nullptr, &g_ebpf_platform_linux); \ + REQUIRE(false); \ + } catch (const std::runtime_error&) { \ + }\ + } + +// Some intentional failures +FAIL_LOAD_ELF("cilium", "not-found.o", "2/1") +FAIL_LOAD_ELF("cilium", "bpf_lxc.o", "not-found") +FAIL_LOAD_ELF("build", "badrelo.o", ".text") +FAIL_LOAD_ELF("invalid", "badsymsize.o", "xdp_redirect_map") + +#define FAIL_UNMARSHAL(dirname, filename, sectionname) \ + TEST_CASE("Try unmarshalling bad program: " dirname "/" filename, "[unmarshal]") { \ + auto raw_progs = read_elf("ebpf-samples/" dirname "/" filename, sectionname, nullptr, &g_ebpf_platform_linux); \ + REQUIRE(raw_progs.size() == 1); \ + raw_program raw_prog = raw_progs.back(); \ + std::variant prog_or_error = unmarshal(raw_prog); \ + REQUIRE(std::holds_alternative(prog_or_error)); \ + } + +// Some intentional unmarshal failures +FAIL_UNMARSHAL("build", "wronghelper.o", "xdp") +FAIL_UNMARSHAL("invalid", "invalid-lddw.o", ".text") + +#define VERIFY_SECTION(dirname, filename, sectionname, options, pass) \ + do { \ + auto raw_progs = read_elf("ebpf-samples/" dirname "/" filename, sectionname, nullptr, &g_ebpf_platform_linux); \ + REQUIRE(raw_progs.size() == 1); \ + raw_program raw_prog = raw_progs.back(); \ + std::variant prog_or_error = unmarshal(raw_prog); \ + REQUIRE(std::holds_alternative(prog_or_error)); \ + auto& prog = std::get(prog_or_error); \ + crab_results res = ebpf_verify_program(std::cout, prog, raw_prog.info, options, nullptr); \ + if (pass) \ + REQUIRE(res.pass_verify()); \ + else \ + REQUIRE(!res.pass_verify()); \ + } while (0) + +#define TEST_SECTION(project, filename, section) \ + TEST_CASE("./check ebpf-samples/" project "/" filename " " section, "[verify][samples][" project "]") { \ + ebpf_verifier_options_t options = ebpf_verifier_default_options; \ + options.abstract_domain = abstract_domain_kind::TYPE_DOMAIN; \ + VERIFY_SECTION(project, filename, section, &options, true); \ + } + +#define TEST_SECTION_REJECT(project, filename, section) \ + TEST_CASE("./check ebpf-samples/" project "/" filename " " section, "[verify][samples][" project "]") { \ + ebpf_verifier_options_t options = ebpf_verifier_default_options; \ + options.abstract_domain = abstract_domain_kind::TYPE_DOMAIN; \ + VERIFY_SECTION(project, filename, section, &options, false); \ + } + +#define TEST_SECTION_REJECT_IF_STRICT(project, filename, section) \ + TEST_CASE("./check ebpf-samples/" project "/" filename " " section, "[verify][samples][" project "]") { \ + ebpf_verifier_options_t options = ebpf_verifier_default_options; \ + options.abstract_domain = abstract_domain_kind::TYPE_DOMAIN; \ + VERIFY_SECTION(project, filename, section, &options, true); \ + options.strict = true; \ + VERIFY_SECTION(project, filename, section, &options, false); \ + } + +#define TEST_SECTION_FAIL(project, filename, section) \ + TEST_CASE("expect failure ebpf-samples/" project "/" filename " " section, "[!shouldfail][verify][samples][" project "]") { \ + ebpf_verifier_options_t options = ebpf_verifier_default_options; \ + options.abstract_domain = abstract_domain_kind::TYPE_DOMAIN; \ + VERIFY_SECTION(project, filename, section, &options, true); \ + } + +#define TEST_SECTION_REJECT_FAIL(project, filename, section) \ + TEST_CASE("expect failure ebpf-samples/" project "/" filename " " section, "[!shouldfail][verify][samples][" project "]") { \ + ebpf_verifier_options_t options = ebpf_verifier_default_options; \ + options.abstract_domain = abstract_domain_kind::TYPE_DOMAIN; \ + VERIFY_SECTION(project, filename, section, &options, false); \ + } + +TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "1/0xdc06") +TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/1") +TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/3") +TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/4") +TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/5") +TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/6") +TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/7") +TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/10") +TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "from-container") + +TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "1/0x1010") +TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "2/1") +TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "2/2") +TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "2/3") +TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "2/4") +TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "2/5") +TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "2/6") +TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "2/7") +TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "from-container") + +TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "1/0x1010") +TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "2/1") +TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "2/2") +TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "2/3") +TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "2/4") +TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "2/5") +TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "2/6") +TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "2/7") +TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "from-container") + +TEST_SECTION("bpf_cilium_test", "bpf_netdev.o", "2/1") +TEST_SECTION("bpf_cilium_test", "bpf_netdev.o", "2/2") +TEST_SECTION("bpf_cilium_test", "bpf_netdev.o", "2/3") +TEST_SECTION("bpf_cilium_test", "bpf_netdev.o", "2/4") +TEST_SECTION("bpf_cilium_test", "bpf_netdev.o", "2/5") +TEST_SECTION("bpf_cilium_test", "bpf_netdev.o", "2/7") +TEST_SECTION("bpf_cilium_test", "bpf_netdev.o", "from-netdev") + +TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "2/1") +TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "2/2") +TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "2/3") +TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "2/4") +TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "2/5") +TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "2/7") +TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "3/2") +TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "from-overlay") + +TEST_SECTION("bpf_cilium_test", "bpf_lb-DLB_L3.o", "2/1") +TEST_SECTION("bpf_cilium_test", "bpf_lb-DLB_L3.o", "2/2") +TEST_SECTION("bpf_cilium_test", "bpf_lb-DLB_L3.o", "from-netdev") + +TEST_SECTION("bpf_cilium_test", "bpf_lb-DLB_L4.o", "2/1") +TEST_SECTION("bpf_cilium_test", "bpf_lb-DLB_L4.o", "2/2") +TEST_SECTION("bpf_cilium_test", "bpf_lb-DLB_L4.o", "from-netdev") + +TEST_SECTION("bpf_cilium_test", "bpf_lb-DUNKNOWN.o", "2/1") +TEST_SECTION("bpf_cilium_test", "bpf_lb-DUNKNOWN.o", "2/2") +TEST_SECTION("bpf_cilium_test", "bpf_lb-DUNKNOWN.o", "from-netdev") + +TEST_SECTION("cilium", "bpf_lb.o", "2/1") +TEST_SECTION("cilium", "bpf_lb.o", "from-netdev") + +TEST_SECTION("cilium", "bpf_lxc.o", "1/0x1010") +TEST_SECTION("cilium", "bpf_lxc.o", "2/1") +TEST_SECTION("cilium", "bpf_lxc.o", "2/3") +TEST_SECTION("cilium", "bpf_lxc.o", "2/4") +TEST_SECTION("cilium", "bpf_lxc.o", "2/5") +TEST_SECTION("cilium", "bpf_lxc.o", "2/6") +TEST_SECTION("cilium", "bpf_lxc.o", "2/7") +TEST_SECTION("cilium", "bpf_lxc.o", "2/8") +TEST_SECTION("cilium", "bpf_lxc.o", "2/9") +TEST_SECTION("cilium", "bpf_lxc.o", "2/10") +TEST_SECTION("cilium", "bpf_lxc.o", "2/11") +TEST_SECTION("cilium", "bpf_lxc.o", "2/12") +TEST_SECTION("cilium", "bpf_lxc.o", "from-container") + +TEST_SECTION("cilium", "bpf_netdev.o", "2/1") +TEST_SECTION("cilium", "bpf_netdev.o", "2/3") +TEST_SECTION("cilium", "bpf_netdev.o", "2/4") +TEST_SECTION("cilium", "bpf_netdev.o", "2/5") +TEST_SECTION("cilium", "bpf_netdev.o", "2/7") +TEST_SECTION("cilium", "bpf_netdev.o", "from-netdev") + +TEST_SECTION("cilium", "bpf_overlay.o", "2/1") +TEST_SECTION("cilium", "bpf_overlay.o", "2/3") +TEST_SECTION("cilium", "bpf_overlay.o", "2/4") +TEST_SECTION("cilium", "bpf_overlay.o", "2/5") +TEST_SECTION("cilium", "bpf_overlay.o", "2/7") +TEST_SECTION("cilium", "bpf_overlay.o", "from-overlay") + +TEST_SECTION("cilium", "bpf_xdp.o", "from-netdev") + +TEST_SECTION("cilium", "bpf_xdp_dsr_linux_v1_1.o", "from-netdev") +TEST_SECTION("cilium", "bpf_xdp_dsr_linux.o", "2/1") +TEST_SECTION("cilium", "bpf_xdp_dsr_linux.o", "from-netdev") + +TEST_SECTION("cilium", "bpf_xdp_snat_linux.o", "2/1") +TEST_SECTION("cilium", "bpf_xdp_snat_linux.o", "from-netdev") + +TEST_SECTION("linux", "cpustat_kern.o", "tracepoint/power/cpu_frequency") +TEST_SECTION("linux", "cpustat_kern.o", "tracepoint/power/cpu_idle") +TEST_SECTION("linux", "lathist_kern.o", "kprobe/trace_preempt_off") +TEST_SECTION("linux", "lathist_kern.o", "kprobe/trace_preempt_on") +TEST_SECTION("linux", "lwt_len_hist_kern.o", "len_hist") +TEST_SECTION("linux", "map_perf_test_kern.o", "kprobe/sys_getegid") +TEST_SECTION("linux", "map_perf_test_kern.o", "kprobe/sys_geteuid") +TEST_SECTION("linux", "map_perf_test_kern.o", "kprobe/sys_getgid") +TEST_SECTION("linux", "map_perf_test_kern.o", "kprobe/sys_getpgid") +TEST_SECTION("linux", "map_perf_test_kern.o", "kprobe/sys_getppid") +TEST_SECTION("linux", "map_perf_test_kern.o", "kprobe/sys_gettid") +TEST_SECTION("linux", "map_perf_test_kern.o", "kprobe/sys_getuid") +TEST_SECTION("linux", "offwaketime_kern.o", "kprobe/try_to_wake_up") +TEST_SECTION("linux", "offwaketime_kern.o", "tracepoint/sched/sched_switch") +TEST_SECTION("linux", "sampleip_kern.o", "perf_event") +TEST_SECTION("linux", "sock_flags_kern.o", "cgroup/sock1") +TEST_SECTION("linux", "sock_flags_kern.o", "cgroup/sock2") +TEST_SECTION("linux", "sockex1_kern.o", "socket1") +TEST_SECTION("linux", "sockex2_kern.o", "socket2") +TEST_SECTION("linux", "sockex3_kern.o", "socket/3") +TEST_SECTION("linux", "sockex3_kern.o", "socket/4") +TEST_SECTION("linux", "sockex3_kern.o", "socket/1") +TEST_SECTION("linux", "sockex3_kern.o", "socket/2") +TEST_SECTION("linux", "sockex3_kern.o", "socket/0") +TEST_SECTION("linux", "spintest_kern.o", "kprobe/__htab_percpu_map_update_elem") +TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_lock") +TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_lock_bh") +TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_lock_irq") +TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_lock_irqsave") +TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_trylock_bh") +TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_trylock") +TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_unlock") +TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_unlock_bh") +TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_unlock_irqrestore") +TEST_SECTION("linux", "spintest_kern.o", "kprobe/htab_map_alloc") +TEST_SECTION("linux", "spintest_kern.o", "kprobe/htab_map_update_elem") +TEST_SECTION("linux", "spintest_kern.o", "kprobe/mutex_spin_on_owner") +TEST_SECTION("linux", "spintest_kern.o", "kprobe/rwsem_spin_on_owner") +TEST_SECTION("linux", "spintest_kern.o", "kprobe/spin_lock") +TEST_SECTION("linux", "spintest_kern.o", "kprobe/spin_unlock") +TEST_SECTION("linux", "spintest_kern.o", "kprobe/spin_unlock_irqrestore") +TEST_SECTION("linux", "syscall_tp_kern.o", "tracepoint/syscalls/sys_enter_open") +TEST_SECTION("linux", "syscall_tp_kern.o", "tracepoint/syscalls/sys_exit_open") +TEST_SECTION("linux", "task_fd_query_kern.o", "kprobe/blk_start_request") +TEST_SECTION("linux", "task_fd_query_kern.o", "kretprobe/blk_account_io_completion") +TEST_SECTION("linux", "tc_l2_redirect_kern.o", "drop_non_tun_vip") +TEST_SECTION("linux", "tc_l2_redirect_kern.o", "l2_to_ip6tun_ingress_redirect") +TEST_SECTION("linux", "tc_l2_redirect_kern.o", "l2_to_iptun_ingress_forward") +TEST_SECTION("linux", "tc_l2_redirect_kern.o", "l2_to_iptun_ingress_redirect") +TEST_SECTION("linux", "tcp_basertt_kern.o", "sockops") +TEST_SECTION("linux", "tcp_bufs_kern.o", "sockops") +TEST_SECTION("linux", "tcp_cong_kern.o", "sockops") +TEST_SECTION("linux", "tcp_iw_kern.o", "sockops") +TEST_SECTION("linux", "tcbpf1_kern.o", "classifier") +TEST_SECTION("linux", "tcbpf1_kern.o", "clone_redirect_recv") +TEST_SECTION("linux", "tcbpf1_kern.o", "clone_redirect_xmit") +TEST_SECTION("linux", "tcbpf1_kern.o", "redirect_recv") +TEST_SECTION("linux", "tcbpf1_kern.o", "redirect_xmit") +TEST_SECTION("linux", "tcp_clamp_kern.o", "sockops") +TEST_SECTION("linux", "tcp_rwnd_kern.o", "sockops") +TEST_SECTION("linux", "tcp_synrto_kern.o", "sockops") +TEST_SECTION("linux", "test_cgrp2_tc_kern.o", "filter") +TEST_SECTION("linux", "test_current_task_under_cgroup_kern.o", "kprobe/sys_sync") +TEST_SECTION("linux", "test_overhead_kprobe_kern.o", "kprobe/__set_task_comm") +TEST_SECTION("linux", "test_overhead_kprobe_kern.o", "kprobe/urandom_read") +TEST_SECTION("linux", "test_overhead_raw_tp_kern.o", "raw_tracepoint/task_rename") +TEST_SECTION("linux", "test_overhead_raw_tp_kern.o", "raw_tracepoint/urandom_read") +TEST_SECTION("linux", "test_overhead_tp_kern.o", "tracepoint/random/urandom_read") +TEST_SECTION("linux", "test_overhead_tp_kern.o", "tracepoint/task/task_rename") +TEST_SECTION("linux", "test_probe_write_user_kern.o", "kprobe/sys_connect") +TEST_SECTION("linux", "trace_event_kern.o", "perf_event") +TEST_SECTION("linux", "trace_output_kern.o", "kprobe/sys_write") +TEST_SECTION("linux", "tracex1_kern.o", "kprobe/__netif_receive_skb_core") +TEST_SECTION("linux", "tracex2_kern.o", "kprobe/kfree_skb") +TEST_SECTION("linux", "tracex2_kern.o", "kprobe/sys_write") +TEST_SECTION("linux", "tracex3_kern.o", "kprobe/blk_account_io_completion") +TEST_SECTION("linux", "tracex3_kern.o", "kprobe/blk_start_request") +TEST_SECTION("linux", "tracex4_kern.o", "kprobe/kmem_cache_free") +TEST_SECTION("linux", "tracex4_kern.o", "kretprobe/kmem_cache_alloc_node") +TEST_SECTION("linux", "tracex5_kern.o", "kprobe/__seccomp_filter") +TEST_SECTION("linux", "tracex5_kern.o", "kprobe/0") +TEST_SECTION("linux", "tracex5_kern.o", "kprobe/1") +TEST_SECTION("linux", "tracex5_kern.o", "kprobe/9") +TEST_SECTION("linux", "tracex6_kern.o", "kprobe/htab_map_get_next_key") +TEST_SECTION("linux", "tracex6_kern.o", "kprobe/htab_map_lookup_elem") +TEST_SECTION("linux", "tracex7_kern.o", "kprobe/open_ctree") +TEST_SECTION("linux", "xdp_adjust_tail_kern.o", "xdp_icmp") +TEST_SECTION("linux", "xdp_fwd_kern.o", "xdp_fwd") +TEST_SECTION("linux", "xdp_fwd_kern.o", "xdp_fwd_direct") +TEST_SECTION("linux", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_cpumap_enqueue") +TEST_SECTION("linux", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_cpumap_kthread") +TEST_SECTION("linux", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_devmap_xmit") +TEST_SECTION("linux", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_exception") +TEST_SECTION("linux", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_redirect") +TEST_SECTION("linux", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_redirect_err") +TEST_SECTION("linux", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_redirect_map") +TEST_SECTION("linux", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_redirect_map_err") +TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "xdp_cpu_map0") +TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "xdp_cpu_map1_touch_data") +TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "xdp_cpu_map2_round_robin") +TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "xdp_cpu_map3_proto_separate") +TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "xdp_cpu_map4_ddos_filter_pktgen") +TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "xdp_cpu_map5_lb_hash_ip_pairs") +TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_cpumap_enqueue") +TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_cpumap_kthread") +TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_exception") +TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_redirect_err") +TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_redirect_map_err") +TEST_SECTION("linux", "xdp_redirect_kern.o", "xdp_redirect") +TEST_SECTION("linux", "xdp_redirect_kern.o", "xdp_redirect_dummy") +TEST_SECTION("linux", "xdp_redirect_map_kern.o", "xdp_redirect_dummy") +TEST_SECTION("linux", "xdp_redirect_map_kern.o", "xdp_redirect_map") +TEST_SECTION("linux", "xdp_router_ipv4_kern.o", "xdp_router_ipv4") +TEST_SECTION("linux", "xdp_rxq_info_kern.o", "xdp_prog0") +TEST_SECTION("linux", "xdp_sample_pkts_kern.o", "xdp_sample") +TEST_SECTION("linux", "xdp_tx_iptunnel_kern.o", "xdp_tx_iptunnel") +TEST_SECTION("linux", "xdp1_kern.o", "xdp1") +TEST_SECTION("linux", "xdp2_kern.o", "xdp1") +TEST_SECTION("linux", "xdp2skb_meta_kern.o", "tc_mark") +TEST_SECTION("linux", "xdp2skb_meta_kern.o", "xdp_mark") +TEST_SECTION("linux", "xdpsock_kern.o", "xdp_sock") +// Finally passes; still requires double-check +TEST_SECTION("linux", "map_perf_test_kern.o", "kprobe/sys_connect") + +TEST_SECTION("prototype-kernel", "napi_monitor_kern.o", "tracepoint/irq/softirq_entry") +TEST_SECTION("prototype-kernel", "napi_monitor_kern.o", "tracepoint/irq/softirq_exit") +TEST_SECTION("prototype-kernel", "napi_monitor_kern.o", "tracepoint/irq/softirq_raise") +TEST_SECTION("prototype-kernel", "napi_monitor_kern.o", "tracepoint/napi/napi_poll") +TEST_SECTION("prototype-kernel", "tc_bench01_redirect_kern.o", "ingress_redirect") +TEST_SECTION("prototype-kernel", "xdp_bench01_mem_access_cost_kern.o", "xdp_bench01") +TEST_SECTION("prototype-kernel", "xdp_bench02_drop_pattern_kern.o", "xdp_bench02") +TEST_SECTION("prototype-kernel", "xdp_ddos01_blacklist_kern.o", "xdp_prog") +TEST_SECTION("prototype-kernel", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_redirect") +TEST_SECTION("prototype-kernel", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_redirect_err") +TEST_SECTION("prototype-kernel", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_redirect_map_err") +TEST_SECTION("prototype-kernel", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_redirect_map") +TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "xdp_cpu_map0") +TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "xdp_cpu_map2_round_robin") +TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_cpumap_enqueue") +TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_cpumap_kthread") +TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_exception") +TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_redirect_err") +TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_redirect_map_err") +TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "xdp_cpu_map1_touch_data") +TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "xdp_cpu_map3_proto_separate") +TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "xdp_cpu_map4_ddos_filter_pktgen") +TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "xdp_cpu_map5_ip_l3_flow_hash") +TEST_SECTION("prototype-kernel", "xdp_redirect_err_kern.o", "xdp_redirect_dummy") +TEST_SECTION("prototype-kernel", "xdp_redirect_err_kern.o", "xdp_redirect_map") +TEST_SECTION("prototype-kernel", "xdp_redirect_err_kern.o", "xdp_redirect_map_rr") +TEST_SECTION("prototype-kernel", "xdp_tcpdump_kern.o", "xdp_tcpdump_to_perf_ring") +TEST_SECTION("prototype-kernel", "xdp_ttl_kern.o", "xdp_ttl") +TEST_SECTION("prototype-kernel", "xdp_vlan01_kern.o", "tc_vlan_push") +TEST_SECTION("prototype-kernel", "xdp_vlan01_kern.o", "xdp_drop_vlan_4011") +TEST_SECTION("prototype-kernel", "xdp_vlan01_kern.o", "xdp_vlan_change") +TEST_SECTION("prototype-kernel", "xdp_vlan01_kern.o", "xdp_vlan_remove_outer") +TEST_SECTION("prototype-kernel", "xdp_vlan01_kern.o", "xdp_vlan_remove_outer2") + +TEST_SECTION("ovs", "datapath.o", "tail-0") +TEST_SECTION("ovs", "datapath.o", "tail-1") +TEST_SECTION("ovs", "datapath.o", "tail-2") +TEST_SECTION("ovs", "datapath.o", "tail-3") +TEST_SECTION("ovs", "datapath.o", "tail-4") +TEST_SECTION("ovs", "datapath.o", "tail-5") +TEST_SECTION("ovs", "datapath.o", "tail-7") +TEST_SECTION("ovs", "datapath.o", "tail-8") +TEST_SECTION("ovs", "datapath.o", "tail-11") +TEST_SECTION("ovs", "datapath.o", "tail-12") +TEST_SECTION("ovs", "datapath.o", "tail-13") +TEST_SECTION("ovs", "datapath.o", "tail-32") +TEST_SECTION("ovs", "datapath.o", "tail-33") +TEST_SECTION("ovs", "datapath.o", "tail-35") +TEST_SECTION("ovs", "datapath.o", "af_xdp") +TEST_SECTION("ovs", "datapath.o", "downcall") +TEST_SECTION("ovs", "datapath.o", "egress") +TEST_SECTION("ovs", "datapath.o", "ingress") +TEST_SECTION("ovs", "datapath.o", "xdp") + +TEST_SECTION("suricata", "bypass_filter.o", "filter") +TEST_SECTION("suricata", "lb.o", "loadbalancer") +TEST_SECTION("suricata", "filter.o", "filter") +TEST_SECTION("suricata", "vlan_filter.o", "filter") +TEST_SECTION("suricata", "xdp_filter.o", "xdp") + +TEST_SECTION("falco", "probe.o", "raw_tracepoint/filler/sys_accept4_e") +TEST_SECTION("falco", "probe.o", "raw_tracepoint/filler/sys_empty") +TEST_SECTION("falco", "probe.o", "raw_tracepoint/filler/sys_pread64_e") +TEST_SECTION("falco", "probe.o", "raw_tracepoint/filler/sys_preadv64_e") +TEST_SECTION("falco", "probe.o", "raw_tracepoint/filler/sys_pwrite64_e") +TEST_SECTION("falco", "probe.o", "raw_tracepoint/filler/sys_single_x") +TEST_SECTION("falco", "probe.o", "raw_tracepoint/filler/sys_sysdigevent_e") +TEST_SECTION("falco", "probe.o", "raw_tracepoint/filler/terminate_filler") +TEST_SECTION("falco", "probe.o", "raw_tracepoint/page_fault_kernel") +TEST_SECTION("falco", "probe.o", "raw_tracepoint/page_fault_user") +TEST_SECTION("falco", "probe.o", "raw_tracepoint/sched_switch") +TEST_SECTION("falco", "probe.o", "raw_tracepoint/signal_deliver") + +// Test some programs that should pass verification except when the strict flag is set. +TEST_SECTION_REJECT_IF_STRICT("build", "mapoverflow.o", ".text") +TEST_SECTION_REJECT_IF_STRICT("build", "mapunderflow.o", ".text") + +/* + * These programs contain "call -1" instruction and cannot be verified: +TEST_SECTION("raw_tracepoint/filler/sys_access_e") +TEST_SECTION("raw_tracepoint/filler/sys_bpf_x") +TEST_SECTION("raw_tracepoint/filler/sys_brk_munmap_mmap_x") +TEST_SECTION("raw_tracepoint/filler/sys_eventfd_e") +TEST_SECTION("raw_tracepoint/filler/sys_execve_e") +TEST_SECTION("raw_tracepoint/filler/sys_generic") +TEST_SECTION("raw_tracepoint/filler/sys_getrlimit_setrlimit_e") +TEST_SECTION("raw_tracepoint/filler/sys_getrlimit_setrlrimit_x") +TEST_SECTION("raw_tracepoint/filler/sys_mount_e") +TEST_SECTION("raw_tracepoint/filler/sys_nanosleep_e") +TEST_SECTION("raw_tracepoint/filler/sys_pagefault_e") +TEST_SECTION("raw_tracepoint/filler/sys_procexit_e") +TEST_SECTION("raw_tracepoint/filler/sys_single") +TEST_SECTION("raw_tracepoint/filler/sys_unshare_e") +TEST_SECTION("raw_tracepoint/sched_process_exit") +TEST_SECTION("raw_tracepoint/filler/sys_chmod_x") +TEST_SECTION("raw_tracepoint/filler/sys_fchmod_x") +TEST_SECTION("raw_tracepoint/filler/sys_fcntl_e") +TEST_SECTION("raw_tracepoint/filler/sys_flock_e") +TEST_SECTION("raw_tracepoint/filler/sys_poll_x") +TEST_SECTION("raw_tracepoint/filler/sys_prlimit_e") +TEST_SECTION("raw_tracepoint/filler/sys_prlimit_x") +TEST_SECTION("raw_tracepoint/filler/sys_ptrace_e") +TEST_SECTION("raw_tracepoint/filler/sys_quotactl_e") +TEST_SECTION("raw_tracepoint/filler/sys_semop_x") +TEST_SECTION("raw_tracepoint/filler/sys_send_e") +TEST_SECTION("raw_tracepoint/filler/sys_sendfile_x") +TEST_SECTION("raw_tracepoint/filler/sys_setns_e") +TEST_SECTION("raw_tracepoint/filler/sys_shutdown_e") +TEST_SECTION("raw_tracepoint/filler/sys_fchmodat_x") +TEST_SECTION("raw_tracepoint/filler/sys_futex_e") +TEST_SECTION("raw_tracepoint/filler/sys_lseek_e") +TEST_SECTION("raw_tracepoint/filler/sys_mkdirat_x") +TEST_SECTION("raw_tracepoint/filler/sys_poll_e") +TEST_SECTION("raw_tracepoint/filler/sys_ptrace_x") +TEST_SECTION("raw_tracepoint/filler/sys_quotactl_x") +TEST_SECTION("raw_tracepoint/filler/sys_semget_e") +TEST_SECTION("raw_tracepoint/filler/sys_signaldeliver_e") +TEST_SECTION("raw_tracepoint/filler/sys_symlinkat_x") +TEST_SECTION("raw_tracepoint/filler/sys_unlinkat_x") +TEST_SECTION("raw_tracepoint/filler/sys_writev_e") +TEST_SECTION("raw_tracepoint/filler/sys_llseek_e") +TEST_SECTION("raw_tracepoint/filler/sys_ppoll_e") +TEST_SECTION("raw_tracepoint/filler/sys_pwritev_e") +TEST_SECTION("raw_tracepoint/filler/sys_renameat_x") +TEST_SECTION("raw_tracepoint/filler/sys_semctl_e") +TEST_SECTION("raw_tracepoint/filler/sched_switch_e") +TEST_SECTION("raw_tracepoint/filler/sys_getsockopt_x") +TEST_SECTION("raw_tracepoint/filler/sys_linkat_x") +TEST_SECTION("raw_tracepoint/filler/sys_renameat2_x") +TEST_SECTION("raw_tracepoint/filler/sys_sendfile_e") +TEST_SECTION("raw_tracepoint/filler/sys_setsockopt_x") +TEST_SECTION("raw_tracepoint/filler/sys_getresuid_and_gid_x") +TEST_SECTION("raw_tracepoint/filler/sys_mmap_e") +TEST_SECTION("raw_tracepoint/filler/sys_socket_bind_x") +TEST_SECTION("raw_tracepoint/filler/sys_socket_x") +TEST_SECTION("raw_tracepoint/sys_enter") +TEST_SECTION("raw_tracepoint/sys_exit") +TEST_SECTION("raw_tracepoint/filler/sys_pipe_x") +TEST_SECTION("raw_tracepoint/filler/sys_socketpair_x") +TEST_SECTION("raw_tracepoint/filler/sys_creat_x") +TEST_SECTION("raw_tracepoint/filler/sys_open_x") +TEST_SECTION("raw_tracepoint/filler/sys_openat_x") +TEST_SECTION("raw_tracepoint/filler/sys_autofill") +TEST_SECTION("raw_tracepoint/filler/proc_startupdate") +TEST_SECTION("raw_tracepoint/filler/sys_recvmsg_x_2") +TEST_SECTION("raw_tracepoint/filler/sys_sendmsg_e") +TEST_SECTION("raw_tracepoint/filler/sys_connect_x") +TEST_SECTION("raw_tracepoint/filler/sys_sendto_e") +TEST_SECTION("raw_tracepoint/filler/sys_accept_x") +TEST_SECTION("raw_tracepoint/filler/sys_read_x") +TEST_SECTION("raw_tracepoint/filler/sys_recv_x") +TEST_SECTION("raw_tracepoint/filler/sys_recvmsg_x") +TEST_SECTION("raw_tracepoint/filler/sys_send_x") +TEST_SECTION("raw_tracepoint/filler/proc_startupdate_3") +TEST_SECTION("raw_tracepoint/filler/sys_readv_preadv_x") +TEST_SECTION("raw_tracepoint/filler/sys_write_x") +TEST_SECTION("raw_tracepoint/filler/sys_writev_pwritev_x") +TEST_SECTION("raw_tracepoint/filler/sys_sendmsg_x") +TEST_SECTION("raw_tracepoint/filler/proc_startupdate_2") +TEST_SECTION("raw_tracepoint/filler/sys_recvfrom_x") +*/ +TEST_SECTION("build", "byteswap.o", ".text") +TEST_SECTION("build", "stackok.o", ".text") +TEST_SECTION("build", "packet_start_ok.o", "xdp") +TEST_SECTION("build", "packet_access.o", "xdp") +TEST_SECTION("build", "tail_call.o", "xdp_prog") +TEST_SECTION("build", "map_in_map.o", ".text") +TEST_SECTION("build", "map_in_map_legacy.o", ".text") +TEST_SECTION("build", "twomaps.o", ".text"); +TEST_SECTION("build", "twostackvars.o", ".text"); +TEST_SECTION("build", "twotypes.o", ".text"); + +// Test some programs that ought to fail verification. +TEST_SECTION_REJECT("build", "badhelpercall.o", ".text") +TEST_SECTION_REJECT("build", "ctxoffset.o", "sockops") +TEST_SECTION_REJECT("build", "badmapptr.o", "test") +TEST_SECTION_REJECT("build", "exposeptr.o", ".text") +TEST_SECTION_REJECT("build", "exposeptr2.o", ".text") +TEST_SECTION_REJECT("build", "mapvalue-overrun.o", ".text") +TEST_SECTION_REJECT("build", "nullmapref.o", "test") +TEST_SECTION_REJECT("build", "packet_overflow.o", "xdp") +TEST_SECTION_REJECT("build", "packet_reallocate.o", "socket_filter") +TEST_SECTION_REJECT("build", "tail_call_bad.o", "xdp_prog") +TEST_SECTION_REJECT("build", "ringbuf_uninit.o", ".text"); + +// The following eBPF programs currently fail verification. +// If the verifier is later updated to accept them, these should +// be changed to TEST_SECTION(). + +// Unsupported: ebpf-function +TEST_SECTION_FAIL("prototype-kernel", "xdp_ddos01_blacklist_kern.o", ".text") + +// Unsupported: implications are lost in correlated branches +TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/7") + +// Failure: 166:168: Upper bound must be at most packet_size (valid_access(r4.offset, width=2) for read) +// This is the result of merging two branches, one with value 0 and another with value -22, +// then checking that the result is != 0. The minor issue is not handling the int32 comparison precisely enough. +// The bigger issue is that the convexity of the numerical domain means that precise handling would still get +// [-22, -1] which is not sufficient (at most -2 is needed) +TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/10") +TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/21") +TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/24") + +TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/15") + +TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/17") + +// Failure: trying to access r4 where r4.packet_offset=[0, 255] and packet_size=[54, 65534] +// Root cause: r5.value=[0, 65535] 209: w5 >>= 8; clears r5 instead of yielding [0, 255] +TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/18") +TEST_SECTION_FAIL("cilium", "bpf_xdp_snat_linux.o", "2/10") +TEST_SECTION_FAIL("cilium", "bpf_xdp_snat_linux.o", "2/18") + +TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/19") + +// Failure: 230: Upper bound must be at most packet_size (valid_access(r3.offset+32, width=8) for write) +// r3.packet_offset=[0, 82] and packet_size=[34, 65534] +// looks like a combination of misunderstanding the value passed to xdp_adjust_tail() +// which is "r7.value=[0, 82]; w7 -= r9;" where r9.value where "r7.value-r9.value<=48" +TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/20") + +TEST_SECTION_FAIL("cilium", "bpf_xdp_snat_linux.o", "2/7") +TEST_SECTION_FAIL("cilium", "bpf_xdp_snat_linux.o", "2/15") +TEST_SECTION_FAIL("cilium", "bpf_xdp_snat_linux.o", "2/17") +TEST_SECTION_FAIL("cilium", "bpf_xdp_snat_linux.o", "2/19") + +// Failure (&255): assert r5.type == number; w5 &= 255; +// fails since in one branch (77) r5 is a number but in another (92:93) it is a packet +TEST_SECTION_FAIL("cilium", "bpf_xdp_snat_linux.o", "2/24") +// Failure (&255): assert r3.type == number; w3 &= 255; +TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/16") +TEST_SECTION_FAIL("cilium", "bpf_xdp_snat_linux.o", "2/16") + +// False positive, unknown cause +TEST_SECTION_FAIL("linux", "test_map_in_map_kern.o", "kprobe/sys_connect") + +void test_analyze_thread(cfg_t* cfg, program_info* info, bool* res) { + *res = run_ebpf_analysis(std::cout, *cfg, *info, nullptr, nullptr); +} + +// Test multithreading +TEST_CASE("multithreading", "[verify][multithreading]") { + auto raw_progs1 = read_elf("ebpf-samples/bpf_cilium_test/bpf_netdev.o", "2/1", nullptr, &g_ebpf_platform_linux); + REQUIRE(raw_progs1.size() == 1); + raw_program raw_prog1 = raw_progs1.back(); + std::variant prog_or_error1 = unmarshal(raw_prog1); + REQUIRE(std::holds_alternative(prog_or_error1)); + auto& prog1 = std::get(prog_or_error1); + cfg_t cfg1 = prepare_cfg(prog1, raw_prog1.info, true); + + auto raw_progs2 = read_elf("ebpf-samples/bpf_cilium_test/bpf_netdev.o", "2/2", nullptr, &g_ebpf_platform_linux); + REQUIRE(raw_progs2.size() == 1); + raw_program raw_prog2 = raw_progs2.back(); + std::variant prog_or_error2 = unmarshal(raw_prog2); + REQUIRE(std::holds_alternative(prog_or_error2)); + auto& prog2 = std::get(prog_or_error2); + cfg_t cfg2 = prepare_cfg(prog2, raw_prog2.info, true); + + bool res1, res2; + std::thread a(test_analyze_thread, &cfg1, &raw_prog1.info, &res1); + std::thread b(test_analyze_thread, &cfg2, &raw_prog2.info, &res2); + a.join(); + b.join(); + + REQUIRE(res1); + REQUIRE(res2); +} From 838002fb59c658b788da2db73934d66a23014074 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Wed, 12 Jul 2023 04:21:37 -0400 Subject: [PATCH 100/373] Implementing all important transformers, assertion checks, join for all important types; Better printing and error reporting Only accessible domain is type domain, while region domain is not; Printing only allowed from type domain; Support for map_fd registers and storing/loading into/from stack; Implement all important transformers, assertion checks, and join related to region domain; Report errors similar to how ebpf domain reports them to help verify results, which is important to run tests; Add support for shared pointers as containing offsets and fixed related operations; Add support for keeping track of widths of pointers stored in stack, but not for register pointers Signed-off-by: Ameer Hamza --- src/crab/abstract_domain.cpp | 7 + src/crab/abstract_domain.hpp | 3 + src/crab/ebpf_domain.hpp | 1 + src/crab/interval.hpp | 4 +- src/crab/region_domain.cpp | 1149 ++++++++++++++++++---------------- src/crab/region_domain.hpp | 178 ++++-- src/crab/type_domain.cpp | 453 +++++++++++--- src/crab/type_domain.hpp | 33 +- src/crab_verifier.cpp | 45 +- src/main/check.cpp | 9 +- 10 files changed, 1154 insertions(+), 728 deletions(-) diff --git a/src/crab/abstract_domain.cpp b/src/crab/abstract_domain.cpp index cc20d4189..059e746c6 100644 --- a/src/crab/abstract_domain.cpp +++ b/src/crab/abstract_domain.cpp @@ -190,6 +190,11 @@ void abstract_domain_t::abstract_domain_model::set_require_check(check_r m_abs_val.set_require_check(f); } +template +std::vector abstract_domain_t::abstract_domain_model::get_errors() { + return m_abs_val.get_errors(); +} + abstract_domain_t::abstract_domain_t(std::unique_ptr concept_) : m_concept(std::move(concept_)) {} @@ -278,6 +283,8 @@ string_invariant abstract_domain_t::to_set() { return m_concept->to_set(); } void abstract_domain_t::set_require_check(check_require_func_t f) { m_concept->set_require_check(f); } +std::vector abstract_domain_t::get_errors() { return m_concept->get_errors(); } + std::ostream& operator<<(std::ostream& o, const abstract_domain_t& dom) { dom.write(o); return o; diff --git a/src/crab/abstract_domain.hpp b/src/crab/abstract_domain.hpp index 172d93f36..7b5f297ab 100644 --- a/src/crab/abstract_domain.hpp +++ b/src/crab/abstract_domain.hpp @@ -52,6 +52,7 @@ class abstract_domain_t { virtual void initialize_loop_counter(const label_t) = 0; virtual string_invariant to_set() = 0; virtual void set_require_check(check_require_func_t f) = 0; + virtual std::vector get_errors() = 0; }; // end class abstract_domain_concept template @@ -90,6 +91,7 @@ class abstract_domain_t { crab::bound_t get_loop_count_upper_bound() override; string_invariant to_set() override; void set_require_check(check_require_func_t f) override; + std::vector get_errors() override; }; // end class abstract_domain_model std::unique_ptr m_concept; @@ -132,6 +134,7 @@ class abstract_domain_t { void initialize_loop_counter(const label_t); string_invariant to_set(); void set_require_check(check_require_func_t f); + std::vector get_errors(); friend std::ostream& operator<<(std::ostream& o, const abstract_domain_t& dom); }; diff --git a/src/crab/ebpf_domain.hpp b/src/crab/ebpf_domain.hpp index 83966e936..6ace2e9a9 100644 --- a/src/crab/ebpf_domain.hpp +++ b/src/crab/ebpf_domain.hpp @@ -50,6 +50,7 @@ class ebpf_domain_t final { void set_require_check(std::function f); bound_t get_loop_count_upper_bound(); static ebpf_domain_t setup_entry(bool init_r1); + std::vector get_errors() { return std::vector(); } static ebpf_domain_t from_constraints(const std::set& constraints, bool setup_constraints); string_invariant to_set(); diff --git a/src/crab/interval.hpp b/src/crab/interval.hpp index 47878de00..972f4f8bf 100644 --- a/src/crab/interval.hpp +++ b/src/crab/interval.hpp @@ -232,6 +232,8 @@ class interval_t final { bound_t _ub; public: + interval_t() : _lb(number_t{0}), _ub(-1) {} + static interval_t top() { return interval_t(bound_t::minus_infinity(), bound_t::plus_infinity()); } static interval_t bottom() { return interval_t(); } @@ -239,8 +241,6 @@ class interval_t final { [[nodiscard]] std::optional finite_size() const { return (_ub - _lb).number(); } private: - interval_t() : _lb(number_t{0}), _ub(-1) {} - static number_t abs(const number_t& x) { return x < 0 ? -x : x; } static number_t max(const number_t& x, const number_t& y) { return x.operator<=(y) ? y : x; } diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index 7c3920e11..359503152 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -1,12 +1,12 @@ // Copyright (c) Prevail Verifier contributors. // SPDX-License-Identifier: MIT -#include - #include "crab/region_domain.hpp" using crab::___print___; using crab::ptr_t; +using crab::mapfd_t; +using crab::ptr_or_mapfd_t; using crab::ptr_with_off_t; using crab::ptr_no_off_t; using crab::ctx_t; @@ -14,9 +14,9 @@ using crab::global_region_env_t; using crab::reg_with_loc_t; using crab::live_registers_t; using crab::register_types_t; - -static std::string size(int w) { return std::string("u") + std::to_string(w * 8); } - +using crab::map_key_size_t; +using crab::map_value_size_t; +using crab::ptr_or_mapfd_cells_t; namespace std { template <> @@ -42,96 +42,71 @@ namespace std { } } }; - - template <> - struct equal_to { - constexpr bool operator()(const crab::ptr_with_off_t& p1, const crab::ptr_with_off_t& p2) const { - return (p1.get_region() == p2.get_region() && p1.get_offset() == p2.get_offset()); - } - }; - - template <> - struct equal_to { - constexpr bool operator()(const crab::ptr_no_off_t& p1, const crab::ptr_no_off_t& p2) const { - return (p1.get_region() == p2.get_region()); - } - }; */ -} - -static void print_ptr_type(const ptr_t& p) { - if (std::holds_alternative(p)) { - auto t = std::get(p); - std::cout << t; + static ptr_t get_ptr(const ptr_or_mapfd_t& t) { + return std::visit( overloaded + { + []( const ptr_with_off_t& x ){ return ptr_t{x};}, + []( const ptr_no_off_t& x ){ return ptr_t{x};}, + []( auto& ) { return ptr_t{};} + }, t + ); } - else { - auto t = std::get(p); - std::cout << t; - } -} - -static void print_type(register_t r, const ptr_t& p) { - std::cout << "r" << static_cast(r) << " : "; - print_ptr_type(p); } -static void print_annotated(Mem const& b, const ptr_t& p, std::ostream& os_) { - if (b.is_load) { - os_ << " "; - print_type(std::get(b.value).v, p); - os_ << " = "; - } - std::string sign = b.access.offset < 0 ? " - " : " + "; - int offset = std::abs(b.access.offset); - os_ << "*(" << size(b.access.width) << " *)"; - os_ << "(" << b.access.basereg << sign << offset << ")\n"; -} - -static void print_annotated(Call const& call, const ptr_t& p, std::ostream& os_) { - os_ << " "; - print_type(0, p); - os_ << " = " << call.name << ":" << call.func << "(...)\n"; -} - -static void print_annotated(Bin const& b, const ptr_t& p, std::ostream& os_) { - os_ << " "; - print_type(b.dst.v, p); - // add better checks as we add more support - if (std::holds_alternative(b.v)) { - if (b.op == Bin::Op::MOV) - os_ << " = r" << static_cast(std::get(b.v).v) << ";"; - else if (b.op == Bin::Op::ADD) - os_ << " += r" << static_cast(std::get(b.v).v) << ";"; - } - else { - if (b.op == Bin::Op::ADD) - os_ << " += " << static_cast(std::get(b.v).v) << ";"; - } -} namespace crab { -inline std::string get_reg_ptr(const region& r) { +inline std::string get_reg_ptr(const region_t& r) { switch (r) { - case region::T_CTX: + case region_t::T_CTX: return "ctx_p"; - case region::T_STACK: + case region_t::T_STACK: return "stack_p"; - case region::T_PACKET: + case region_t::T_PACKET: return "packet_p"; default: return "shared_p"; } } -inline std::ostream& operator<<(std::ostream& o, const region& t) { - o << static_cast::type>(t); +static bool same_region(const ptr_t& ptr1, const ptr_t& ptr2) { + return ((std::holds_alternative(ptr1) + && std::holds_alternative(ptr2)) + || (std::holds_alternative(ptr1) + && std::holds_alternative(ptr2))); +} + +static void print_ptr_type(const ptr_t& ptr) { + if (std::holds_alternative(ptr)) { + ptr_with_off_t ptr_with_off = std::get(ptr); + std::cout << ptr_with_off; + } + else { + ptr_no_off_t ptr_no_off = std::get(ptr); + std::cout << ptr_no_off; + } +} + +static void print_ptr_or_mapfd_type(const ptr_or_mapfd_t& ptr_or_mapfd) { + if (std::holds_alternative(ptr_or_mapfd)) { + std::cout << std::get(ptr_or_mapfd); + } + else { + auto ptr = get_ptr(ptr_or_mapfd); + print_ptr_type(ptr); + } +} + +inline std::ostream& operator<<(std::ostream& o, const region_t& t) { + o << static_cast::type>(t); return o; } bool operator==(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { - return (p1.get_region() == p2.get_region() && p1.get_offset() == p2.get_offset()); + return (p1.get_region() == p2.get_region() && p1.get_offset() == p2.get_offset() + && p1.get_region_size() == p2.get_region_size()); } bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { @@ -139,7 +114,9 @@ bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { } void ptr_with_off_t::write(std::ostream& o) const { - o << get_reg_ptr(m_r) << "<" << m_offset << ">"; + o << get_reg_ptr(m_r) << "<" << m_offset; + if (m_region_size.lb() >= number_t{0}) o << "," << m_region_size; + o << ">"; } std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p) { @@ -147,9 +124,17 @@ std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p) { return o; } -void ptr_with_off_t::set_offset(int off) { m_offset = off; } +interval_t ptr_with_off_t::get_region_size() const { return m_region_size; } -void ptr_with_off_t::set_region(region r) { m_r = r; } +void ptr_with_off_t::set_offset(interval_t off) { m_offset = off; } + +void ptr_with_off_t::set_region_size(interval_t region_sz) { m_region_size = region_sz; } + +void ptr_with_off_t::set_region(region_t r) { m_r = r; } + +ptr_with_off_t ptr_with_off_t::operator|(const ptr_with_off_t& other) const { + return ptr_with_off_t(m_r, m_offset | other.m_offset, m_region_size | other.m_region_size); +} bool operator==(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { return (p1.get_region() == p2.get_region()); @@ -168,7 +153,29 @@ std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p) { return o; } -void ptr_no_off_t::set_region(region r) { m_r = r; } +void ptr_no_off_t::set_region(region_t r) { m_r = r; } + +bool operator==(const mapfd_t& m1, const mapfd_t& m2) { + return (m1.get_value_type() == m2.get_value_type()); +} + +std::ostream& operator<<(std::ostream& o, const mapfd_t& m) { + m.write(o); + return o; +} + +bool mapfd_t::has_type_map_programs() const { + return (m_value_type == EbpfMapValueType::PROGRAM); +} + +void mapfd_t::write(std::ostream& o) const { + if (has_type_map_programs()) { + o << "map_fd_programs"; + } + else { + o << "map_fd"; + } +} void reg_with_loc_t::write(std::ostream& o) const { o << "r" << static_cast(m_reg) << "@" << m_loc->second << " in " << m_loc->first << " "; @@ -195,51 +202,29 @@ std::size_t reg_with_loc_t::hash() const { return seed; } -std::ostream& operator<<(std::ostream& o, const stack_t& st) { - o << "Stack: "; - if (st.is_bottom()) - o << "_|_\n"; - else { - o << "{"; - for (auto s : st.m_ptrs) { - o << s.first << ": "; - print_ptr_type(s.second); - o << ", "; - } - o << "}"; - } - return o; -} - -std::ostream& operator<<(std::ostream& o, const ctx_t& _ctx) { - - o << "type of context: " << (_ctx.m_packet_ptrs.empty() ? "top" : "") << "\n"; - for (const auto& it : _ctx.m_packet_ptrs) { - o << " stores at " << it.first << ": " << it.second << "\n"; - } - return o; -} - ctx_t::ctx_t(const ebpf_context_descriptor_t* desc) { - if (desc->data != -1) { - m_packet_ptrs[desc->data] = crab::ptr_no_off_t(crab::region::T_PACKET); + if (desc->data >= 0) { + m_packet_ptrs[desc->data] = crab::ptr_no_off_t(crab::region_t::T_PACKET); + } + if (desc->end >= 0) { + m_packet_ptrs[desc->end] = crab::ptr_no_off_t(crab::region_t::T_PACKET); } - if (desc->end != -1) { - m_packet_ptrs[desc->end] = crab::ptr_no_off_t(crab::region::T_PACKET); + if (desc->meta >= 0) { + m_packet_ptrs[desc->meta] = crab::ptr_no_off_t(crab::region_t::T_PACKET); } - if (desc->meta != -1) { - m_packet_ptrs[desc->meta] = crab::ptr_no_off_t(crab::region::T_PACKET); + if (desc->size >= 0) { + size = desc->size; } } -size_t ctx_t::size() const { - return m_packet_ptrs.size(); +int ctx_t::get_size() const { + return size; } -std::vector ctx_t::get_keys() const { - std::vector keys; - keys.reserve(size()); +std::vector ctx_t::get_keys() const { + std::vector keys; + keys.reserve(size); for (auto const&kv : m_packet_ptrs) { keys.push_back(kv.first); @@ -247,26 +232,12 @@ std::vector ctx_t::get_keys() const { return keys; } -std::optional ctx_t::find(int key) const { +std::optional ctx_t::find(uint64_t key) const { auto it = m_packet_ptrs.find(key); if (it == m_packet_ptrs.end()) return {}; return it->second; } - -std::ostream& operator<<(std::ostream& o, const register_types_t& typ) { - if (typ.is_bottom()) - o << "_|_\n"; - else { - for (const auto& v : *(typ.m_region_env)) { - o << v.first << ": "; - print_ptr_type(v.second); - o << "\n"; - } - } - return o; -} - register_types_t register_types_t::operator|(const register_types_t& other) const { if (is_bottom() || other.is_top()) { return other; @@ -274,28 +245,39 @@ register_types_t register_types_t::operator|(const register_types_t& other) cons return *this; } live_registers_t out_vars; + + // a hack to store region information at the start of a joined basic block + // in join, we do not know the label of the bb, hence we store the information + // at a bb that is not used anywhere else in the program, and later when we know + // the bb label, we can fix + location_t loc = location_t(std::make_pair(label_t(-2, -2), 0)); + for (size_t i = 0; i < m_cur_def.size(); i++) { if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; auto it1 = find(*(m_cur_def[i])); auto it2 = other.find(*(other.m_cur_def[i])); if (it1 && it2) { - ptr_t pt1 = it1.value(), pt2 = it2.value(); - if (pt1 == pt2) { + ptr_or_mapfd_t ptr_or_mapfd1 = it1.value(), ptr_or_mapfd2 = it2.value(); + auto reg = reg_with_loc_t((register_t)i, loc); + if (ptr_or_mapfd1 == ptr_or_mapfd2) { out_vars[i] = m_cur_def[i]; } - // TODO - //else if (std::holds_alternative(pt1) - // && std::holds_alternative(pt2)) { - // auto pt_with_off1 = std::get(pt1); - // auto pt_with_off2 = std::get(pt2); - // if (pt_with_off1.get_region() == pt_with_off2.get_region()) { - // //out_vars[i] = std::make_shared(ptr_or_mapfd1) + && !std::holds_alternative(ptr_or_mapfd2)) { + auto ptr1 = get_ptr(ptr_or_mapfd1); + auto ptr2 = get_ptr(ptr_or_mapfd2); + if (std::holds_alternative(ptr1) + && std::holds_alternative(ptr2)) { + ptr_with_off_t ptr_with_off1 = std::get(ptr1); + ptr_with_off_t ptr_with_off2 = std::get(ptr2); + if (ptr_with_off1.get_region() == ptr_with_off2.get_region()) { + out_vars[i] = std::make_shared(reg); + (*m_region_env)[reg] = std::move(ptr_with_off1 | ptr_with_off2); + } + } + } } } - return register_types_t(std::move(out_vars), m_region_env, false); } @@ -307,7 +289,6 @@ void register_types_t::operator-=(register_t var) { } void register_types_t::set_to_bottom() { - m_cur_def = live_registers_t{nullptr}; m_is_bottom = true; } @@ -327,34 +308,51 @@ bool register_types_t::is_top() const { return true; } -void register_types_t::insert(register_t reg, const reg_with_loc_t& reg_with_loc, const ptr_t& type) { +void register_types_t::insert(register_t reg, const reg_with_loc_t& reg_with_loc, + const ptr_or_mapfd_t& type) { (*m_region_env)[reg_with_loc] = type; m_cur_def[reg] = std::make_shared(reg_with_loc); } -std::optional register_types_t::find(reg_with_loc_t reg) const { +void register_types_t::print_all_register_types() const { + std::cout << "\tregion types: {\n"; + for (auto const& kv : *m_region_env) { + std::cout << "\t\t" << kv.first << " : "; + print_ptr_or_mapfd_type(kv.second); + std::cout << "\n"; + } + std::cout << "\t}\n"; +} + +std::optional register_types_t::find(reg_with_loc_t reg) const { auto it = m_region_env->find(reg); if (it == m_region_env->end()) return {}; return it->second; } -std::optional register_types_t::find(register_t key) const { +std::optional register_types_t::find(register_t key) const { if (m_cur_def[key] == nullptr) return {}; const reg_with_loc_t& reg = *(m_cur_def[key]); return find(reg); } -void register_types_t::print_types_at(location_t loc) const { +void register_types_t::adjust_bb_for_registers(location_t loc) { + location_t old_loc = location_t(std::make_pair(label_t(-2, -2), 0)); for (size_t i = 0; i < m_cur_def.size(); i++) { - auto reg_with_loc = reg_with_loc_t(i, loc); - auto it = find(reg_with_loc); - if (it) { - std::cout << " "; - print_type(i, it.value()); - std::cout << "\n"; + auto new_reg = reg_with_loc_t((register_t)i, loc); + auto old_reg = reg_with_loc_t((register_t)i, old_loc); + + auto it = find((register_t)i); + if (!it) continue; + + if (*m_cur_def[i] == old_reg) { + m_region_env->erase(old_reg); } + + m_cur_def[i] = std::make_shared(new_reg); + (*m_region_env)[new_reg] = it.value(); + } - std::cout << "\n"; } stack_t stack_t::operator|(const stack_t& other) const { @@ -363,21 +361,46 @@ stack_t stack_t::operator|(const stack_t& other) const { } else if (other.is_bottom() || is_top()) { return *this; } - offset_to_ptr_t out_ptrs; + ptr_or_mapfd_types_t out_ptrs; for (auto const&kv: m_ptrs) { - auto it = other.find(kv.first); - if (it && kv.second == it.value()) - out_ptrs.insert(kv); + auto maybe_ptr_or_mapfd_cells = other.find(kv.first); + if (maybe_ptr_or_mapfd_cells) { + auto ptr_or_mapfd_cells1 = kv.second; + auto ptr_or_mapfd_cells2 = maybe_ptr_or_mapfd_cells.value(); + auto ptr_or_mapfd1 = ptr_or_mapfd_cells1.first; + auto ptr_or_mapfd2 = ptr_or_mapfd_cells2.first; + int width1 = ptr_or_mapfd_cells1.second; + int width2 = ptr_or_mapfd_cells2.second; + int width_joined = std::max(width1, width2); + if (ptr_or_mapfd1 == ptr_or_mapfd2) { + out_ptrs[kv.first] = std::make_pair(ptr_or_mapfd1, width_joined); + } + else if (std::holds_alternative(ptr_or_mapfd1) && + std::holds_alternative(ptr_or_mapfd2)) { + auto ptr_with_off1 = std::get(ptr_or_mapfd1); + auto ptr_with_off2 = std::get(ptr_or_mapfd2); + if (ptr_with_off1.get_region() == ptr_with_off2.get_region()) { + out_ptrs[kv.first] + = std::make_pair(ptr_with_off1 | ptr_with_off2, width_joined); + } + } + } } return stack_t(std::move(out_ptrs), false); } -void stack_t::operator-=(int key) { +void stack_t::operator-=(uint64_t key) { auto it = find(key); if (it) m_ptrs.erase(key); } +void stack_t::operator-=(const std::vector& keys) { + for (auto &key : keys) { + *this -= key; + } +} + void stack_t::set_to_bottom() { m_ptrs.clear(); m_is_bottom = true; @@ -400,16 +423,16 @@ bool stack_t::is_top() const { return m_ptrs.empty(); } -void stack_t::insert(int key, ptr_t value) { - m_ptrs[key] = value; +void stack_t::store(uint64_t key, ptr_or_mapfd_t value, int width) { + m_ptrs[key] = std::make_pair(value, width); } size_t stack_t::size() const { return m_ptrs.size(); } -std::vector stack_t::get_keys() const { - std::vector keys; +std::vector stack_t::get_keys() const { + std::vector keys; keys.reserve(size()); for (auto const&kv : m_ptrs) { @@ -419,15 +442,36 @@ std::vector stack_t::get_keys() const { } -std::optional stack_t::find(int key) const { +std::optional stack_t::find(uint64_t key) const { auto it = m_ptrs.find(key); if (it == m_ptrs.end()) return {}; return it->second; } +std::vector stack_t::find_overlapping_cells(uint64_t start, int width) const { + std::vector overlapping_cells; + auto it = m_ptrs.begin(); + while (it != m_ptrs.end() && it->first < start) { + it++; + } + if (it != m_ptrs.begin()) { + it--; + auto key = it->first; + auto width_key = it->second.second; + if (key < start && key+width_key > start) overlapping_cells.push_back(key); + } + + for (; it != m_ptrs.end(); it++) { + auto key = it->first; + if (key >= start && key < start+width) overlapping_cells.push_back(key); + if (key >= start+width) break; + } + return overlapping_cells; +} + } -std::optional region_domain_t::find_ptr_type(register_t reg) { +std::optional region_domain_t::find_ptr_or_mapfd_type(register_t reg) const { return m_registers.find(reg); } @@ -456,23 +500,27 @@ void region_domain_t::set_to_top() { m_registers.set_to_top(); } +std::optional region_domain_t::find_ptr_or_mapfd_at_loc(const reg_with_loc_t& reg) const { + return m_registers.find(reg); +} + size_t region_domain_t::ctx_size() const { - return m_ctx->size(); + return m_ctx->get_size(); } -std::vector region_domain_t::get_ctx_keys() const { +std::vector region_domain_t::get_ctx_keys() const { return m_ctx->get_keys(); } -std::vector region_domain_t::get_stack_keys() const { +std::vector region_domain_t::get_stack_keys() const { return m_stack.get_keys(); } -std::optional region_domain_t::find_in_ctx(int key) const { +std::optional region_domain_t::find_in_ctx(uint64_t key) const { return m_ctx->find(key); } -std::optional region_domain_t::find_in_stack(int key) const { +std::optional region_domain_t::find_in_stack(uint64_t key) const { return m_stack.find(key); } @@ -515,22 +563,20 @@ region_domain_t region_domain_t::operator|(region_domain_t&& other) const { } region_domain_t region_domain_t::operator&(const region_domain_t& abs) const { + /* WARNING: The operation is not implemented yet.*/ return abs; } region_domain_t region_domain_t::widen(const region_domain_t& abs, bool to_constants) { + /* WARNING: The operation is not implemented yet.*/ return abs; } region_domain_t region_domain_t::narrow(const region_domain_t& other) const { + /* WARNING: The operation is not implemented yet.*/ return other; } -void region_domain_t::write(std::ostream& os) const { - os << m_registers; - os << m_stack << "\n"; -} - crab::bound_t region_domain_t::get_loop_count_upper_bound() { // WARNING: Not implemented yet. return crab::bound_t{crab::number_t{0}}; @@ -544,92 +590,144 @@ string_invariant region_domain_t::to_set() { return string_invariant{}; } -void region_domain_t::operator()(const Undefined & u, location_t loc, int print) { - if (is_bottom()) return; - if (print > 0) - std::cout << " " << u << ";\n"; -} -void region_domain_t::operator()(const Un &u, location_t loc, int print) { - if (is_bottom()) return; - if (print > 0) - std::cout << " " << u << ";\n"; -} void region_domain_t::operator()(const LoadMapFd &u, location_t loc, int print) { - if (is_bottom()) return; - if (print > 0) { - std::cout << " " << u << ";\n"; - return; - } - m_registers -= u.dst.v; + auto reg = u.dst.v; + auto reg_with_loc = reg_with_loc_t(reg, loc); + const EbpfMapDescriptor& desc = global_program_info.get().platform->get_map_descriptor(u.mapfd); + const EbpfMapValueType& map_value_type = global_program_info.get().platform-> + get_map_type(desc.type).value_type; + map_key_size_t map_key_size = desc.key_size; + map_value_size_t map_value_size = desc.value_size; + auto type = mapfd_t(u.mapfd, map_value_type, map_key_size, map_value_size); + m_registers.insert(reg, reg_with_loc, type); } + void region_domain_t::operator()(const Call &u, location_t loc, int print) { - if (is_bottom()) return; + std::optional maybe_fd_reg{}; + for (ArgSingle param : u.singles) { + if (param.kind == ArgSingle::Kind::MAP_FD) maybe_fd_reg = param.reg; + break; + } register_t r0_reg{R0_RETURN_VALUE}; auto r0 = reg_with_loc_t(r0_reg, loc); - if (print > 0) { - if (u.is_map_lookup) { - auto it = m_registers.find(r0); - if (it) { - print_annotated(u, it.value(), std::cout); - } - } - else - std::cout << " " << u << ";\n"; - return; - } if (u.is_map_lookup) { - auto type = ptr_no_off_t(crab::region::T_SHARED); - m_registers.insert(r0_reg, r0, type); + if (!maybe_fd_reg) { + m_registers -= r0_reg; + return; + } + auto ptr_or_mapfd = m_registers.find(maybe_fd_reg->v); + if (!ptr_or_mapfd || !std::holds_alternative(ptr_or_mapfd.value())) { + m_registers -= r0_reg; + return; + } + auto mapfd = std::get(ptr_or_mapfd.value()); + auto map_desc = global_program_info.get().platform->get_map_descriptor(mapfd.get_mapfd()); + if (mapfd.get_value_type() == EbpfMapValueType::MAP) { + const EbpfMapDescriptor& inner_map_desc = global_program_info.get().platform-> + get_map_descriptor(map_desc.inner_map_fd); + const EbpfMapValueType& inner_map_value_type = global_program_info.get().platform-> + get_map_type(inner_map_desc.type).value_type; + map_key_size_t inner_map_key_size = inner_map_desc.key_size; + map_value_size_t inner_map_value_size = inner_map_desc.value_size; + auto type = mapfd_t(map_desc.inner_map_fd, inner_map_value_type, + inner_map_key_size, inner_map_value_size); + m_registers.insert(r0_reg, r0, type); + } + else { + auto type = ptr_with_off_t(crab::region_t::T_SHARED, interval_t{number_t{0}}, + interval_t{mapfd.get_value_size()}); + m_registers.insert(r0_reg, r0, type); + } } else { m_registers -= r0_reg; } } + void region_domain_t::operator()(const Callx &u, location_t loc, int print) { // WARNING: Not implemented yet. } + void region_domain_t::operator()(const IncrementLoopCounter &u, location_t loc, int print) { // WARNING: Not implemented yet. } + void region_domain_t::operator()(const Atomic &u, location_t loc, int print) { // WARNING: Not implemented yet. } -void region_domain_t::operator()(const Exit &u, location_t loc, int print) { - if (is_bottom()) return; - if (print > 0) - std::cout << " " << u << ";\n"; -} -void region_domain_t::operator()(const Jmp &u, location_t loc, int print) { - if (is_bottom()) return; - if (print > 0) - std::cout << " " << u << ";\n"; -} + void region_domain_t::operator()(const Packet & u, location_t loc, int print) { - if (is_bottom()) return; - if (print > 0) { - std::cout << " " << u << ";\n"; + m_registers -= register_t{R0_RETURN_VALUE}; +} + +void region_domain_t::operator()(const Addable& u, location_t loc, int print) { + + auto maybe_ptr_type1 = m_registers.find(u.ptr.v); + auto maybe_ptr_type2 = m_registers.find(u.num.v); + // a -> b <-> !a || b + if (!maybe_ptr_type1 || !maybe_ptr_type2) { return; } - m_registers -= register_t{0}; -} -void region_domain_t::operator()(const Assume &u, location_t loc, int print) { - if (is_bottom()) return; - if (print > 0) - std::cout << " " << u << ";\n"; + //std::cout << "type error: Addable assertion fail\n"; + m_errors.push_back("Addable assertion fail"); +} + +void region_domain_t::operator()(const ValidAccess& s, location_t loc, int print) { + bool is_comparison_check = s.width == (Value)Imm{0}; + if (std::holds_alternative(s.width)) return; + int width = std::get(s.width).v; + + auto maybe_ptr_or_mapfd_type = m_registers.find(s.reg.v); + if (maybe_ptr_or_mapfd_type) { + auto reg_ptr_or_mapfd_type = maybe_ptr_or_mapfd_type.value(); + if (std::holds_alternative(reg_ptr_or_mapfd_type)) { + auto reg_with_off_ptr_type = std::get(reg_ptr_or_mapfd_type); + auto offset = reg_with_off_ptr_type.get_offset(); + auto offset_to_check = offset+interval_t{s.offset}; + auto offset_lb = offset_to_check.lb(); + auto offset_plus_width_ub = offset_to_check.ub()+crab::bound_t{width}; + if (reg_with_off_ptr_type.get_region() == crab::region_t::T_STACK) { + if (crab::bound_t{STACK_BEGIN} <= offset_lb + && offset_plus_width_ub <= crab::bound_t{EBPF_STACK_SIZE}) + return; + } + else if (reg_with_off_ptr_type.get_region() == crab::region_t::T_CTX) { + if (crab::bound_t{CTX_BEGIN} <= offset_lb + && offset_plus_width_ub <= crab::bound_t{ctx_size()}) + return; + } + else { // shared + if (crab::bound_t{SHARED_BEGIN} <= offset_lb && + offset_plus_width_ub <= reg_with_off_ptr_type.get_region_size().lb()) return; + // TODO: check null access + //return; + } + } + else if (std::holds_alternative(reg_ptr_or_mapfd_type)) { + // We do not handle packet ptr access in region domain + return; + } + else { + // mapfd + if (is_comparison_check) return; + //std::cout << "type error: FDs cannot be dereferenced directly\n"; + m_errors.push_back("FDs cannot be dereferenced directly"); + } + //std::cout << "type error: valid access assert fail\n"; + m_errors.push_back("valid access assert fail"); + } } -void region_domain_t::operator()(const TypeConstraint& s, location_t loc, int print) { - check_type_constraint(s); -} +void region_domain_t::operator()(const ValidStore& u, location_t loc, int print) { -void region_domain_t::operator()(const Assert &u, location_t loc, int print) { - if (is_bottom()) return; - if (print > 0) { - std::cout << " " << u << ";\n"; + bool is_stack_p = is_stack_pointer(u.mem.v); + auto maybe_ptr_type2 = m_registers.find(u.val.v); + + if (is_stack_p || !maybe_ptr_type2) { return; } - //std::cout << "assert: " << u << "\n"; - std::visit([this, loc, print](const auto& v) { std::apply(*this, std::make_tuple(v, loc, print)); }, u.cst); + //std::cout << "type error: Valid store assertion fail\n"; + m_errors.push_back("Valid store assertion fail"); } region_domain_t region_domain_t::setup_entry() { @@ -642,8 +740,9 @@ region_domain_t region_domain_t::setup_entry() { auto r1 = reg_with_loc_t(R1_ARG, std::make_pair(label_t::entry, static_cast(0))); auto r10 = reg_with_loc_t(R10_STACK_POINTER, std::make_pair(label_t::entry, static_cast(0))); - typ.insert(R1_ARG, r1, ptr_with_off_t(crab::region::T_CTX, 0)); - typ.insert(R10_STACK_POINTER, r10, ptr_with_off_t(crab::region::T_STACK, 512)); + typ.insert(R1_ARG, r1, ptr_with_off_t(crab::region_t::T_CTX, interval_t{number_t{0}})); + typ.insert(R10_STACK_POINTER, r10, + ptr_with_off_t(crab::region_t::T_STACK, interval_t{number_t{512}})); region_domain_t inv(std::move(typ), crab::stack_t::top(), ctx); return inv; @@ -656,187 +755,184 @@ void region_domain_t::report_type_error(std::string s, location_t loc) { set_to_bottom(); } -void region_domain_t::check_type_constraint(const TypeConstraint& s) { - auto it = find_ptr_type(s.reg.v); +void region_domain_t::operator()(const TypeConstraint& s, location_t loc, int print) { + auto it = find_ptr_or_mapfd_type(s.reg.v); if (it) { - if (s.types == TypeGroup::pointer || s.types == TypeGroup::ptr_or_num - || s.types == TypeGroup::non_map_fd) return; - ptr_t p_type = it.value(); - if (std::holds_alternative(p_type)) { - ptr_with_off_t p_type_with_off = std::get(p_type); - if (p_type_with_off.get_region() == crab::region::T_CTX) { + // it is a pointer or mapfd + ptr_or_mapfd_t ptr_or_mapfd_type = it.value(); + if (std::holds_alternative(ptr_or_mapfd_type)) { + if (s.types == TypeGroup::non_map_fd) return; + if (s.types == TypeGroup::pointer || s.types == TypeGroup::ptr_or_num) return; + ptr_with_off_t ptr_with_off = std::get(ptr_or_mapfd_type); + if (ptr_with_off.get_region() == crab::region_t::T_CTX) { if (s.types == TypeGroup::ctx) return; } + else if (ptr_with_off.get_region() == crab::region_t::T_SHARED) { + if (s.types == TypeGroup::shared || s.types == TypeGroup::mem + || s.types == TypeGroup::mem_or_num) return; + } else { if (s.types == TypeGroup::stack || s.types == TypeGroup::mem - || s.types == TypeGroup::stack_or_packet || s.types == TypeGroup::mem_or_num) { + || s.types == TypeGroup::stack_or_packet + || s.types == TypeGroup::mem_or_num) { return; } } } + else if (std::holds_alternative(ptr_or_mapfd_type)) { + if (s.types == TypeGroup::non_map_fd) return; + if (s.types == TypeGroup::pointer || s.types == TypeGroup::ptr_or_num) return; + if (s.types == TypeGroup::packet || s.types == TypeGroup::mem + || s.types == TypeGroup::mem_or_num + || s.types == TypeGroup::stack_or_packet) return; + } else { - ptr_no_off_t p_type_no_off = std::get(p_type); - if (p_type_no_off.get_region() == crab::region::T_PACKET) { - if (s.types == TypeGroup::packet || s.types == TypeGroup::mem - || s.types == TypeGroup::mem_or_num) return; - } - else if (p_type_no_off.get_region() == crab::region::T_SHARED) { - if (s.types == TypeGroup::shared || s.types == TypeGroup::mem - || s.types == TypeGroup::mem_or_num) return; - } - else { - // we might have the case where we add an unknown number to stack or ctx's offset and we do not know the offset now - if (p_type_no_off.get_region() == crab::region::T_STACK) { - if (s.types == TypeGroup::stack || s.types == TypeGroup::stack_or_packet - || s.types == TypeGroup::mem || s.types == TypeGroup::mem_or_num) - return; - } - else { - if (s.types == TypeGroup::ctx) return; - } + auto map_fd = std::get(ptr_or_mapfd_type); + if (map_fd.has_type_map_programs()) { + if (s.types == TypeGroup::map_fd_programs) return; + } else { + if (s.types == TypeGroup::map_fd) return; } } } else { - // map_fd, non_map_fd, map_fd_programs, and numbers, all should come here - // right now, pass any such cases, add more strict checks later - // TODO + // if we don't know the type, we assume it is a number if (s.types == TypeGroup::number || s.types == TypeGroup::ptr_or_num - || s.types == TypeGroup::map_fd_programs || s.types == TypeGroup::non_map_fd - || s.types == TypeGroup::ptr_or_num || s.types == TypeGroup::mem_or_num - || s.types == TypeGroup::map_fd) + || s.types == TypeGroup::non_map_fd || s.types == TypeGroup::ptr_or_num + || s.types == TypeGroup::mem_or_num) return; } - std::cout << "type constraint assert fail: " << s << "\n"; - //exit(1); -} - -void region_domain_t::do_bin(const Bin& bin, std::shared_ptr src_const_value, location_t loc, int print) { - if (is_bottom()) return; - if (print > 0) { - if (print == 2) { - if ((std::holds_alternative(bin.v) && (bin.op == Bin::Op::MOV || bin.op == Bin::Op::ADD)) || - (std::holds_alternative(bin.v) && bin.op == Bin::Op::ADD)) { - auto reg_with_loc = reg_with_loc_t(bin.dst.v, loc); - auto it = m_registers.find(reg_with_loc); - if (it) { - print_annotated(bin, it.value(), std::cout); - std::cout << "\n"; - return; - } - } - } - std::cout << " " << bin << ";\n"; - return; + //std::cout << "type error: type constraint assert fail: " << s << "\n"; + m_errors.push_back("type constraint assert fail"); +} + +void region_domain_t::update_ptr_or_mapfd(ptr_or_mapfd_t&& ptr_or_mapfd, const interval_t&& change, + Bin::Op op, const reg_with_loc_t& reg_with_loc, uint8_t reg) { + if (std::holds_alternative(ptr_or_mapfd)) { + auto ptr_or_mapfd_with_off = std::get(ptr_or_mapfd); + auto offset = ptr_or_mapfd_with_off.get_offset(); + auto updated_offset = op == Bin::Op::ADD ? offset + change : offset - change; + ptr_or_mapfd_with_off.set_offset(updated_offset); + m_registers.insert(reg, reg_with_loc, ptr_or_mapfd_with_off); } - - ptr_t dst_reg; - if (bin.op == Bin::Op::ADD) { - auto it = m_registers.find(bin.dst.v); - if (!it) { - m_registers -= bin.dst.v; - return; - } - dst_reg = it.value(); + else if (std::holds_alternative(ptr_or_mapfd)) { + m_registers.insert(reg, reg_with_loc, ptr_or_mapfd); + } + else { + //std::cout << "type error: mapfd register cannot be incremented/decremented\n"; + m_errors.push_back("mapfd register cannot be incremented/decremented"); + m_registers -= reg; } +} - if (std::holds_alternative(bin.v)) { - Reg src = std::get(bin.v); - switch (bin.op) - { - // ra = rb - case Bin::Op::MOV: { - auto it1 = m_registers.find(src.v); - if (!it1) { - m_registers -= bin.dst.v; - break; +interval_t region_domain_t::do_bin(const Bin& bin, + const std::optional& src_interval_opt, + const std::optional& src_ptr_or_mapfd_opt, + const std::optional& dst_ptr_or_mapfd_opt, location_t loc) { + + using Op = Bin::Op; + // if we are doing a move, where src is a number and dst is not set, nothing to do + if (src_interval_opt && !dst_ptr_or_mapfd_opt && bin.op == Op::MOV) + return interval_t::bottom(); + + ptr_or_mapfd_t src_ptr_or_mapfd, dst_ptr_or_mapfd; + interval_t src_interval; + if (src_ptr_or_mapfd_opt) src_ptr_or_mapfd = std::move(src_ptr_or_mapfd_opt.value()); + if (dst_ptr_or_mapfd_opt) dst_ptr_or_mapfd = std::move(dst_ptr_or_mapfd_opt.value()); + if (src_interval_opt) src_interval = std::move(src_interval_opt.value()); + + auto reg = reg_with_loc_t(bin.dst.v, loc); + + switch (bin.op) + { + // ra = b, where b is a pointer/mapfd, a numerical register, or a constant; + case Op::MOV: { + // b is a pointer/mapfd + if (src_ptr_or_mapfd_opt) + m_registers.insert(bin.dst.v, reg, src_ptr_or_mapfd); + // b is a numerical register, or constant + else if (dst_ptr_or_mapfd_opt) { + m_registers -= bin.dst.v; + } + break; + } + // ra += b, where ra is a pointer/mapfd, or a numerical register, + // b is a pointer/mapfd, a numerical register, or a constant; + case Op::ADD: { + // adding pointer to another + if (src_ptr_or_mapfd_opt && dst_ptr_or_mapfd_opt) { + if (is_stack_pointer(bin.dst.v)) + m_stack.set_to_top(); + else { + // TODO: handle other cases properly + //std::cout << "type error: addition of two pointers\n"; + m_errors.push_back("addition of two pointers"); } - auto reg = reg_with_loc_t(bin.dst.v, loc); - m_registers.insert(bin.dst.v, reg, it1.value()); - break; + m_registers -= bin.dst.v; } - // ra += rb - case Bin::Op::ADD: { - auto it1 = m_registers.find(src.v); - if (it1) { - std::string s = std::to_string(static_cast(src.v)); - std::string s1 = std::to_string(static_cast(bin.dst.v)); - std::string desc = std::string("\taddition of two pointers, r") + s + " and r" + s1 + " not allowed\n"; - report_type_error(desc, loc); - return; + // ra is a pointer/mapfd + // b is a numerical register, or a constant + else if (dst_ptr_or_mapfd_opt && src_interval_opt) { + update_ptr_or_mapfd(std::move(dst_ptr_or_mapfd), std::move(src_interval), + bin.op, reg, bin.dst.v); + } + // b is a pointer/mapfd + // ra is a numerical register + else if (src_ptr_or_mapfd_opt && !dst_ptr_or_mapfd_opt) { + update_ptr_or_mapfd(std::move(src_ptr_or_mapfd), interval_t::top(), + bin.op, reg, bin.dst.v); + } + break; + } + // ra -= b, where ra is a pointer/mapfd + // b is a pointer/mapfd, numerical register, or a constant; + case Op::SUB: { + // b is a pointer/mapfd + if (dst_ptr_or_mapfd_opt && src_ptr_or_mapfd_opt) { + if (std::holds_alternative(dst_ptr_or_mapfd) && + std::holds_alternative(src_ptr_or_mapfd)) { + //std::cout << "type error: mapfd registers subtraction not defined\n"; + m_errors.push_back("mapfd registers subtraction not defined"); } - if (std::holds_alternative(dst_reg)) { - ptr_with_off_t dst_reg_with_off = std::get(dst_reg); - if (dst_reg_with_off.get_region() == crab::region::T_STACK) { - if (src_const_value) { - int updated_offset = dst_reg_with_off.get_offset()+(*src_const_value); - dst_reg_with_off.set_offset(updated_offset); - auto reg = reg_with_loc_t(bin.dst.v, loc); - m_registers.insert(bin.dst.v, reg, dst_reg_with_off); - - } - else { - m_stack.set_to_top(); - m_stack -= dst_reg_with_off.get_offset(); - return; - } + else if (std::holds_alternative(dst_ptr_or_mapfd) && + std::holds_alternative(src_ptr_or_mapfd)) { + auto dst_ptr_or_mapfd_with_off = std::get(dst_ptr_or_mapfd); + auto src_ptr_or_mapfd_with_off = std::get(src_ptr_or_mapfd); + if (dst_ptr_or_mapfd_with_off.get_region() + == src_ptr_or_mapfd_with_off.get_region()) { + m_registers -= bin.dst.v; + return (dst_ptr_or_mapfd_with_off.get_offset() - + src_ptr_or_mapfd_with_off.get_offset()); } else { - // currently, we do not read any other pointers from CTX except the ones already stored - // in case we add the functionality, we will have to implement forgetting of CTX offsets + //std::cout << "type error: subtraction between pointers of different region\n"; + m_errors.push_back("subtraction between pointers of different region"); } } - else { - auto reg = reg_with_loc_t(bin.dst.v, loc); - m_registers.insert(bin.dst.v, reg, dst_reg); + else if (!same_region(get_ptr(dst_ptr_or_mapfd), get_ptr(src_ptr_or_mapfd))) { + //std::cout << "type error: subtraction between pointers of different region\n"; + m_errors.push_back("subtraction between pointers of different region"); } - break; - } - default: m_registers -= bin.dst.v; - break; - } - } - else { - int imm = static_cast(std::get(bin.v).v); - switch (bin.op) - { - case Bin::Op::ADD: { - if (std::holds_alternative(dst_reg)) { - auto ptr_with_off = std::get(dst_reg); - ptr_with_off.set_offset(ptr_with_off.get_offset() + imm); - auto reg = reg_with_loc_t(bin.dst.v, loc); - m_registers.insert(bin.dst.v, reg, ptr_with_off); - } - else { - auto reg = reg_with_loc_t(bin.dst.v, loc); - m_registers.insert(bin.dst.v, reg, dst_reg); - } - break; } - default: { - m_registers -= bin.dst.v; - break; + // b is a numerical register, or a constant + else if (dst_ptr_or_mapfd_opt && src_interval_opt) { + update_ptr_or_mapfd(std::move(dst_ptr_or_mapfd), std::move(src_interval), + bin.op, reg, bin.dst.v); } + break; + } + default: { + m_registers -= bin.dst.v; + break; } } + return interval_t::bottom(); } -void region_domain_t::operator()(const Bin& bin, location_t loc, int print) { - do_bin(bin, nullptr, loc, print); -} - -void region_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc, int print) { +void region_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc) { - if (print > 0) { - auto target_reg_loc = reg_with_loc_t(target_reg.v, loc); - auto it = m_registers.find(target_reg_loc); - if (it) - print_annotated(b, it.value(), std::cout); - else - std::cout << " " << b << ";\n"; - return; - } + int width = b.access.width; int offset = b.access.offset; Reg basereg = b.access.basereg; @@ -844,58 +940,86 @@ void region_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t lo if (!it) { std::string s = std::to_string(static_cast(basereg.v)); std::string desc = std::string("\tloading from an unknown pointer, or from number - r") + s + "\n"; - report_type_error(desc, loc); + //std::cout << desc; + m_registers -= target_reg.v; return; } - ptr_t type_basereg = it.value(); + auto type_basereg = it.value(); - if (std::holds_alternative(type_basereg)) { - //std::cout << "type_error: loading from either packet or shared region not allowed - r" << (int)basereg.v << "\n"; + if (!std::holds_alternative(type_basereg) + || std::get(type_basereg).get_region() == crab::region_t::T_SHARED) { + // loading from either packet, shared region or mapfd does not happen in region domain m_registers -= target_reg.v; return; } - ptr_with_off_t type_with_off = std::get(type_basereg); - int load_at = offset+type_with_off.get_offset(); + auto type_with_off = std::get(type_basereg); + auto p_offset = type_with_off.get_offset(); + auto offset_singleton = p_offset.singleton(); switch (type_with_off.get_region()) { - case crab::region::T_STACK: { - - auto it = m_stack.find(load_at); - - if (!it) { - //std::cout << "type_error: no field at loaded offset " << load_at << " in stack\n"; + case crab::region_t::T_STACK: { + if (!offset_singleton) { + for (auto const& k : m_stack.get_keys()) { + auto start = p_offset.lb(); + auto end = p_offset.ub()+number_t{offset+width-1}; + interval_t range{start, end}; + if (range[number_t{(int)k}]) { + //std::cout << "stack load at unknown offset, and offset range contains pointers\n"; + m_errors.push_back("stack load at unknown offset, and offset range contains pointers"); + break; + } + } m_registers -= target_reg.v; - return; - } - ptr_t type_loaded = it.value(); - - if (std::holds_alternative(type_loaded)) { - ptr_with_off_t type_loaded_with_off = std::get(type_loaded); - auto reg = reg_with_loc_t(target_reg.v, loc); - m_registers.insert(target_reg.v, reg, type_loaded_with_off); } else { - ptr_no_off_t type_loaded_no_off = std::get(type_loaded); + auto ptr_offset = offset_singleton.value(); + auto load_at = (uint64_t)(ptr_offset + offset); + + auto it = m_stack.find(load_at); + + if (!it) { + // no field at loaded offset in stack + m_registers -= target_reg.v; + return; + } + auto type_loaded = it.value(); + auto reg = reg_with_loc_t(target_reg.v, loc); - m_registers.insert(target_reg.v, reg, type_loaded_no_off); + m_registers.insert(target_reg.v, reg, type_loaded.first); } - break; } - case crab::region::T_CTX: { - - auto it = m_ctx->find(load_at); - - if (!it) { - //std::cout << "type_error: no field at loaded offset " << load_at << " in context\n"; + case crab::region_t::T_CTX: { + + if (!offset_singleton) { + for (auto const& k : m_ctx->get_keys()) { + auto start = p_offset.lb(); + auto end = p_offset.ub()+crab::bound_t{offset+width-1}; + interval_t range{start, end}; + if (range[number_t{(int)k}]) { + //std::cout << "ctx load at unknown offset, and offset range contains pointers\n"; + m_errors.push_back("ctx load at unknown offset, and offset range contains pointers"); + break; + } + } m_registers -= target_reg.v; - return; } - ptr_no_off_t type_loaded = it.value(); + else { + auto ptr_offset = offset_singleton.value(); + auto load_at = (uint64_t)(ptr_offset + offset); + auto it = m_ctx->find(load_at); + + if (!it) { + // no field at loaded offset in ctx + m_registers -= target_reg.v; + return; + } + ptr_no_off_t type_loaded = it.value(); - auto reg = reg_with_loc_t(target_reg.v, loc); - m_registers.insert(target_reg.v, reg, type_loaded); + auto reg = reg_with_loc_t(target_reg.v, loc); + m_registers.insert(target_reg.v, reg, type_loaded); + } break; } @@ -905,173 +1029,96 @@ void region_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t lo } } -void region_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t loc, int print) { +void region_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t loc) { - if (print > 0) { - std::cout << " " << b << ";\n"; - return; - } int offset = b.access.offset; Reg basereg = b.access.basereg; int width = b.access.width; - auto it = m_registers.find(basereg.v); - if (!it) { + // TODO: move generic checks to type domain + auto maybe_basereg_type = m_registers.find(basereg.v); + if (!maybe_basereg_type) { std::string s = std::to_string(static_cast(basereg.v)); std::string desc = std::string("\tstoring at an unknown pointer, or from number - r") + s + "\n"; - report_type_error(desc, loc); + //std::cout << desc; + m_errors.push_back(desc); return; } - ptr_t type_basereg = it.value(); - - auto it2 = m_registers.find(target_reg.v); - - if (std::holds_alternative(type_basereg)) { - // base register is either CTX_P or STACK_P - ptr_with_off_t type_basereg_with_off = std::get(type_basereg); - - int store_at = offset+type_basereg_with_off.get_offset(); - if (type_basereg_with_off.get_region() == crab::region::T_STACK) { - // type of basereg is STACK_P - if (!it2) { - // std::cout << "type_error: storing either a number or an unknown pointer - r" << (int)target_reg.v << "\n"; - m_stack -= store_at; + auto basereg_type = maybe_basereg_type.value(); + auto targetreg_type = m_registers.find(target_reg.v); + + if (std::holds_alternative(basereg_type)) { + // base register is either CTX_P, STACK_P or SHARED_P + auto basereg_type_with_off = std::get(basereg_type); + + if (basereg_type_with_off.get_region() == crab::region_t::T_STACK) { + auto offset_singleton = basereg_type_with_off.get_offset().singleton(); + if (!offset_singleton) { + //std::cout << "type error: storing to a pointer with unknown offset\n"; + m_errors.push_back("storing to a pointer with unknown offset"); return; } - else { - auto type_to_store = it2.value(); - /* - if (std::holds_alternative(type_to_store) && - std::get(type_to_store).r == crab::region::T_STACK) { - std::string s = std::to_string(static_cast(target_reg.v)); - std::string desc = std::string("\twe cannot store stack pointer, r") + s + ", into stack\n"; - //report_type_error(desc, loc); - return; - } - else { - */ - for (auto i = store_at+1; i < store_at+width; i++) { - auto it3 = m_stack.find(i); - if (it3) { - std::string s = std::to_string(store_at); - std::string s1 = std::to_string(i); - std::string desc = std::string("\ttype being stored into stack at ") + s + " is overlapping with already stored at " + s1 + "\n"; - report_type_error(desc, loc); - return; - } - } - m_stack.insert(store_at, type_to_store); - // revise: code below checks if there is already something stored at same location, the type should be the same -- it is very restricted and not required. - // However, when we support storing info like width of type, we need more checks - /* - auto it4 = m_stack.find(store_at); - if (it4) { - auto type_in_stack = it4.value(); - if (type_to_store != type_in_stack) { - std::string s = std::to_string(store_at); - std::string desc = std::string("\ttype being stored at offset ") + s + " is not the same as already stored in stack\n"; - report_type_error(desc, loc); - return; - } - } - else { - m_stack.insert(store_at, type_to_store); - } - */ - //} - } + auto store_at = (uint64_t)offset+(uint64_t)offset_singleton.value(); + // type of basereg is STACK_P + auto overlapping_cells = m_stack.find_overlapping_cells(store_at, width); + m_stack -= overlapping_cells; + + // if targetreg_type is empty, we are storing a number + if (!targetreg_type) return; + + auto type_to_store = targetreg_type.value(); + m_stack.store(store_at, type_to_store, width); } - else if (type_basereg_with_off.get_region() == crab::region::T_CTX) { + else if (basereg_type_with_off.get_region() == crab::region_t::T_CTX) { // type of basereg is CTX_P - if (it2) { + if (targetreg_type) { std::string s = std::to_string(static_cast(target_reg.v)); std::string desc = std::string("\twe cannot store a pointer, r") + s + ", into ctx\n"; - report_type_error(desc, loc); + //std::cout << desc; + m_errors.push_back(desc); return; } } - else - assert(false); + else { + // type of basereg is SHARED_P + if (targetreg_type) { + //std::cout << "type error: we cannot store a pointer into shared\n"; + m_errors.push_back("we cannot store a pointer into shared"); + } + } } - else { - // base register type is either PACKET_P, SHARED_P or STACK_P without known offset - ptr_no_off_t type_basereg_no_off = std::get(type_basereg); - - // if basereg is a stack_p with no offset, we do not store anything, and no type errors - // if we later load with that pointer, we read nothing -- load is no-op - if (it2 && type_basereg_no_off.get_region() != crab::region::T_STACK) { + else if (std::holds_alternative(basereg_type)) { + // base register type is a PACKET_P + if (targetreg_type) { std::string s = std::to_string(static_cast(target_reg.v)); - std::string desc = std::string("\twe cannot store a pointer, r") + s + ", into packet or shared\n"; - report_type_error(desc, loc); + std::string desc = std::string("\twe cannot store a pointer, r") + s + ", into packet\n"; + //std::cout << desc; + m_errors.push_back(desc); return; } } -} - -void region_domain_t::operator()(const Mem& b, location_t loc, int print) { - if (is_bottom()) return; - - if (std::holds_alternative(b.value)) { - if (b.is_load) { - do_load(b, std::get(b.value), loc, print); - } else { - do_mem_store(b, std::get(b.value), loc, print); - } - } else { - std::string s = std::to_string(static_cast(std::get(b.value).v)); - std::string desc = std::string("\tEither loading to a number (not allowed) or storing a number (not allowed yet) - ") + s + "\n"; - report_type_error(desc, loc); + else { + //std::cout << "type error: we cannot store a pointer into a mapfd\n"; + m_errors.push_back("we cannot store a pointer into a mapfd"); return; } } -void region_domain_t::print_registers_at(location_t loc) const { - m_registers.print_types_at(loc); -} - -void region_domain_t::print_initial_types() const { - auto label = label_t::entry; - location_t loc = location_t(std::make_pair(label, 0)); - std::cout << "\n" << *m_ctx << "\n"; - std::cout << m_stack << "\n"; - std::cout << "Initial register types:\n"; - print_registers_at(loc); -} -void region_domain_t::operator()(const basic_block_t& bb, int print) { - auto label = bb.label(); - uint32_t curr_pos = 0; - location_t loc; - if (print > 0) { - if (label == label_t::entry) { - print_initial_types(); - m_is_bottom = false; - } - std::cout << label << ":\n"; +bool region_domain_t::is_stack_pointer(register_t reg) const { + auto type = m_registers.find(reg); + if (!type) { // not a pointer + return false; } + auto ptr_or_mapfd_type = type.value(); + return (std::holds_alternative(ptr_or_mapfd_type) && + std::get(ptr_or_mapfd_type).get_region() == crab::region_t::T_STACK); +} - for (const Instruction& statement : bb) { - loc = location_t(std::make_pair(label, ++curr_pos)); - if (print > 0) std::cout << " " << curr_pos << "."; - std::visit([this, loc, print](const auto& v) { std::apply(*this, std::make_tuple(v, loc, print)); }, statement); - if (print > 0 && error_location->first == loc->first && error_location->second == loc->second) std::cout << "type_error\n"; - } +void region_domain_t::adjust_bb_for_types(location_t loc) { + m_registers.adjust_bb_for_registers(loc); +} - if (print > 0) { - auto [it, et] = bb.next_blocks(); - if (it != et) { - std::cout << " " - << "goto "; - for (; it != et;) { - std::cout << *it; - ++it; - if (it == et) { - std::cout << ";"; - } else { - std::cout << ","; - } - } - } - std::cout << "\n\n"; - } +void region_domain_t::print_all_register_types() const { + m_registers.print_all_register_types(); } diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index c4d1998b3..0ea7fb7bd 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -4,6 +4,7 @@ #pragma once #include +#include #include "crab/abstract_domain.hpp" #include "crab/cfg.hpp" @@ -11,9 +12,19 @@ #include "string_constraints.hpp" #include +#include "platform.hpp" + +using crab::interval_t; +using crab::number_t; + +constexpr int STACK_BEGIN = 0; +constexpr int CTX_BEGIN = 0; +constexpr int PACKET_BEGIN = 0; +constexpr int SHARED_BEGIN = 0; + namespace crab { -enum class region { +enum class region_t { T_CTX, T_STACK, T_PACKET, @@ -22,7 +33,7 @@ enum class region { class ptr_no_off_t { - region m_r; + region_t m_r; public: ptr_no_off_t() = default; @@ -30,10 +41,10 @@ class ptr_no_off_t { ptr_no_off_t(ptr_no_off_t &&) = default; ptr_no_off_t &operator=(const ptr_no_off_t &) = default; ptr_no_off_t &operator=(ptr_no_off_t &&) = default; - ptr_no_off_t(region _r) : m_r(_r) {} + ptr_no_off_t(region_t _r) : m_r(_r) {} - constexpr region get_region() const { return m_r; } - void set_region(region); + constexpr region_t get_region() const { return m_r; } + void set_region(region_t); void write(std::ostream&) const; friend std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p); //bool operator==(const ptr_no_off_t& p2); @@ -41,8 +52,9 @@ class ptr_no_off_t { }; class ptr_with_off_t { - region m_r; - int m_offset; + region_t m_r; + interval_t m_offset; + interval_t m_region_size; public: ptr_with_off_t() = default; @@ -50,18 +62,48 @@ class ptr_with_off_t { ptr_with_off_t(ptr_with_off_t &&) = default; ptr_with_off_t &operator=(const ptr_with_off_t &) = default; ptr_with_off_t &operator=(ptr_with_off_t &&) = default; - ptr_with_off_t(region _r, int _off) : m_r(_r), m_offset(_off) {} - - constexpr int get_offset() const { return m_offset; } - void set_offset(int); - constexpr region get_region() const { return m_r; } - void set_region(region); + ptr_with_off_t(region_t _r, interval_t _off, interval_t _region_sz=interval_t::top()) + : m_r(_r), m_offset(_off), m_region_size(_region_sz) {} + ptr_with_off_t operator|(const ptr_with_off_t&) const; + interval_t get_region_size() const; + void set_region_size(interval_t); + interval_t get_offset() const { return m_offset; } + void set_offset(interval_t); + constexpr region_t get_region() const { return m_r; } + void set_region(region_t); void write(std::ostream&) const; friend std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p); //bool operator==(const ptr_with_off_t& p2); //bool operator!=(const ptr_with_off_t& p2); }; +using map_key_size_t = unsigned int; +using map_value_size_t = unsigned int; + +class mapfd_t { + int m_mapfd; + EbpfMapValueType m_value_type; + map_key_size_t m_key_size; + map_value_size_t m_value_size; + + public: + mapfd_t(const mapfd_t&) = default; + mapfd_t(mapfd_t&&) = default; + mapfd_t &operator=(const mapfd_t&) = default; + mapfd_t &operator=(mapfd_t&&) = default; + mapfd_t(int mapfd, EbpfMapValueType val_type, map_key_size_t key_size, + map_value_size_t value_size) + : m_mapfd(mapfd), m_value_type(val_type), m_key_size(key_size), m_value_size(value_size) {} + friend std::ostream& operator<<(std::ostream&, const mapfd_t&); + void write(std::ostream&) const; + + bool has_type_map_programs() const; + constexpr EbpfMapValueType get_value_type() const { return m_value_type; } + constexpr map_key_size_t get_key_size() const { return m_key_size; } + constexpr map_value_size_t get_value_size() const { return m_value_size; } + constexpr int get_mapfd() const { return m_mapfd; } +}; + using ptr_t = std::variant; using register_t = uint8_t; using location_t = boost::optional>; @@ -79,47 +121,50 @@ class reg_with_loc_t { }; class ctx_t { - using offset_to_ptr_no_off_t = std::unordered_map; + using ptr_types_t = std::unordered_map; - offset_to_ptr_no_off_t m_packet_ptrs; + ptr_types_t m_packet_ptrs; + int size = 0; public: ctx_t(const ebpf_context_descriptor_t* desc); - size_t size() const; - std::vector get_keys() const; - std::optional find(int key) const; - friend std::ostream& operator<<(std::ostream& o, const ctx_t& _ctx); + int get_size() const; + std::vector get_keys() const; + std::optional find(uint64_t key) const; }; -class stack_t { - using offset_to_ptr_t = std::unordered_map; +using ptr_or_mapfd_t = std::variant; +using ptr_or_mapfd_cells_t = std::pair; +using ptr_or_mapfd_types_t = std::map; - offset_to_ptr_t m_ptrs; +class stack_t { + ptr_or_mapfd_types_t m_ptrs; bool m_is_bottom; public: stack_t(bool is_bottom = false) : m_is_bottom(is_bottom) {} - stack_t(offset_to_ptr_t && ptrs, bool is_bottom) + stack_t(ptr_or_mapfd_types_t && ptrs, bool is_bottom) : m_ptrs(std::move(ptrs)) , m_is_bottom(is_bottom) {} stack_t operator|(const stack_t& other) const; - void operator-=(int); + void operator-=(uint64_t); + void operator-=(const std::vector&); void set_to_bottom(); void set_to_top(); static stack_t bottom(); static stack_t top(); bool is_bottom() const; bool is_top() const; - const offset_to_ptr_t &get_ptrs() { return m_ptrs; } - void insert(int key, ptr_t value); - std::optional find(int key) const; - std::vector get_keys() const; + const ptr_or_mapfd_types_t &get_ptrs() { return m_ptrs; } + void store(uint64_t, ptr_or_mapfd_t, int); + std::optional find(uint64_t) const; + std::vector get_keys() const; + std::vector find_overlapping_cells(uint64_t, int) const; size_t size() const; - friend std::ostream& operator<<(std::ostream& o, const stack_t& st); }; using live_registers_t = std::array, 11>; -using global_region_env_t = std::unordered_map; +using global_region_env_t = std::unordered_map; class register_types_t { @@ -129,10 +174,12 @@ class register_types_t { public: register_types_t(bool is_bottom = false) : m_region_env(nullptr), m_is_bottom(is_bottom) {} - explicit register_types_t(live_registers_t&& vars, std::shared_ptr reg_type_env, bool is_bottom = false) + explicit register_types_t(live_registers_t&& vars, + std::shared_ptr reg_type_env, bool is_bottom = false) : m_cur_def(std::move(vars)), m_region_env(reg_type_env), m_is_bottom(is_bottom) {} - explicit register_types_t(std::shared_ptr reg_type_env, bool is_bottom = false) + explicit register_types_t(std::shared_ptr reg_type_env, + bool is_bottom = false) : m_region_env(reg_type_env), m_is_bottom(is_bottom) {} register_types_t operator|(const register_types_t& other) const; @@ -141,12 +188,12 @@ class register_types_t { void set_to_top(); bool is_bottom() const; bool is_top() const; - void insert(register_t reg, const reg_with_loc_t& reg_with_loc, const ptr_t& type); - std::optional find(reg_with_loc_t reg) const; - std::optional find(register_t key) const; + void insert(register_t reg, const reg_with_loc_t& reg_with_loc, const ptr_or_mapfd_t& type); + std::optional find(reg_with_loc_t reg) const; + std::optional find(register_t key) const; const live_registers_t &get_vars() { return m_cur_def; } - friend std::ostream& operator<<(std::ostream& o, const register_types_t& p); - void print_types_at(location_t) const; + void adjust_bb_for_registers(location_t loc); + void print_all_register_types() const; }; } @@ -158,6 +205,7 @@ class region_domain_t final { crab::stack_t m_stack; crab::register_types_t m_registers; std::shared_ptr m_ctx; + std::vector m_errors; public: @@ -191,25 +239,26 @@ class region_domain_t final { region_domain_t narrow(const region_domain_t& other) const; //forget void operator-=(crab::variable_t var); + void operator-=(register_t var) { m_registers -= var; } //// abstract transformers - void operator()(const Undefined &, location_t loc = boost::none, int print = 0); - void operator()(const Bin &, location_t loc = boost::none, int print = 0); - void operator()(const Un &, location_t loc = boost::none, int print = 0); + void operator()(const Undefined &, location_t loc = boost::none, int print = 0) {} + void operator()(const Bin &, location_t loc = boost::none, int print = 0) {} + void operator()(const Un &, location_t loc = boost::none, int print = 0) {} void operator()(const LoadMapFd &, location_t loc = boost::none, int print = 0); void operator()(const Atomic&, location_t loc = boost::none, int print = 0); void operator()(const Call &, location_t loc = boost::none, int print = 0); void operator()(const Callx&, location_t loc = boost::none, int print = 0); - void operator()(const Exit &, location_t loc = boost::none, int print = 0); - void operator()(const Jmp &, location_t loc = boost::none, int print = 0); - void operator()(const Mem &, location_t loc = boost::none, int print = 0); + void operator()(const Exit &, location_t loc = boost::none, int print = 0) {} + void operator()(const Jmp &, location_t loc = boost::none, int print = 0) {} + void operator()(const Mem &, location_t loc = boost::none, int print = 0) {} void operator()(const Packet &, location_t loc = boost::none, int print = 0); - void operator()(const Assume &, location_t loc = boost::none, int print = 0); - void operator()(const Assert &, location_t loc = boost::none, int print = 0); - void operator()(const ValidAccess&, location_t loc = boost::none, int print = 0) {} + void operator()(const Assume &, location_t loc = boost::none, int print = 0) {} + void operator()(const Assert &, location_t loc = boost::none, int print = 0) {} + void operator()(const ValidAccess&, location_t loc = boost::none, int print = 0); void operator()(const Comparable& s, location_t loc = boost::none, int print = 0) {} - void operator()(const Addable& s, location_t loc = boost::none, int print = 0) {} - void operator()(const ValidStore& s, location_t loc = boost::none, int print = 0) {} + void operator()(const Addable& s, location_t loc = boost::none, int print = 0); + void operator()(const ValidStore& s, location_t loc = boost::none, int print = 0); void operator()(const TypeConstraint& s, location_t loc = boost::none, int print = 0); void operator()(const ValidSize& s, location_t loc = boost::none, int print = 0) {} void operator()(const ValidMapKeyValue& s, location_t loc = boost::none, int print = 0) {} @@ -217,25 +266,32 @@ class region_domain_t final { void operator()(const ValidDivisor& s, location_t loc = boost::none, int print = 0) {} void operator()(const FuncConstraint& s, location_t loc = boost::none, int print = 0) {}; void operator()(const IncrementLoopCounter&, location_t loc = boost::none, int print = 0); - void operator()(const basic_block_t& bb, int print = 0); - void write(std::ostream& os) const; + void operator()(const basic_block_t& bb, int print = 0) {} + void write(std::ostream& os) const {} crab::bound_t get_loop_count_upper_bound(); void initialize_loop_counter(const label_t&); + friend std::ostream& operator<<(std::ostream&, const region_domain_t&); string_invariant to_set(); void set_require_check(check_require_func_t f) {} - void do_load(const Mem&, const Reg&, location_t, int print = 0); - void do_mem_store(const Mem&, const Reg&, location_t, int print = 0); - void do_bin(const Bin&, std::shared_ptr, location_t, int print = 0); - void check_type_constraint(const TypeConstraint&); + void do_load(const Mem&, const Reg&, location_t); + void do_mem_store(const Mem&, const Reg&, location_t); + interval_t do_bin(const Bin&, const std::optional&, + const std::optional&, + const std::optional&, location_t); + void update_ptr_or_mapfd(crab::ptr_or_mapfd_t&&, const interval_t&&, Bin::Op, + const crab::reg_with_loc_t&, uint8_t); void report_type_error(std::string, location_t); - std::optional find_ptr_type(register_t); + std::optional find_ptr_or_mapfd_type(register_t) const; size_t ctx_size() const; - std::optional find_in_ctx(int key) const; - std::vector get_ctx_keys() const; - std::optional find_in_stack(int key) const; - std::vector get_stack_keys() const; - void print_registers_at(location_t) const; - void print_initial_types() const; + std::optional find_in_ctx(uint64_t key) const; + std::vector get_ctx_keys() const; + std::optional find_in_stack(uint64_t key) const; + std::optional find_ptr_or_mapfd_at_loc(const crab::reg_with_loc_t&) const; + std::vector get_stack_keys() const; + bool is_stack_pointer(register_t) const; + void adjust_bb_for_types(location_t loc); + void print_all_register_types() const; + std::vector& get_errors() { return m_errors; } }; // end region_domain_t diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 46ff873fd..dfe245540 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -5,6 +5,76 @@ #include "crab/type_domain.hpp" +namespace std { + static ptr_t get_ptr(const ptr_or_mapfd_t& t) { + return std::visit( overloaded + { + []( const ptr_with_off_t& x ){ return ptr_t{x};}, + []( const ptr_no_off_t& x ){ return ptr_t{x};}, + []( auto& ) { return ptr_t{};} + }, t + ); + } +} + +static std::string size(int w) { return std::string("u") + std::to_string(w * 8); } + +static void print_ptr_type(const ptr_t& ptr) { + if (std::holds_alternative(ptr)) { + ptr_with_off_t ptr_with_off = std::get(ptr); + std::cout << ptr_with_off; + } + else { + ptr_no_off_t ptr_no_off = std::get(ptr); + std::cout << ptr_no_off; + } +} + +static void print_ptr_or_mapfd_type(const ptr_or_mapfd_t& ptr_or_mapfd) { + if (std::holds_alternative(ptr_or_mapfd)) { + std::cout << std::get(ptr_or_mapfd); + } + else { + auto ptr = get_ptr(ptr_or_mapfd); + print_ptr_type(ptr); + } +} + +static void print_register(Reg r, std::optional& p) { + std::cout << r << " : "; + if (p) { + print_ptr_or_mapfd_type(p.value()); + } +} + +static void print_annotated(std::ostream& o, const Call& call, std::optional& p) { + o << " "; + print_register(Reg{(uint8_t)R0_RETURN_VALUE}, p); + o << " = " << call.name << ":" << call.func << "(...)\n"; +} + +static void print_annotated(std::ostream& o, const Bin& b, std::optional& p) { + o << " "; + print_register(b.dst, p); + o << " " << b.op << "= " << b.v << "\n"; +} + +static void print_annotated(std::ostream& o, const LoadMapFd& u, std::optional& p) { + o << " "; + print_register(u.dst, p); + o << " = map_fd " << u.mapfd << "\n"; +} + +static void print_annotated(std::ostream& o, const Mem& b, std::optional& p) { + o << " "; + print_register(std::get(b.value), p); + o << " = "; + std::string sign = b.access.offset < 0 ? " - " : " + "; + int offset = std::abs(b.access.offset); + o << "*(" << size(b.access.width) << " *)"; + o << "(" << b.access.basereg << sign << offset << ")\n"; +} + bool type_domain_t::is_bottom() const { return m_is_bottom; } @@ -34,6 +104,7 @@ bool type_domain_t::operator<=(const type_domain_t& abs) const { } type_domain_t type_domain_t::widen(const type_domain_t& other, bool to_constants) { + /* WARNING: The operation is not implemented yet.*/ type_domain_t res{}; return res; } @@ -72,16 +143,15 @@ type_domain_t type_domain_t::operator|(type_domain_t&& other) const { } type_domain_t type_domain_t::operator&(const type_domain_t& abs) const { + /* WARNING: The operation is not implemented yet.*/ return abs; } type_domain_t type_domain_t::narrow(const type_domain_t& other) const { + /* WARNING: The operation is not implemented yet.*/ return other; } -void type_domain_t::write(std::ostream& os) const { -} - void type_domain_t::initialize_loop_counter(label_t label) { // WARNING: Not implemented yet } @@ -96,59 +166,193 @@ string_invariant type_domain_t::to_set() { } void type_domain_t::operator()(const Undefined & u, location_t loc, int print) { - if (is_bottom()) return; - m_region(u, loc, print); + m_region(u, loc); } + void type_domain_t::operator()(const Un &u, location_t loc, int print) { - if (is_bottom()) return; - m_region(u, loc, print); } + void type_domain_t::operator()(const LoadMapFd &u, location_t loc, int print) { - if (is_bottom()) return; - m_region(u, loc, print); + m_region(u, loc); } + void type_domain_t::operator()(const Atomic &u, location_t loc, int print) { // WARNING: Not implemented yet } void type_domain_t::operator()(const IncrementLoopCounter &u, location_t loc, int print) { // WARNING: Not implemented yet } + void type_domain_t::operator()(const Call &u, location_t loc, int print) { - if (is_bottom()) return; - m_region(u, loc, print); + + for (ArgPair param : u.pairs) { + if (param.kind == ArgPair::Kind::PTR_TO_WRITABLE_MEM) { + auto maybe_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(param.mem.v); + if (!maybe_ptr_or_mapfd) continue; + auto ptr_or_mapfd = maybe_ptr_or_mapfd.value(); + if (std::holds_alternative(ptr_or_mapfd)) { + auto ptr_with_off = std::get(ptr_or_mapfd); + if (ptr_with_off.get_region() == region_t::T_STACK) { + auto offset_singleton = ptr_with_off.get_offset().singleton(); + if (!offset_singleton) { + //std::cout << "type error: storing at an unknown offset in stack\n"; + m_errors.push_back("storing at an unknown offset in stack"); + continue; + } + } + } + } + } + m_region(u, loc); } + void type_domain_t::operator()(const Callx &u, location_t loc, int print) { // WARNING: Not implemented yet } + void type_domain_t::operator()(const Exit &u, location_t loc, int print) { - if (is_bottom()) return; - m_region(u, loc, print); + m_region(u, loc); } + void type_domain_t::operator()(const Jmp &u, location_t loc, int print) { - if (is_bottom()) return; - m_region(u, loc, print); + m_region(u, loc); } + void type_domain_t::operator()(const Packet & u, location_t loc, int print) { - if (is_bottom()) return; - m_region(u, loc, print); + m_region(u, loc); } + void type_domain_t::operator()(const Assume &u, location_t loc, int print) { - if (is_bottom()) return; - m_region(u, loc, print); } void type_domain_t::operator()(const ValidAccess& s, location_t loc, int print) { + m_region(s, loc); } void type_domain_t::operator()(const TypeConstraint& s, location_t loc, int print) { - m_region.check_type_constraint(s); + m_region(s, loc); } void type_domain_t::operator()(const Assert &u, location_t loc, int print) { - if (is_bottom()) return; std::visit([this, loc, print](const auto& v) { std::apply(*this, std::make_tuple(v, loc, print)); }, u.cst); } +static bool is_mapfd_type(const ptr_or_mapfd_t& ptr_or_mapfd) { + return (std::holds_alternative(ptr_or_mapfd)); +} + +static region_t get_region(const ptr_t& ptr) { + if (std::holds_alternative(ptr)) { + return std::get(ptr).get_region(); + } + else { + return std::get(ptr).get_region(); + } +} + +void type_domain_t::operator()(const Comparable& u, location_t loc, int print) { + + auto maybe_ptr_or_mapfd1 = m_region.find_ptr_or_mapfd_type(u.r1.v); + auto maybe_ptr_or_mapfd2 = m_region.find_ptr_or_mapfd_type(u.r2.v); + if (maybe_ptr_or_mapfd1 && maybe_ptr_or_mapfd2) { + // an extra check just to make sure registers are not labelled both ptrs and numbers + auto ptr_or_mapfd1 = maybe_ptr_or_mapfd1.value(); + auto ptr_or_mapfd2 = maybe_ptr_or_mapfd1.value(); + if (is_mapfd_type(ptr_or_mapfd1) && is_mapfd_type(ptr_or_mapfd2)) { + return; + } + else if (!is_mapfd_type(ptr_or_mapfd1) && !is_mapfd_type(ptr_or_mapfd2)) { + auto ptr1 = get_ptr(ptr_or_mapfd1); + auto ptr2 = get_ptr(ptr_or_mapfd2); + if (get_region(ptr1) == get_region(ptr2)) { + return; + } + } + } + else if (!maybe_ptr_or_mapfd1 && !maybe_ptr_or_mapfd2) { + // all other cases when we do not have a ptr or mapfd, the type is a number + return; + } + //std::cout << "type error: Non-comparable types\n"; + m_errors.push_back("Non-comparable types"); +} + +void type_domain_t::operator()(const Addable& u, location_t loc, int print) { + m_region(u, loc); +} + +void type_domain_t::operator()(const ValidStore& u, location_t loc, int print) { + m_region(u, loc); +} + + +void type_domain_t::operator()(const ValidSize& u, location_t loc, int print) { +} + +void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int print) { + + int width; + auto maybe_ptr_or_mapfd_basereg = m_region.find_ptr_or_mapfd_type(u.access_reg.v); + auto maybe_mapfd = m_region.find_ptr_or_mapfd_type(u.map_fd_reg.v); + if (maybe_ptr_or_mapfd_basereg && maybe_mapfd) { + auto ptr_or_mapfd_basereg = maybe_ptr_or_mapfd_basereg.value(); + auto mapfd = maybe_mapfd.value(); + if (is_mapfd_type(mapfd)) { + auto mapfd_type = std::get(mapfd); + if (u.key) { + width = (int)mapfd_type.get_key_size(); + } + else { + width = (int)mapfd_type.get_value_size(); + } + if (std::holds_alternative(ptr_or_mapfd_basereg)) { + auto ptr_with_off = std::get(ptr_or_mapfd_basereg); + if (ptr_with_off.get_region() == region_t::T_STACK) { + auto offset_singleton = ptr_with_off.get_offset().singleton(); + if (!offset_singleton) { + //std::cout << "type error: reading the stack at an unknown offset\n"; + m_errors.push_back("reading the stack at an unknown offset"); + return; + } + auto offset_to_check = (uint64_t)offset_singleton.value(); + auto it2 = m_region.find_in_stack(offset_to_check); + if (it2) { + //std::cout << "type error: map update with a non-numerical value\n"; + m_errors.push_back("map update with a non-numerical value"); + } + return; + } + } + else if (std::holds_alternative(ptr_or_mapfd_basereg)) { + auto ptr_no_off = std::get(ptr_or_mapfd_basereg); + if (ptr_no_off.get_region() == region_t::T_PACKET) { + // We do not check packet ptr accesses yet + return; + } + } + } + } + //std::cout << "type error: valid map key value assertion failed\n"; + m_errors.push_back("valid map key value assertion failed"); +} + +void type_domain_t::operator()(const ZeroCtxOffset& u, location_t loc, int print) { + + auto maybe_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(u.reg.v); + if (maybe_ptr_or_mapfd) { + if (std::holds_alternative(maybe_ptr_or_mapfd.value())) { + auto ptr_type_with_off = std::get(maybe_ptr_or_mapfd.value()); + if (ptr_type_with_off.get_offset() == interval_t{crab::number_t{0}}) return; + } + else if (std::holds_alternative(maybe_ptr_or_mapfd.value())) { + // We do not yet support packet ptr offsets + return; + } + } + //std::cout << "type error: Zero Offset assertion fail\n"; + m_errors.push_back("Zero Offset assertion fail"); +} + type_domain_t type_domain_t::setup_entry() { region_domain_t reg = region_domain_t::setup_entry(); type_domain_t typ(std::move(reg)); @@ -156,115 +360,198 @@ type_domain_t type_domain_t::setup_entry() { } void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { - if (is_bottom()) return; - m_region.do_bin(bin, nullptr, loc, print); + + auto dst_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(bin.dst.v); + + std::optional src_ptr_or_mapfd; + std::optional src_interval; + if (std::holds_alternative(bin.v)) { + Reg r = std::get(bin.v); + src_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(r.v); + } + else { + auto imm = std::get(bin.v); + src_interval = interval_t{crab::number_t{static_cast(imm.v)}}; + } + + using Op = Bin::Op; + // for all operations except mov, add, sub, the src and dst should be numbers + if ((src_ptr_or_mapfd || dst_ptr_or_mapfd) + && (bin.op != Op::MOV && bin.op != Op::ADD && bin.op != Op::SUB)) { + //std::cout << "type error: operation on pointers not allowed\n"; + m_errors.push_back("operation on pointers not allowed"); + m_region -= bin.dst.v; + return; + } + + m_region.do_bin(bin, src_interval, src_ptr_or_mapfd, dst_ptr_or_mapfd, loc); } void type_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc, int print) { - m_region.do_load(b, target_reg, loc, print); + m_region.do_load(b, target_reg, loc); } void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t loc, int print) { - m_region.do_mem_store(b, target_reg, loc, print); + + m_region.do_mem_store(b, target_reg, loc); } void type_domain_t::operator()(const Mem& b, location_t loc, int print) { - if (is_bottom()) return; - if (std::holds_alternative(b.value)) { if (b.is_load) { do_load(b, std::get(b.value), loc, print); } else { do_mem_store(b, std::get(b.value), loc, print); } + } else { + std::string s = std::to_string(static_cast(std::get(b.value).v)); + std::string desc = std::string("\tEither loading to a number (not allowed) or storing a number (not allowed yet) - ") + s + "\n"; + //std::cout << desc; + m_errors.push_back(desc); + return; } } -static void print_ptr_type(ptr_t ptr) { - if (std::holds_alternative(ptr)) { - ptr_with_off_t ptr_with_off = std::get(ptr); - std::cout << ptr_with_off; - } - else { - ptr_no_off_t ptr_no_off = std::get(ptr); - std::cout << ptr_no_off; +// the method does not work well as it requires info about the label of basic block we are in +// this info is not available when we are only printing any state +// but it is available when we are processing a basic block for all its instructions:w +// +void type_domain_t::print_registers() const { + std::cout << " register types: {\n"; + for (size_t i = 0; i < NUM_REGISTERS; i++) { + register_t reg = (register_t)i; + auto maybe_ptr_or_mapfd_type = m_region.find_ptr_or_mapfd_type(reg); + if (maybe_ptr_or_mapfd_type) { + std::cout << " "; + print_register(Reg{(uint8_t)reg}, maybe_ptr_or_mapfd_type); + std::cout << "\n"; + } } + std::cout << " }\n"; } void type_domain_t::print_ctx() const { - std::vector ctx_keys = m_region.get_ctx_keys(); - std::cout << "ctx: {\n"; - for (auto const k : ctx_keys) { - std::optional ptr = m_region.find_in_ctx(k); + std::vector ctx_keys = m_region.get_ctx_keys(); + std::cout << " ctx: {\n"; + for (auto const& k : ctx_keys) { + auto ptr = m_region.find_in_ctx(k); if (ptr) { - std::cout << " " << k << ": "; + std::cout << " " << k << ": "; print_ptr_type(ptr.value()); std::cout << ",\n"; } } - std::cout << "}\n\n"; + std::cout << " }\n"; } void type_domain_t::print_stack() const { - std::vector stack_keys = m_region.get_stack_keys(); - std::cout << "stack: {\n"; - for (auto const k : stack_keys) { - std::optional ptr = m_region.find_in_stack(k); - if (ptr) { - std::cout << " " << k << ": "; - print_ptr_type(ptr.value()); + std::vector stack_keys_region = m_region.get_stack_keys(); + std::cout << " stack: {\n"; + for (auto const& k : stack_keys_region) { + auto maybe_ptr_or_mapfd_cells = m_region.find_in_stack(k); + if (maybe_ptr_or_mapfd_cells) { + auto ptr_or_mapfd_cells = maybe_ptr_or_mapfd_cells.value(); + int width = ptr_or_mapfd_cells.second; + auto ptr_or_mapfd = ptr_or_mapfd_cells.first; + std::cout << " [" << k << "-" << k+width-1 << "] : "; + print_ptr_or_mapfd_type(ptr_or_mapfd); std::cout << ",\n"; } } - std::cout << "}\n\n"; -} - -void type_domain_t::print_initial_registers() const { - auto label = label_t::entry; - location_t loc = location_t(std::make_pair(label, 0)); - std::cout << "Initial register types:\n"; - m_region.print_registers_at(loc); + std::cout << " }\n"; } -void type_domain_t::print_initial_types() const { - print_ctx(); - print_stack(); - print_initial_registers(); +void type_domain_t::adjust_bb_for_types(location_t loc) { + m_region.adjust_bb_for_types(loc); } void type_domain_t::operator()(const basic_block_t& bb, int print) { + + if (print != 0) { + write(std::cout, bb, print); + return; + } + auto label = bb.label(); uint32_t curr_pos = 0; - location_t loc; - if (print > 0) { - if (label == label_t::entry) { - print_initial_types(); - m_is_bottom = false; - } - std::cout << label << ":\n"; - } + location_t loc = location_t(std::make_pair(label, curr_pos)); + if (print == 0) + adjust_bb_for_types(loc); for (const Instruction& statement : bb) { loc = location_t(std::make_pair(label, ++curr_pos)); - if (print > 0) std::cout << " " << curr_pos << "."; std::visit([this, loc, print](const auto& v) { std::apply(*this, std::make_tuple(v, loc, print)); }, statement); } - if (print > 0) { - auto [it, et] = bb.next_blocks(); - if (it != et) { - std::cout << " " - << "goto "; - for (; it != et;) { - std::cout << *it; - ++it; - if (it == et) { - std::cout << ";"; - } else { - std::cout << ","; - } + operator+=(m_region.get_errors()); +} + +void type_domain_t::write(std::ostream& o, const basic_block_t& bb, int print) const { + if (is_bottom()) { + o << bb << "\n"; + return; + } + if (print < 0) { + o << "state of stack and ctx in program:\n"; + print_ctx(); + print_stack(); + o << "\n"; + return; + } + + o << bb.label() << ":\n"; + uint32_t curr_pos = 0; + for (const Instruction& statement : bb) { + ++curr_pos; + location_t loc = location_t(std::make_pair(bb.label(), curr_pos)); + o << " " << curr_pos << "."; + if (std::holds_alternative(statement)) { + auto r0_reg = crab::reg_with_loc_t(register_t{R0_RETURN_VALUE}, loc); + auto region = m_region.find_ptr_or_mapfd_at_loc(r0_reg); + print_annotated(o, std::get(statement), region); + } + else if (std::holds_alternative(statement)) { + auto b = std::get(statement); + auto reg_with_loc = crab::reg_with_loc_t(b.dst.v, loc); + auto region = m_region.find_ptr_or_mapfd_at_loc(reg_with_loc); + print_annotated(o, b, region); + } + else if (std::holds_alternative(statement)) { + auto u = std::get(statement); + if (u.is_load) { + auto target_reg = std::get(u.value); + auto target_reg_loc = crab::reg_with_loc_t(target_reg.v, loc); + auto region = m_region.find_ptr_or_mapfd_at_loc(target_reg_loc); + print_annotated(o, u, region); + } + else o << " " << u << "\n"; + } + else if (std::holds_alternative(statement)) { + auto u = std::get(statement); + auto reg = crab::reg_with_loc_t(u.dst.v, loc); + auto region = m_region.find_ptr_or_mapfd_at_loc(reg); + print_annotated(o, u, region); + } + else o << " " << statement << "\n"; + } + + auto [it, et] = bb.next_blocks(); + if (it != et) { + o << " " << "goto "; + for (; it != et;) { + o << *it; + ++it; + if (it == et) { + o << ";"; + } else { + o << ","; } } - std::cout << "\n\n"; } + o << "\n\n"; +} + +std::ostream& operator<<(std::ostream& o, const type_domain_t& typ) { + typ.write(o); + return o; } diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 2a3f5656e..9ed5714f8 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -11,13 +11,19 @@ #include "linear_constraint.hpp" #include "string_constraints.hpp" +constexpr int NUM_REGISTERS = 11; + using crab::ptr_t; +using crab::ptr_or_mapfd_t; using crab::ptr_with_off_t; using crab::ptr_no_off_t; +using crab::mapfd_t; +using crab::region_t; class type_domain_t final { region_domain_t m_region; bool m_is_bottom = false; + std::vector m_errors; public: @@ -67,31 +73,36 @@ class type_domain_t final { void operator()(const Assume &, location_t loc = boost::none, int print = 0); void operator()(const Assert &, location_t loc = boost::none, int print = 0); void operator()(const ValidAccess&, location_t loc = boost::none, int print = 0); - void operator()(const Comparable& s, location_t loc = boost::none, int print = 0) {} - void operator()(const Addable& s, location_t loc = boost::none, int print = 0) {} - void operator()(const ValidStore& s, location_t loc = boost::none, int print = 0) {} - void operator()(const TypeConstraint& s, location_t loc = boost::none, int print = 0); - void operator()(const ValidSize& s, location_t loc = boost::none, int print = 0) {} - void operator()(const ValidMapKeyValue& s, location_t loc = boost::none, int print = 0) {} - void operator()(const ZeroCtxOffset& s, location_t loc = boost::none, int print = 0) {} + void operator()(const Comparable&, location_t loc = boost::none, int print = 0); + void operator()(const Addable&, location_t loc = boost::none, int print = 0); + void operator()(const ValidStore&, location_t loc = boost::none, int print = 0); + void operator()(const TypeConstraint&, location_t loc = boost::none, int print = 0); + void operator()(const ValidSize&, location_t loc = boost::none, int print = 0); + void operator()(const ValidMapKeyValue&, location_t loc = boost::none, int print = 0); + void operator()(const ZeroCtxOffset&, location_t loc = boost::none, int print = 0); void operator()(const ValidDivisor& s, location_t loc = boost::none, int print = 0) {} void operator()(const FuncConstraint& s, location_t loc = boost::none, int print = 0) {}; void operator()(const IncrementLoopCounter&, location_t loc = boost::none, int print = 0); void operator()(const basic_block_t& bb, int print = 0); - void write(std::ostream& os) const; + void write(std::ostream& os, const basic_block_t&, int) const; + void write(std::ostream& os) const {} + friend std::ostream& operator<<(std::ostream& o, const type_domain_t& dom); void initialize_loop_counter(label_t label); crab::bound_t get_loop_count_upper_bound(); string_invariant to_set(); void set_require_check(check_require_func_t f) {} + std::vector& get_errors() { return m_errors; } private: void do_load(const Mem&, const Reg&, location_t, int print = 0); void do_mem_store(const Mem&, const Reg&, location_t, int print = 0); - void print_initial_types() const; void report_type_error(std::string, location_t); + void print_registers() const; void print_ctx() const; void print_stack() const; - void print_initial_registers() const; - + void adjust_bb_for_types(location_t); + void operator+=(std::vector& errs) { + m_errors.insert(m_errors.end(), errs.begin(), errs.end()); + } }; // end type_domain_t diff --git a/src/crab_verifier.cpp b/src/crab_verifier.cpp index d63066e22..fea511dbb 100644 --- a/src/crab_verifier.cpp +++ b/src/crab_verifier.cpp @@ -75,6 +75,20 @@ static checks_db generate_report(cfg_t& cfg, return m_db; } +static checks_db generate_report_type_domain(cfg_t& cfg, + crab::invariant_table_t& post_invariants) { + checks_db m_db; + for (const label_t& label : cfg.sorted_labels()) { + abstract_domain_t from_inv(post_invariants.at(label)); + + auto errors = from_inv.get_errors(); + for (auto& error : errors) { + m_db.add_warning(label, error); + } + } + return m_db; +} + static auto get_line_info(const InstructionSeq& insts) { std::map label_to_line_info; for (auto& [label, inst, line_info] : insts) { @@ -107,19 +121,26 @@ static void print_report(std::ostream& os, const checks_db& db, const Instructio static checks_db get_analysis_report(std::ostream& s, cfg_t& cfg, crab::invariant_table_t& pre_invariants, crab::invariant_table_t& post_invariants) { // Analyze the control-flow graph. - checks_db db = generate_report(cfg, pre_invariants, post_invariants); - if (thread_local_options.abstract_domain == abstract_domain_kind::TYPE_DOMAIN - || thread_local_options.abstract_domain == abstract_domain_kind::REGION_DOMAIN) { - auto state = post_invariants.at(label_t::exit); + //checks_db db = generate_report(cfg, pre_invariants, post_invariants); + checks_db db; + if (thread_local_options.abstract_domain == abstract_domain_kind::TYPE_DOMAIN) { + db = generate_report_type_domain(cfg, post_invariants); + auto exit_state = post_invariants.at(label_t::exit); + // only to print ctx and stack, fix later + exit_state(cfg.get_node(label_t::exit), -1); for (const label_t& label : cfg.sorted_labels()) { - state(cfg.get_node(label), thread_local_options.print_invariants ? 2 : 1); + auto post_state = post_invariants.at(label); + post_state(cfg.get_node(label), thread_local_options.print_invariants); } } - else if (thread_local_options.print_invariants) { - for (const label_t& label : cfg.sorted_labels()) { - s << "\nPre-invariant : " << pre_invariants.at(label) << "\n"; - s << cfg.get_node(label); - s << "\nPost-invariant: " << post_invariants.at(label) << "\n"; + else { + db = generate_report(cfg, pre_invariants, post_invariants); + if (thread_local_options.print_invariants) { + for (const label_t& label : cfg.sorted_labels()) { + s << "\nPre-invariant : " << pre_invariants.at(label) << "\n"; + s << cfg.get_node(label); + s << "\nPost-invariant: " << post_invariants.at(label) << "\n"; + } } } return db; @@ -132,10 +153,6 @@ static abstract_domain_t make_initial(const ebpf_verifier_options_t* options) { ebpf_domain_t entry_inv = ebpf_domain_t::setup_entry(true); return abstract_domain_t(entry_inv); } - case abstract_domain_kind::REGION_DOMAIN: { - region_domain_t entry_inv = region_domain_t::setup_entry(); - return abstract_domain_t(entry_inv); - } case abstract_domain_kind::TYPE_DOMAIN: { type_domain_t entry_inv = type_domain_t::setup_entry(); return abstract_domain_t(entry_inv); diff --git a/src/main/check.cpp b/src/main/check.cpp index 42c9a682e..6b0a3bfd1 100644 --- a/src/main/check.cpp +++ b/src/main/check.cpp @@ -55,7 +55,7 @@ int main(int argc, char** argv) { app.add_flag("-l", list, "List sections"); std::string domain = "zoneCrab"; - std::set doms{"stats", "linux", "zoneCrab", "region", "cfg", "type"}; + std::set doms{"stats", "linux", "zoneCrab", "cfg", "type"}; app.add_set("-d,--dom,--domain", domain, doms, "Abstract domain")->type_name("DOMAIN"); app.add_flag("--termination", ebpf_verifier_options.check_termination, "Verify termination"); @@ -163,12 +163,9 @@ int main(int argc, char** argv) { print_map_descriptors(global_program_info->map_descriptors, out); } - if (domain == "zoneCrab" || domain == "region" || domain == "type") { + if (domain == "zoneCrab" || domain == "type") { ebpf_verifier_stats_t verifier_stats; - if (domain == "region") { - ebpf_verifier_options.abstract_domain = abstract_domain_kind::REGION_DOMAIN; - } - else if (domain == "type") { + if (domain == "type") { ebpf_verifier_options.abstract_domain = abstract_domain_kind::TYPE_DOMAIN; } auto [res, seconds] = timed_execution([&] { From 83ac4abe47e6289301b41e625a4d2f6f12f0a392 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Thu, 13 Jul 2023 05:27:16 -0400 Subject: [PATCH 101/373] Refactor Move both type_domain_t and region_domain_t under the namespace crab; Move common code to both domains to another file; Remove redundant code/comments and improved structure; Signed-off-by: Ameer Hamza --- src/crab/abstract_domain.cpp | 4 +- src/crab/common.cpp | 208 ++++++++++++++++++++++++++ src/crab/common.hpp | 126 ++++++++++++++++ src/crab/region_domain.cpp | 275 ++++++++--------------------------- src/crab/region_domain.hpp | 178 +++++------------------ src/crab/type_domain.cpp | 136 ++++++----------- src/crab/type_domain.hpp | 55 +++---- src/crab_verifier.cpp | 1 + 8 files changed, 501 insertions(+), 482 deletions(-) create mode 100644 src/crab/common.cpp create mode 100644 src/crab/common.hpp diff --git a/src/crab/abstract_domain.cpp b/src/crab/abstract_domain.cpp index 059e746c6..c5d18f8e2 100644 --- a/src/crab/abstract_domain.cpp +++ b/src/crab/abstract_domain.cpp @@ -292,5 +292,5 @@ std::ostream& operator<<(std::ostream& o, const abstract_domain_t& dom) { // REQUIRED: instantiation for supported domains template abstract_domain_t::abstract_domain_t(crab::ebpf_domain_t); -template abstract_domain_t::abstract_domain_t(type_domain_t); -template abstract_domain_t::abstract_domain_t(region_domain_t); +template abstract_domain_t::abstract_domain_t(crab::type_domain_t); +template abstract_domain_t::abstract_domain_t(crab::region_domain_t); diff --git a/src/crab/common.cpp b/src/crab/common.cpp new file mode 100644 index 000000000..fd7d894ae --- /dev/null +++ b/src/crab/common.cpp @@ -0,0 +1,208 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT + +#pragma once + +#include "crab/common.hpp" + +namespace std { + template <> + struct hash { + size_t operator()(const crab::reg_with_loc_t& reg) const { return reg.hash(); } + }; + + static crab::ptr_t get_ptr(const crab::ptr_or_mapfd_t& t) { + return std::visit( overloaded + { + []( const crab::ptr_with_off_t& x ){ return crab::ptr_t{x};}, + []( const crab::ptr_no_off_t& x ){ return crab::ptr_t{x};}, + []( auto& ) { return crab::ptr_t{};} + }, t + ); + } +} + +namespace crab { + +bool same_region(const ptr_t& ptr1, const ptr_t& ptr2) { + return ((std::holds_alternative(ptr1) + && std::holds_alternative(ptr2)) + || (std::holds_alternative(ptr1) + && std::holds_alternative(ptr2))); +} + +inline std::ostream& operator<<(std::ostream& o, const region_t& t) { + o << static_cast::type>(t); + return o; +} + +bool operator==(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { + return (p1.get_region() == p2.get_region() && p1.get_offset() == p2.get_offset() + && p1.get_region_size() == p2.get_region_size()); +} + +bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { + return !(p1 == p2); +} + +interval_t ptr_with_off_t::get_region_size() const { return m_region_size; } + +void ptr_with_off_t::set_offset(interval_t off) { m_offset = off; } + +void ptr_with_off_t::set_region_size(interval_t region_sz) { m_region_size = region_sz; } + +void ptr_with_off_t::set_region(region_t r) { m_r = r; } + +ptr_with_off_t ptr_with_off_t::operator|(const ptr_with_off_t& other) const { + return ptr_with_off_t(m_r, m_offset | other.m_offset, m_region_size | other.m_region_size); +} + +bool operator==(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { + return (p1.get_region() == p2.get_region()); +} + +bool operator!=(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { + return !(p1 == p2); +} + +void ptr_no_off_t::set_region(region_t r) { m_r = r; } + +bool operator==(const mapfd_t& m1, const mapfd_t& m2) { + return (m1.get_value_type() == m2.get_value_type()); +} + +std::ostream& operator<<(std::ostream& o, const mapfd_t& m) { + m.write(o); + return o; +} + +bool mapfd_t::has_type_map_programs() const { + return (m_value_type == EbpfMapValueType::PROGRAM); +} + +void mapfd_t::write(std::ostream& o) const { + if (has_type_map_programs()) { + o << "map_fd_programs"; + } + else { + o << "map_fd"; + } +} + +void reg_with_loc_t::write(std::ostream& o) const { + o << "r" << static_cast(m_reg) << "@" << m_loc->second << " in " << m_loc->first << " "; +} + +std::ostream& operator<<(std::ostream& o, const reg_with_loc_t& reg) { + reg.write(o); + return o; +} + +bool reg_with_loc_t::operator==(const reg_with_loc_t& other) const { + return (m_reg == other.m_reg && m_loc == other.m_loc); +} + +std::size_t reg_with_loc_t::hash() const { + // Similar to boost::hash_combine + using std::hash; + + std::size_t seed = hash()(m_reg); + seed ^= hash()(m_loc->first.from) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= hash()(m_loc->first.to) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= hash()(m_loc->second) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + + return seed; +} + +inline std::string get_reg_ptr(const region_t& r) { + switch (r) { + case region_t::T_CTX: + return "ctx_p"; + case region_t::T_STACK: + return "stack_p"; + case region_t::T_PACKET: + return "packet_p"; + default: + return "shared_p"; + } +} + +void ptr_with_off_t::write(std::ostream& o) const { + o << get_reg_ptr(m_r) << "<" << m_offset; + if (m_region_size.lb() >= number_t{0}) o << "," << m_region_size; + o << ">"; +} + +std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p) { + p.write(o); + return o; +} + +void ptr_no_off_t::write(std::ostream& o) const { + o << get_reg_ptr(get_region()); +} + +std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p) { + p.write(o); + return o; +} + +} // namespace crab + +void print_ptr_type(const crab::ptr_t& ptr) { + if (std::holds_alternative(ptr)) { + crab::ptr_with_off_t ptr_with_off = std::get(ptr); + std::cout << ptr_with_off; + } + else { + crab::ptr_no_off_t ptr_no_off = std::get(ptr); + std::cout << ptr_no_off; + } +} + +void print_ptr_or_mapfd_type(const crab::ptr_or_mapfd_t& ptr_or_mapfd) { + if (std::holds_alternative(ptr_or_mapfd)) { + std::cout << std::get(ptr_or_mapfd); + } + else { + auto ptr = get_ptr(ptr_or_mapfd); + print_ptr_type(ptr); + } +} + +void print_register(Reg r, std::optional& p) { + std::cout << r << " : "; + if (p) { + print_ptr_or_mapfd_type(p.value()); + } +} + +inline std::string size_(int w) { return std::string("u") + std::to_string(w * 8); } + +void print_annotated(std::ostream& o, const Call& call, std::optional& p) { + o << " "; + print_register(Reg{(uint8_t)R0_RETURN_VALUE}, p); + o << " = " << call.name << ":" << call.func << "(...)\n"; +} + +void print_annotated(std::ostream& o, const Bin& b, std::optional& p) { + o << " "; + print_register(b.dst, p); + o << " " << b.op << "= " << b.v << "\n"; +} + +void print_annotated(std::ostream& o, const LoadMapFd& u, std::optional& p) { + o << " "; + print_register(u.dst, p); + o << " = map_fd " << u.mapfd << "\n"; +} + +void print_annotated(std::ostream& o, const Mem& b, std::optional& p) { + o << " "; + print_register(std::get(b.value), p); + o << " = "; + std::string sign = b.access.offset < 0 ? " - " : " + "; + int offset = std::abs(b.access.offset); + o << "*(" << size_(b.access.width) << " *)"; + o << "(" << b.access.basereg << sign << offset << ")\n"; +} diff --git a/src/crab/common.hpp b/src/crab/common.hpp new file mode 100644 index 000000000..3d894ec38 --- /dev/null +++ b/src/crab/common.hpp @@ -0,0 +1,126 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT + +#pragma once + +#include "linear_constraint.hpp" +#include "string_constraints.hpp" +#include "asm_syntax.hpp" +#include "asm_ostream.hpp" + +constexpr int NUM_REGISTERS = 11; + +constexpr int STACK_BEGIN = 0; +constexpr int CTX_BEGIN = 0; +constexpr int PACKET_BEGIN = 0; +constexpr int SHARED_BEGIN = 0; + +namespace crab { + +enum class region_t { + T_CTX, + T_STACK, + T_PACKET, + T_SHARED +}; + +class ptr_no_off_t { + region_t m_r; + + public: + ptr_no_off_t() = default; + ptr_no_off_t(const ptr_no_off_t &) = default; + ptr_no_off_t(ptr_no_off_t &&) = default; + ptr_no_off_t &operator=(const ptr_no_off_t &) = default; + ptr_no_off_t &operator=(ptr_no_off_t &&) = default; + ptr_no_off_t(region_t _r) : m_r(_r) {} + + [[nodiscard]] region_t get_region() const { return m_r; } + void set_region(region_t); + void write(std::ostream&) const; + friend std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p); +}; + +class ptr_with_off_t { + region_t m_r; + interval_t m_offset; + interval_t m_region_size; + + public: + ptr_with_off_t() = default; + ptr_with_off_t(const ptr_with_off_t &) = default; + ptr_with_off_t(ptr_with_off_t &&) = default; + ptr_with_off_t &operator=(const ptr_with_off_t &) = default; + ptr_with_off_t &operator=(ptr_with_off_t &&) = default; + ptr_with_off_t(region_t _r, interval_t _off, interval_t _region_sz=interval_t::top()) + : m_r(_r), m_offset(_off), m_region_size(_region_sz) {} + ptr_with_off_t operator|(const ptr_with_off_t&) const; + interval_t get_region_size() const; + void set_region_size(interval_t); + [[nodiscard]] interval_t get_offset() const { return m_offset; } + void set_offset(interval_t); + [[nodiscard]] region_t get_region() const { return m_r; } + void set_region(region_t); + void write(std::ostream&) const; + friend std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p); +}; + +using map_key_size_t = unsigned int; +using map_value_size_t = unsigned int; + +class mapfd_t { + int m_mapfd; + EbpfMapValueType m_value_type; + map_key_size_t m_key_size; + map_value_size_t m_value_size; + + public: + mapfd_t(const mapfd_t&) = default; + mapfd_t(mapfd_t&&) = default; + mapfd_t &operator=(const mapfd_t&) = default; + mapfd_t &operator=(mapfd_t&&) = default; + mapfd_t(int mapfd, EbpfMapValueType val_type, map_key_size_t key_size, + map_value_size_t value_size) + : m_mapfd(mapfd), m_value_type(val_type), m_key_size(key_size), m_value_size(value_size) {} + friend std::ostream& operator<<(std::ostream&, const mapfd_t&); + void write(std::ostream&) const; + + bool has_type_map_programs() const; + [[nodiscard]] EbpfMapValueType get_value_type() const { return m_value_type; } + [[nodiscard]] map_key_size_t get_key_size() const { return m_key_size; } + [[nodiscard]] map_value_size_t get_value_size() const { return m_value_size; } + [[nodiscard]] int get_mapfd() const { return m_mapfd; } +}; + +using ptr_t = std::variant; +using register_t = uint8_t; +using location_t = boost::optional>; + +class reg_with_loc_t { + register_t m_reg; + location_t m_loc; + + public: + reg_with_loc_t(register_t _r, location_t _loc) : m_reg(_r), m_loc(_loc) {} + bool operator==(const reg_with_loc_t& other) const; + std::size_t hash() const; + friend std::ostream& operator<<(std::ostream& o, const reg_with_loc_t& reg); + void write(std::ostream& ) const; +}; + +using ptr_or_mapfd_t = std::variant; +using ptr_or_mapfd_cells_t = std::pair; +using ptr_or_mapfd_types_t = std::map; + +using live_registers_t = std::array, 11>; +using global_region_env_t = std::unordered_map; + +} // namespace crab + +void print_ptr_or_mapfd_type(const crab::ptr_or_mapfd_t&); +void print_ptr_type(const crab::ptr_t& ptr); +void print_register(Reg r, std::optional& p); +void print_annotated(std::ostream& o, const Call& call, std::optional& p); +void print_annotated(std::ostream& o, const Bin& b, std::optional& p); +void print_annotated(std::ostream& o, const LoadMapFd& u, std::optional& p); +void print_annotated(std::ostream& o, const Mem& b, std::optional& p); diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index 359503152..4b46901ab 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -2,206 +2,10 @@ // SPDX-License-Identifier: MIT #include "crab/region_domain.hpp" - -using crab::___print___; -using crab::ptr_t; -using crab::mapfd_t; -using crab::ptr_or_mapfd_t; -using crab::ptr_with_off_t; -using crab::ptr_no_off_t; -using crab::ctx_t; -using crab::global_region_env_t; -using crab::reg_with_loc_t; -using crab::live_registers_t; -using crab::register_types_t; -using crab::map_key_size_t; -using crab::map_value_size_t; -using crab::ptr_or_mapfd_cells_t; - -namespace std { - template <> - struct hash { - size_t operator()(const crab::reg_with_loc_t& reg) const { return reg.hash(); } - }; - - // does not seem to work for me - /* - template <> - struct equal_to { - constexpr bool operator()(const crab::ptr_t& p1, const crab::ptr_t& p2) const { - if (p1.index() != p2.index()) return false; - if (std::holds_alternative(p1)) { - auto ptr_no_off1 = std::get(p1); - auto ptr_no_off2 = std::get(p2); - return (ptr_no_off1.get_region() == ptr_no_off2.get_region()); - } - else { - auto ptr_with_off1 = std::get(p1); - auto ptr_with_off2 = std::get(p2); - return (ptr_with_off1.get_region() == ptr_with_off2.get_region() && ptr_with_off1.get_offset() == ptr_with_off2.get_offset()); - } - } - }; - */ - - static ptr_t get_ptr(const ptr_or_mapfd_t& t) { - return std::visit( overloaded - { - []( const ptr_with_off_t& x ){ return ptr_t{x};}, - []( const ptr_no_off_t& x ){ return ptr_t{x};}, - []( auto& ) { return ptr_t{};} - }, t - ); - } -} - +#include "crab/common.cpp" namespace crab { -inline std::string get_reg_ptr(const region_t& r) { - switch (r) { - case region_t::T_CTX: - return "ctx_p"; - case region_t::T_STACK: - return "stack_p"; - case region_t::T_PACKET: - return "packet_p"; - default: - return "shared_p"; - } -} - -static bool same_region(const ptr_t& ptr1, const ptr_t& ptr2) { - return ((std::holds_alternative(ptr1) - && std::holds_alternative(ptr2)) - || (std::holds_alternative(ptr1) - && std::holds_alternative(ptr2))); -} - -static void print_ptr_type(const ptr_t& ptr) { - if (std::holds_alternative(ptr)) { - ptr_with_off_t ptr_with_off = std::get(ptr); - std::cout << ptr_with_off; - } - else { - ptr_no_off_t ptr_no_off = std::get(ptr); - std::cout << ptr_no_off; - } -} - -static void print_ptr_or_mapfd_type(const ptr_or_mapfd_t& ptr_or_mapfd) { - if (std::holds_alternative(ptr_or_mapfd)) { - std::cout << std::get(ptr_or_mapfd); - } - else { - auto ptr = get_ptr(ptr_or_mapfd); - print_ptr_type(ptr); - } -} - -inline std::ostream& operator<<(std::ostream& o, const region_t& t) { - o << static_cast::type>(t); - return o; -} - -bool operator==(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { - return (p1.get_region() == p2.get_region() && p1.get_offset() == p2.get_offset() - && p1.get_region_size() == p2.get_region_size()); -} - -bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { - return !(p1 == p2); -} - -void ptr_with_off_t::write(std::ostream& o) const { - o << get_reg_ptr(m_r) << "<" << m_offset; - if (m_region_size.lb() >= number_t{0}) o << "," << m_region_size; - o << ">"; -} - -std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p) { - p.write(o); - return o; -} - -interval_t ptr_with_off_t::get_region_size() const { return m_region_size; } - -void ptr_with_off_t::set_offset(interval_t off) { m_offset = off; } - -void ptr_with_off_t::set_region_size(interval_t region_sz) { m_region_size = region_sz; } - -void ptr_with_off_t::set_region(region_t r) { m_r = r; } - -ptr_with_off_t ptr_with_off_t::operator|(const ptr_with_off_t& other) const { - return ptr_with_off_t(m_r, m_offset | other.m_offset, m_region_size | other.m_region_size); -} - -bool operator==(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { - return (p1.get_region() == p2.get_region()); -} - -bool operator!=(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { - return !(p1 == p2); -} - -void ptr_no_off_t::write(std::ostream& o) const { - o << get_reg_ptr(get_region()); -} - -std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p) { - p.write(o); - return o; -} - -void ptr_no_off_t::set_region(region_t r) { m_r = r; } - -bool operator==(const mapfd_t& m1, const mapfd_t& m2) { - return (m1.get_value_type() == m2.get_value_type()); -} - -std::ostream& operator<<(std::ostream& o, const mapfd_t& m) { - m.write(o); - return o; -} - -bool mapfd_t::has_type_map_programs() const { - return (m_value_type == EbpfMapValueType::PROGRAM); -} - -void mapfd_t::write(std::ostream& o) const { - if (has_type_map_programs()) { - o << "map_fd_programs"; - } - else { - o << "map_fd"; - } -} - -void reg_with_loc_t::write(std::ostream& o) const { - o << "r" << static_cast(m_reg) << "@" << m_loc->second << " in " << m_loc->first << " "; -} - -std::ostream& operator<<(std::ostream& o, const reg_with_loc_t& reg) { - reg.write(o); - return o; -} - -bool reg_with_loc_t::operator==(const reg_with_loc_t& other) const { - return (m_reg == other.m_reg && m_loc == other.m_loc); -} - -std::size_t reg_with_loc_t::hash() const { - // Similar to boost::hash_combine - using std::hash; - - std::size_t seed = hash()(m_reg); - seed ^= hash()(m_loc->first.from) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - seed ^= hash()(m_loc->first.to) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - seed ^= hash()(m_loc->second) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - - return seed; -} - ctx_t::ctx_t(const ebpf_context_descriptor_t* desc) { if (desc->data >= 0) { @@ -218,10 +22,6 @@ ctx_t::ctx_t(const ebpf_context_descriptor_t* desc) } } -int ctx_t::get_size() const { - return size; -} - std::vector ctx_t::get_keys() const { std::vector keys; keys.reserve(size); @@ -469,8 +269,6 @@ std::vector stack_t::find_overlapping_cells(uint64_t start, int width) return overlapping_cells; } -} - std::optional region_domain_t::find_ptr_or_mapfd_type(register_t reg) const { return m_registers.find(reg); } @@ -590,6 +388,48 @@ string_invariant region_domain_t::to_set() { return string_invariant{}; } +void region_domain_t::operator()(const Undefined &u, location_t loc, int print) {} + +void region_domain_t::operator()(const Exit &u, location_t loc, int print) {} + +void region_domain_t::operator()(const Jmp &u, location_t loc, int print) {} + +void region_domain_t::operator()(const Assume& u, location_t loc, int print) { + // nothing to do here +} + +void region_domain_t::operator()(const Assert& u, location_t loc, int print) { + // nothing to do here +} + +void region_domain_t::operator()(const Comparable& u, location_t loc, int print) { + // nothing to do here +} + +void region_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int print) { + // nothing to do here +} + +void region_domain_t::operator()(const ZeroCtxOffset& u, location_t loc, int print) { + // nothing to do here +} + +void region_domain_t::operator()(const basic_block_t& bb, int print) { + // nothing to do here +} + +void region_domain_t::operator()(const Un &u, location_t loc, int print) { + /* WARNING: The operation is not implemented yet.*/ +} + +void region_domain_t::operator()(const ValidDivisor& u, location_t loc, int print) { + /* WARNING: The operation is not implemented yet.*/ +} + +void region_domain_t::operator()(const ValidSize& u, location_t loc, int print) { + /* WARNING: The operation is not implemented yet.*/ +} + void region_domain_t::operator()(const LoadMapFd &u, location_t loc, int print) { auto reg = u.dst.v; auto reg_with_loc = reg_with_loc_t(reg, loc); @@ -656,11 +496,11 @@ void region_domain_t::operator()(const Atomic &u, location_t loc, int print) { // WARNING: Not implemented yet. } -void region_domain_t::operator()(const Packet & u, location_t loc, int print) { +void region_domain_t::operator()(const Packet &u, location_t loc, int print) { m_registers -= register_t{R0_RETURN_VALUE}; } -void region_domain_t::operator()(const Addable& u, location_t loc, int print) { +void region_domain_t::operator()(const Addable &u, location_t loc, int print) { auto maybe_ptr_type1 = m_registers.find(u.ptr.v); auto maybe_ptr_type2 = m_registers.find(u.num.v); @@ -672,7 +512,7 @@ void region_domain_t::operator()(const Addable& u, location_t loc, int print) { m_errors.push_back("Addable assertion fail"); } -void region_domain_t::operator()(const ValidAccess& s, location_t loc, int print) { +void region_domain_t::operator()(const ValidAccess &s, location_t loc, int print) { bool is_comparison_check = s.width == (Value)Imm{0}; if (std::holds_alternative(s.width)) return; int width = std::get(s.width).v; @@ -730,7 +570,7 @@ void region_domain_t::operator()(const ValidStore& u, location_t loc, int print) m_errors.push_back("Valid store assertion fail"); } -region_domain_t region_domain_t::setup_entry() { +region_domain_t&& region_domain_t::setup_entry() { std::shared_ptr ctx = std::make_shared(global_program_info.get().type.context_descriptor); std::shared_ptr all_types = std::make_shared(); @@ -744,15 +584,8 @@ region_domain_t region_domain_t::setup_entry() { typ.insert(R10_STACK_POINTER, r10, ptr_with_off_t(crab::region_t::T_STACK, interval_t{number_t{512}})); - region_domain_t inv(std::move(typ), crab::stack_t::top(), ctx); - return inv; -} - -void region_domain_t::report_type_error(std::string s, location_t loc) { - std::cout << "type_error at line " << loc->second << " in bb " << loc->first << "\n"; - std::cout << s; - error_location = loc; - set_to_bottom(); + static region_domain_t inv(std::move(typ), crab::stack_t::top(), ctx); + return std::move(inv); } void region_domain_t::operator()(const TypeConstraint& s, location_t loc, int print) { @@ -825,6 +658,10 @@ void region_domain_t::update_ptr_or_mapfd(ptr_or_mapfd_t&& ptr_or_mapfd, const i } } +void region_domain_t::operator()(const Bin& b, location_t loc, int print) { + // nothing to do here +} + interval_t region_domain_t::do_bin(const Bin& bin, const std::optional& src_interval_opt, const std::optional& src_ptr_or_mapfd_opt, @@ -1029,6 +866,10 @@ void region_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t lo } } +void region_domain_t::operator()(const Mem& m, location_t loc, int print) { + // nothing to do here +} + void region_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t loc) { int offset = b.access.offset; @@ -1122,3 +963,5 @@ void region_domain_t::adjust_bb_for_types(location_t loc) { void region_domain_t::print_all_register_types() const { m_registers.print_all_register_types(); } + +} // namespace crab diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index 0ea7fb7bd..3bd2111ba 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -7,6 +7,7 @@ #include #include "crab/abstract_domain.hpp" +#include "crab/common.hpp" #include "crab/cfg.hpp" #include "linear_constraint.hpp" #include "string_constraints.hpp" @@ -14,129 +15,21 @@ #include "platform.hpp" -using crab::interval_t; -using crab::number_t; - -constexpr int STACK_BEGIN = 0; -constexpr int CTX_BEGIN = 0; -constexpr int PACKET_BEGIN = 0; -constexpr int SHARED_BEGIN = 0; - namespace crab { -enum class region_t { - T_CTX, - T_STACK, - T_PACKET, - T_SHARED -}; - - -class ptr_no_off_t { - region_t m_r; - - public: - ptr_no_off_t() = default; - ptr_no_off_t(const ptr_no_off_t &) = default; - ptr_no_off_t(ptr_no_off_t &&) = default; - ptr_no_off_t &operator=(const ptr_no_off_t &) = default; - ptr_no_off_t &operator=(ptr_no_off_t &&) = default; - ptr_no_off_t(region_t _r) : m_r(_r) {} - - constexpr region_t get_region() const { return m_r; } - void set_region(region_t); - void write(std::ostream&) const; - friend std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p); - //bool operator==(const ptr_no_off_t& p2); - //bool operator!=(const ptr_no_off_t& p2); -}; - -class ptr_with_off_t { - region_t m_r; - interval_t m_offset; - interval_t m_region_size; - - public: - ptr_with_off_t() = default; - ptr_with_off_t(const ptr_with_off_t &) = default; - ptr_with_off_t(ptr_with_off_t &&) = default; - ptr_with_off_t &operator=(const ptr_with_off_t &) = default; - ptr_with_off_t &operator=(ptr_with_off_t &&) = default; - ptr_with_off_t(region_t _r, interval_t _off, interval_t _region_sz=interval_t::top()) - : m_r(_r), m_offset(_off), m_region_size(_region_sz) {} - ptr_with_off_t operator|(const ptr_with_off_t&) const; - interval_t get_region_size() const; - void set_region_size(interval_t); - interval_t get_offset() const { return m_offset; } - void set_offset(interval_t); - constexpr region_t get_region() const { return m_r; } - void set_region(region_t); - void write(std::ostream&) const; - friend std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p); - //bool operator==(const ptr_with_off_t& p2); - //bool operator!=(const ptr_with_off_t& p2); -}; - -using map_key_size_t = unsigned int; -using map_value_size_t = unsigned int; - -class mapfd_t { - int m_mapfd; - EbpfMapValueType m_value_type; - map_key_size_t m_key_size; - map_value_size_t m_value_size; - - public: - mapfd_t(const mapfd_t&) = default; - mapfd_t(mapfd_t&&) = default; - mapfd_t &operator=(const mapfd_t&) = default; - mapfd_t &operator=(mapfd_t&&) = default; - mapfd_t(int mapfd, EbpfMapValueType val_type, map_key_size_t key_size, - map_value_size_t value_size) - : m_mapfd(mapfd), m_value_type(val_type), m_key_size(key_size), m_value_size(value_size) {} - friend std::ostream& operator<<(std::ostream&, const mapfd_t&); - void write(std::ostream&) const; - - bool has_type_map_programs() const; - constexpr EbpfMapValueType get_value_type() const { return m_value_type; } - constexpr map_key_size_t get_key_size() const { return m_key_size; } - constexpr map_value_size_t get_value_size() const { return m_value_size; } - constexpr int get_mapfd() const { return m_mapfd; } -}; - -using ptr_t = std::variant; -using register_t = uint8_t; -using location_t = boost::optional>; - -class reg_with_loc_t { - register_t m_reg; - location_t m_loc; - - public: - reg_with_loc_t(register_t _r, location_t _loc) : m_reg(_r), m_loc(_loc) {} - bool operator==(const reg_with_loc_t& other) const; - std::size_t hash() const; - friend std::ostream& operator<<(std::ostream& o, const reg_with_loc_t& reg); - void write(std::ostream& ) const; -}; - class ctx_t { using ptr_types_t = std::unordered_map; ptr_types_t m_packet_ptrs; - int size = 0; + size_t size = 0; public: ctx_t(const ebpf_context_descriptor_t* desc); - int get_size() const; + constexpr size_t get_size() const { return size; } std::vector get_keys() const; std::optional find(uint64_t key) const; }; -using ptr_or_mapfd_t = std::variant; -using ptr_or_mapfd_cells_t = std::pair; -using ptr_or_mapfd_types_t = std::map; - class stack_t { ptr_or_mapfd_types_t m_ptrs; bool m_is_bottom; @@ -163,9 +56,6 @@ class stack_t { size_t size() const; }; -using live_registers_t = std::array, 11>; -using global_region_env_t = std::unordered_map; - class register_types_t { live_registers_t m_cur_def; @@ -191,17 +81,14 @@ class register_types_t { void insert(register_t reg, const reg_with_loc_t& reg_with_loc, const ptr_or_mapfd_t& type); std::optional find(reg_with_loc_t reg) const; std::optional find(register_t key) const; - const live_registers_t &get_vars() { return m_cur_def; } + [[nodiscard]] live_registers_t &get_vars() { return m_cur_def; } void adjust_bb_for_registers(location_t loc); void print_all_register_types() const; }; -} - class region_domain_t final { bool m_is_bottom = false; - location_t error_location = boost::none; crab::stack_t m_stack; crab::register_types_t m_registers; std::shared_ptr m_ctx; @@ -217,7 +104,7 @@ class region_domain_t final { region_domain_t(crab::register_types_t&& _types, crab::stack_t&& _st, std::shared_ptr _ctx) : m_stack(std::move(_st)), m_registers(std::move(_types)), m_ctx(_ctx) {} // eBPF initialization: R1 points to ctx, R10 to stack, etc. - static region_domain_t setup_entry(); + static region_domain_t&& setup_entry(); // bottom/top static region_domain_t bottom(); void set_to_top(); @@ -242,32 +129,32 @@ class region_domain_t final { void operator-=(register_t var) { m_registers -= var; } //// abstract transformers - void operator()(const Undefined &, location_t loc = boost::none, int print = 0) {} - void operator()(const Bin &, location_t loc = boost::none, int print = 0) {} - void operator()(const Un &, location_t loc = boost::none, int print = 0) {} - void operator()(const LoadMapFd &, location_t loc = boost::none, int print = 0); + void operator()(const Undefined&, location_t loc = boost::none, int print = 0); + void operator()(const Bin&, location_t loc = boost::none, int print = 0); + void operator()(const Un&, location_t loc = boost::none, int print = 0); + void operator()(const LoadMapFd&, location_t loc = boost::none, int print = 0); void operator()(const Atomic&, location_t loc = boost::none, int print = 0); - void operator()(const Call &, location_t loc = boost::none, int print = 0); + void operator()(const Call&, location_t loc = boost::none, int print = 0); void operator()(const Callx&, location_t loc = boost::none, int print = 0); - void operator()(const Exit &, location_t loc = boost::none, int print = 0) {} - void operator()(const Jmp &, location_t loc = boost::none, int print = 0) {} - void operator()(const Mem &, location_t loc = boost::none, int print = 0) {} - void operator()(const Packet &, location_t loc = boost::none, int print = 0); - void operator()(const Assume &, location_t loc = boost::none, int print = 0) {} - void operator()(const Assert &, location_t loc = boost::none, int print = 0) {} + void operator()(const Exit&, location_t loc = boost::none, int print = 0); + void operator()(const Jmp&, location_t loc = boost::none, int print = 0); + void operator()(const Mem&, location_t loc = boost::none, int print = 0); + void operator()(const Packet&, location_t loc = boost::none, int print = 0); + void operator()(const Assume&, location_t loc = boost::none, int print = 0); + void operator()(const Assert&, location_t loc = boost::none, int print = 0); void operator()(const ValidAccess&, location_t loc = boost::none, int print = 0); - void operator()(const Comparable& s, location_t loc = boost::none, int print = 0) {} - void operator()(const Addable& s, location_t loc = boost::none, int print = 0); - void operator()(const ValidStore& s, location_t loc = boost::none, int print = 0); - void operator()(const TypeConstraint& s, location_t loc = boost::none, int print = 0); - void operator()(const ValidSize& s, location_t loc = boost::none, int print = 0) {} - void operator()(const ValidMapKeyValue& s, location_t loc = boost::none, int print = 0) {} - void operator()(const ZeroCtxOffset& s, location_t loc = boost::none, int print = 0) {} - void operator()(const ValidDivisor& s, location_t loc = boost::none, int print = 0) {} + void operator()(const Comparable&, location_t loc = boost::none, int print = 0); + void operator()(const Addable&, location_t loc = boost::none, int print = 0); + void operator()(const ValidStore&, location_t loc = boost::none, int print = 0); + void operator()(const TypeConstraint&, location_t loc = boost::none, int print = 0); + void operator()(const ValidSize&, location_t loc = boost::none, int print = 0); + void operator()(const ValidMapKeyValue&, location_t loc = boost::none, int print = 0); + void operator()(const ZeroCtxOffset&, location_t loc = boost::none, int print = 0); + void operator()(const ValidDivisor&, location_t loc = boost::none, int print = 0); void operator()(const FuncConstraint& s, location_t loc = boost::none, int print = 0) {}; void operator()(const IncrementLoopCounter&, location_t loc = boost::none, int print = 0); - void operator()(const basic_block_t& bb, int print = 0) {} - void write(std::ostream& os) const {} + void operator()(const basic_block_t& bb, int print = 0); + void write(std::ostream& o) const {} crab::bound_t get_loop_count_upper_bound(); void initialize_loop_counter(const label_t&); friend std::ostream& operator<<(std::ostream&, const region_domain_t&); @@ -282,16 +169,17 @@ class region_domain_t final { void update_ptr_or_mapfd(crab::ptr_or_mapfd_t&&, const interval_t&&, Bin::Op, const crab::reg_with_loc_t&, uint8_t); - void report_type_error(std::string, location_t); std::optional find_ptr_or_mapfd_type(register_t) const; - size_t ctx_size() const; + [[nodiscard]] size_t ctx_size() const; std::optional find_in_ctx(uint64_t key) const; - std::vector get_ctx_keys() const; + [[nodiscard]] std::vector get_ctx_keys() const; std::optional find_in_stack(uint64_t key) const; std::optional find_ptr_or_mapfd_at_loc(const crab::reg_with_loc_t&) const; - std::vector get_stack_keys() const; + [[nodiscard]] std::vector get_stack_keys() const; bool is_stack_pointer(register_t) const; - void adjust_bb_for_types(location_t loc); + void adjust_bb_for_types(location_t); void print_all_register_types() const; - std::vector& get_errors() { return m_errors; } + [[nodiscard]] std::vector& get_errors() { return m_errors; } }; // end region_domain_t + +} // namespace crab diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index dfe245540..f78c5210d 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -1,79 +1,21 @@ // Copyright (c) Prevail Verifier contributors. // SPDX-License-Identifier: MIT -#include - #include "crab/type_domain.hpp" namespace std { - static ptr_t get_ptr(const ptr_or_mapfd_t& t) { + static crab::ptr_t get_ptr(const crab::ptr_or_mapfd_t& t) { return std::visit( overloaded { - []( const ptr_with_off_t& x ){ return ptr_t{x};}, - []( const ptr_no_off_t& x ){ return ptr_t{x};}, - []( auto& ) { return ptr_t{};} + []( const crab::ptr_with_off_t& x ){ return crab::ptr_t{x};}, + []( const crab::ptr_no_off_t& x ){ return crab::ptr_t{x};}, + []( auto& ) { return crab::ptr_t{};} }, t ); } } -static std::string size(int w) { return std::string("u") + std::to_string(w * 8); } - -static void print_ptr_type(const ptr_t& ptr) { - if (std::holds_alternative(ptr)) { - ptr_with_off_t ptr_with_off = std::get(ptr); - std::cout << ptr_with_off; - } - else { - ptr_no_off_t ptr_no_off = std::get(ptr); - std::cout << ptr_no_off; - } -} - -static void print_ptr_or_mapfd_type(const ptr_or_mapfd_t& ptr_or_mapfd) { - if (std::holds_alternative(ptr_or_mapfd)) { - std::cout << std::get(ptr_or_mapfd); - } - else { - auto ptr = get_ptr(ptr_or_mapfd); - print_ptr_type(ptr); - } -} - -static void print_register(Reg r, std::optional& p) { - std::cout << r << " : "; - if (p) { - print_ptr_or_mapfd_type(p.value()); - } -} - -static void print_annotated(std::ostream& o, const Call& call, std::optional& p) { - o << " "; - print_register(Reg{(uint8_t)R0_RETURN_VALUE}, p); - o << " = " << call.name << ":" << call.func << "(...)\n"; -} - -static void print_annotated(std::ostream& o, const Bin& b, std::optional& p) { - o << " "; - print_register(b.dst, p); - o << " " << b.op << "= " << b.v << "\n"; -} - -static void print_annotated(std::ostream& o, const LoadMapFd& u, std::optional& p) { - o << " "; - print_register(u.dst, p); - o << " = map_fd " << u.mapfd << "\n"; -} - -static void print_annotated(std::ostream& o, const Mem& b, std::optional& p) { - o << " "; - print_register(std::get(b.value), p); - o << " = "; - std::string sign = b.access.offset < 0 ? " - " : " + "; - int offset = std::abs(b.access.offset); - o << "*(" << size(b.access.width) << " *)"; - o << "(" << b.access.basereg << sign << offset << ")\n"; -} +namespace crab { bool type_domain_t::is_bottom() const { return m_is_bottom; @@ -165,14 +107,11 @@ string_invariant type_domain_t::to_set() { return string_invariant{}; } -void type_domain_t::operator()(const Undefined & u, location_t loc, int print) { - m_region(u, loc); -} +void type_domain_t::operator()(const Undefined& u, location_t loc, int print) {} -void type_domain_t::operator()(const Un &u, location_t loc, int print) { -} +void type_domain_t::operator()(const Un& u, location_t loc, int print) {} -void type_domain_t::operator()(const LoadMapFd &u, location_t loc, int print) { +void type_domain_t::operator()(const LoadMapFd& u, location_t loc, int print) { m_region(u, loc); } @@ -183,7 +122,7 @@ void type_domain_t::operator()(const IncrementLoopCounter &u, location_t loc, in // WARNING: Not implemented yet } -void type_domain_t::operator()(const Call &u, location_t loc, int print) { +void type_domain_t::operator()(const Call& u, location_t loc, int print) { for (ArgPair param : u.pairs) { if (param.kind == ArgPair::Kind::PTR_TO_WRITABLE_MEM) { @@ -210,19 +149,20 @@ void type_domain_t::operator()(const Callx &u, location_t loc, int print) { // WARNING: Not implemented yet } -void type_domain_t::operator()(const Exit &u, location_t loc, int print) { - m_region(u, loc); -} +void type_domain_t::operator()(const Exit& u, location_t loc, int print) {} + +void type_domain_t::operator()(const Jmp& u, location_t loc, int print) {} -void type_domain_t::operator()(const Jmp &u, location_t loc, int print) { +void type_domain_t::operator()(const Packet& u, location_t loc, int print) { m_region(u, loc); } -void type_domain_t::operator()(const Packet & u, location_t loc, int print) { - m_region(u, loc); +void type_domain_t::operator()(const Assume& u, location_t loc, int print) { + /* WARNING: The operation is not implemented yet.*/ } -void type_domain_t::operator()(const Assume &u, location_t loc, int print) { +void type_domain_t::operator()(const ValidDivisor& s, location_t loc, int print) { + /* WARNING: The operation is not implemented yet.*/ } void type_domain_t::operator()(const ValidAccess& s, location_t loc, int print) { @@ -233,7 +173,7 @@ void type_domain_t::operator()(const TypeConstraint& s, location_t loc, int prin m_region(s, loc); } -void type_domain_t::operator()(const Assert &u, location_t loc, int print) { +void type_domain_t::operator()(const Assert& u, location_t loc, int print) { std::visit([this, loc, print](const auto& v) { std::apply(*this, std::make_tuple(v, loc, print)); }, u.cst); } @@ -285,8 +225,8 @@ void type_domain_t::operator()(const ValidStore& u, location_t loc, int print) { m_region(u, loc); } - void type_domain_t::operator()(const ValidSize& u, location_t loc, int print) { + m_region(u, loc); } void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int print) { @@ -354,7 +294,7 @@ void type_domain_t::operator()(const ZeroCtxOffset& u, location_t loc, int print } type_domain_t type_domain_t::setup_entry() { - region_domain_t reg = region_domain_t::setup_entry(); + auto&& reg = crab::region_domain_t::setup_entry(); type_domain_t typ(std::move(reg)); return typ; } @@ -468,7 +408,7 @@ void type_domain_t::adjust_bb_for_types(location_t loc) { void type_domain_t::operator()(const basic_block_t& bb, int print) { if (print != 0) { - write(std::cout, bb, print); + print_annotated(std::cout, *this, bb, print); return; } @@ -486,15 +426,29 @@ void type_domain_t::operator()(const basic_block_t& bb, int print) { operator+=(m_region.get_errors()); } -void type_domain_t::write(std::ostream& o, const basic_block_t& bb, int print) const { - if (is_bottom()) { +std::optional +type_domain_t::find_ptr_or_mapfd_at_loc(const crab::reg_with_loc_t& loc) const { + return m_region.find_ptr_or_mapfd_at_loc(loc); +} + +std::ostream& operator<<(std::ostream& o, const type_domain_t& typ) { + typ.write(o); + return o; +} + +} // namespace crab + + +void print_annotated(std::ostream& o, const crab::type_domain_t& typ, + const basic_block_t& bb, int print) { + if (typ.is_bottom()) { o << bb << "\n"; return; } if (print < 0) { o << "state of stack and ctx in program:\n"; - print_ctx(); - print_stack(); + typ.print_ctx(); + typ.print_stack(); o << "\n"; return; } @@ -507,13 +461,13 @@ void type_domain_t::write(std::ostream& o, const basic_block_t& bb, int print) c o << " " << curr_pos << "."; if (std::holds_alternative(statement)) { auto r0_reg = crab::reg_with_loc_t(register_t{R0_RETURN_VALUE}, loc); - auto region = m_region.find_ptr_or_mapfd_at_loc(r0_reg); + auto region = typ.find_ptr_or_mapfd_at_loc(r0_reg); print_annotated(o, std::get(statement), region); } else if (std::holds_alternative(statement)) { auto b = std::get(statement); auto reg_with_loc = crab::reg_with_loc_t(b.dst.v, loc); - auto region = m_region.find_ptr_or_mapfd_at_loc(reg_with_loc); + auto region = typ.find_ptr_or_mapfd_at_loc(reg_with_loc); print_annotated(o, b, region); } else if (std::holds_alternative(statement)) { @@ -521,7 +475,7 @@ void type_domain_t::write(std::ostream& o, const basic_block_t& bb, int print) c if (u.is_load) { auto target_reg = std::get(u.value); auto target_reg_loc = crab::reg_with_loc_t(target_reg.v, loc); - auto region = m_region.find_ptr_or_mapfd_at_loc(target_reg_loc); + auto region = typ.find_ptr_or_mapfd_at_loc(target_reg_loc); print_annotated(o, u, region); } else o << " " << u << "\n"; @@ -529,7 +483,7 @@ void type_domain_t::write(std::ostream& o, const basic_block_t& bb, int print) c else if (std::holds_alternative(statement)) { auto u = std::get(statement); auto reg = crab::reg_with_loc_t(u.dst.v, loc); - auto region = m_region.find_ptr_or_mapfd_at_loc(reg); + auto region = typ.find_ptr_or_mapfd_at_loc(reg); print_annotated(o, u, region); } else o << " " << statement << "\n"; @@ -551,7 +505,3 @@ void type_domain_t::write(std::ostream& o, const basic_block_t& bb, int print) c o << "\n\n"; } -std::ostream& operator<<(std::ostream& o, const type_domain_t& typ) { - typ.write(o); - return o; -} diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 9ed5714f8..d99432492 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -3,25 +3,24 @@ #pragma once -#include - #include "crab/abstract_domain.hpp" #include "crab/region_domain.hpp" -#include "crab/cfg.hpp" -#include "linear_constraint.hpp" -#include "string_constraints.hpp" - -constexpr int NUM_REGISTERS = 11; +#include "crab/common.hpp" using crab::ptr_t; -using crab::ptr_or_mapfd_t; +using crab::variable_t; using crab::ptr_with_off_t; using crab::ptr_no_off_t; +using crab::region_domain_t; using crab::mapfd_t; +using crab::ptr_or_mapfd_t; using crab::region_t; +using crab::interval_t; + +namespace crab { class type_domain_t final { - region_domain_t m_region; + crab::region_domain_t m_region; bool m_is_bottom = false; std::vector m_errors; @@ -30,7 +29,7 @@ class type_domain_t final { type_domain_t() = default; type_domain_t(type_domain_t&& o) = default; type_domain_t(const type_domain_t& o) = default; - explicit type_domain_t(region_domain_t&& reg, bool is_bottom = false) : + explicit type_domain_t(crab::region_domain_t&& reg, bool is_bottom = false) : m_region(reg), m_is_bottom(is_bottom) {} type_domain_t& operator=(type_domain_t&& o) = default; type_domain_t& operator=(const type_domain_t& o) = default; @@ -59,19 +58,19 @@ class type_domain_t final { void operator-=(crab::variable_t var); //// abstract transformers - void operator()(const Undefined &, location_t loc = boost::none, int print = 0); - void operator()(const Bin &, location_t loc = boost::none, int print = 0); - void operator()(const Un &, location_t loc = boost::none, int print = 0); - void operator()(const LoadMapFd &, location_t loc = boost::none, int print = 0); + void operator()(const Undefined&, location_t loc = boost::none, int print = 0); + void operator()(const Bin&, location_t loc = boost::none, int print = 0); + void operator()(const Un&, location_t loc = boost::none, int print = 0); + void operator()(const LoadMapFd&, location_t loc = boost::none, int print = 0); void operator()(const Atomic&, location_t loc = boost::none, int print = 0); - void operator()(const Call &, location_t loc = boost::none, int print = 0); + void operator()(const Call&, location_t loc = boost::none, int print = 0); void operator()(const Callx&, location_t loc = boost::none, int print = 0); - void operator()(const Exit &, location_t loc = boost::none, int print = 0); - void operator()(const Jmp &, location_t loc = boost::none, int print = 0); - void operator()(const Mem &, location_t loc = boost::none, int print = 0); - void operator()(const Packet &, location_t loc = boost::none, int print = 0); - void operator()(const Assume &, location_t loc = boost::none, int print = 0); - void operator()(const Assert &, location_t loc = boost::none, int print = 0); + void operator()(const Exit&, location_t loc = boost::none, int print = 0); + void operator()(const Jmp&, location_t loc = boost::none, int print = 0); + void operator()(const Mem&, location_t loc = boost::none, int print = 0); + void operator()(const Packet&, location_t loc = boost::none, int print = 0); + void operator()(const Assume&, location_t loc = boost::none, int print = 0); + void operator()(const Assert&, location_t loc = boost::none, int print = 0); void operator()(const ValidAccess&, location_t loc = boost::none, int print = 0); void operator()(const Comparable&, location_t loc = boost::none, int print = 0); void operator()(const Addable&, location_t loc = boost::none, int print = 0); @@ -80,18 +79,20 @@ class type_domain_t final { void operator()(const ValidSize&, location_t loc = boost::none, int print = 0); void operator()(const ValidMapKeyValue&, location_t loc = boost::none, int print = 0); void operator()(const ZeroCtxOffset&, location_t loc = boost::none, int print = 0); - void operator()(const ValidDivisor& s, location_t loc = boost::none, int print = 0) {} + void operator()(const ValidDivisor&, location_t loc = boost::none, int print = 0); void operator()(const FuncConstraint& s, location_t loc = boost::none, int print = 0) {}; void operator()(const IncrementLoopCounter&, location_t loc = boost::none, int print = 0); void operator()(const basic_block_t& bb, int print = 0); - void write(std::ostream& os, const basic_block_t&, int) const; void write(std::ostream& os) const {} friend std::ostream& operator<<(std::ostream& o, const type_domain_t& dom); void initialize_loop_counter(label_t label); crab::bound_t get_loop_count_upper_bound(); string_invariant to_set(); void set_require_check(check_require_func_t f) {} - std::vector& get_errors() { return m_errors; } + [[nodiscard]] std::vector& get_errors() { return m_errors; } + void print_ctx() const; + void print_stack() const; + std::optional find_ptr_or_mapfd_at_loc(const crab::reg_with_loc_t&) const; private: @@ -99,10 +100,12 @@ class type_domain_t final { void do_mem_store(const Mem&, const Reg&, location_t, int print = 0); void report_type_error(std::string, location_t); void print_registers() const; - void print_ctx() const; - void print_stack() const; void adjust_bb_for_types(location_t); void operator+=(std::vector& errs) { m_errors.insert(m_errors.end(), errs.begin(), errs.end()); } }; // end type_domain_t + +} // namespace crab + +void print_annotated(std::ostream&, const crab::type_domain_t&, const basic_block_t&, int); diff --git a/src/crab_verifier.cpp b/src/crab_verifier.cpp index fea511dbb..962ced1a7 100644 --- a/src/crab_verifier.cpp +++ b/src/crab_verifier.cpp @@ -24,6 +24,7 @@ #include "string_constraints.hpp" using crab::ebpf_domain_t; +using crab::type_domain_t; using crab::linear_constraint_t; thread_local crab::lazy_allocator global_program_info; From f92c2f2b39da7a1180c72b51959bd1b4e7044ed7 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Thu, 13 Jul 2023 19:36:55 -0400 Subject: [PATCH 102/373] Attempt to add functionality/fix code in reference to latest PREVAIL List of added changes/fixes: 1) ZeroCtxOffset now only checks offsets for Ctx pointers, as the name suggests; 2) Better support for TypeConstraint assertions, and added TypeGroup::singleton_ptr; 3) Small fixes/additions in Bin and Load operations; 4) Refactor Signed-off-by: Ameer Hamza --- src/crab/region_domain.cpp | 120 +++++++++++++++++++++---------------- src/crab/region_domain.hpp | 3 +- src/crab/type_domain.cpp | 50 +++++++--------- 3 files changed, 93 insertions(+), 80 deletions(-) diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index 4b46901ab..8d46c00f8 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -411,15 +411,21 @@ void region_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int } void region_domain_t::operator()(const ZeroCtxOffset& u, location_t loc, int print) { - // nothing to do here + if (is_ctx_pointer(u.reg.v)) { + auto maybe_ptr_or_mapfd = m_registers.find(u.reg.v); + auto ctx_ptr = std::get(maybe_ptr_or_mapfd.value()); + if (ctx_ptr.get_offset() == interval_t{crab::number_t{0}}) return; + } + //std::cout << "type error: Zero Offset assertion fail\n"; + m_errors.push_back("Zero Ctx Offset assertion fail"); } void region_domain_t::operator()(const basic_block_t& bb, int print) { // nothing to do here } -void region_domain_t::operator()(const Un &u, location_t loc, int print) { - /* WARNING: The operation is not implemented yet.*/ +void region_domain_t::operator()(const Un& u, location_t loc, int print) { + // nothing to do here } void region_domain_t::operator()(const ValidDivisor& u, location_t loc, int print) { @@ -433,7 +439,8 @@ void region_domain_t::operator()(const ValidSize& u, location_t loc, int print) void region_domain_t::operator()(const LoadMapFd &u, location_t loc, int print) { auto reg = u.dst.v; auto reg_with_loc = reg_with_loc_t(reg, loc); - const EbpfMapDescriptor& desc = global_program_info.get().platform->get_map_descriptor(u.mapfd); + const EbpfMapDescriptor& desc + = global_program_info.get().platform->get_map_descriptor(u.mapfd); const EbpfMapValueType& map_value_type = global_program_info.get().platform-> get_map_type(desc.type).value_type; map_key_size_t map_key_size = desc.key_size; @@ -589,37 +596,11 @@ region_domain_t&& region_domain_t::setup_entry() { } void region_domain_t::operator()(const TypeConstraint& s, location_t loc, int print) { - auto it = find_ptr_or_mapfd_type(s.reg.v); - if (it) { + auto ptr_or_mapfd_opt = m_registers.find(s.reg.v); + if (ptr_or_mapfd_opt) { // it is a pointer or mapfd - ptr_or_mapfd_t ptr_or_mapfd_type = it.value(); - if (std::holds_alternative(ptr_or_mapfd_type)) { - if (s.types == TypeGroup::non_map_fd) return; - if (s.types == TypeGroup::pointer || s.types == TypeGroup::ptr_or_num) return; - ptr_with_off_t ptr_with_off = std::get(ptr_or_mapfd_type); - if (ptr_with_off.get_region() == crab::region_t::T_CTX) { - if (s.types == TypeGroup::ctx) return; - } - else if (ptr_with_off.get_region() == crab::region_t::T_SHARED) { - if (s.types == TypeGroup::shared || s.types == TypeGroup::mem - || s.types == TypeGroup::mem_or_num) return; - } - else { - if (s.types == TypeGroup::stack || s.types == TypeGroup::mem - || s.types == TypeGroup::stack_or_packet - || s.types == TypeGroup::mem_or_num) { - return; - } - } - } - else if (std::holds_alternative(ptr_or_mapfd_type)) { - if (s.types == TypeGroup::non_map_fd) return; - if (s.types == TypeGroup::pointer || s.types == TypeGroup::ptr_or_num) return; - if (s.types == TypeGroup::packet || s.types == TypeGroup::mem - || s.types == TypeGroup::mem_or_num - || s.types == TypeGroup::stack_or_packet) return; - } - else { + auto ptr_or_mapfd_type = ptr_or_mapfd_opt.value(); + if (std::holds_alternative(ptr_or_mapfd_type)) { auto map_fd = std::get(ptr_or_mapfd_type); if (map_fd.has_type_map_programs()) { if (s.types == TypeGroup::map_fd_programs) return; @@ -627,6 +608,33 @@ void region_domain_t::operator()(const TypeConstraint& s, location_t loc, int pr if (s.types == TypeGroup::map_fd) return; } } + else { + if (s.types == TypeGroup::pointer || s.types == TypeGroup::ptr_or_num) return; + if (s.types == TypeGroup::non_map_fd) return; + if (std::holds_alternative(ptr_or_mapfd_type)) { + ptr_with_off_t ptr_with_off = std::get(ptr_or_mapfd_type); + if (ptr_with_off.get_region() == crab::region_t::T_CTX) { + if (s.types == TypeGroup::singleton_ptr) return; + if (s.types == TypeGroup::ctx) return; + } + else { + if (s.types == TypeGroup::mem || s.types == TypeGroup::mem_or_num) return; + if (ptr_with_off.get_region() == crab::region_t::T_SHARED) { + if (s.types == TypeGroup::shared) return; + } + else { + if (s.types == TypeGroup::singleton_ptr) return; + if (s.types == TypeGroup::stack || s.types == TypeGroup::stack_or_packet) + return; + } + } + } + else if (std::holds_alternative(ptr_or_mapfd_type)) { + if (s.types == TypeGroup::singleton_ptr) return; + if (s.types == TypeGroup::mem || s.types == TypeGroup::mem_or_num) return; + if (s.types == TypeGroup::packet || s.types == TypeGroup::stack_or_packet) return; + } + } } else { // if we don't know the type, we assume it is a number @@ -640,11 +648,11 @@ void region_domain_t::operator()(const TypeConstraint& s, location_t loc, int pr } void region_domain_t::update_ptr_or_mapfd(ptr_or_mapfd_t&& ptr_or_mapfd, const interval_t&& change, - Bin::Op op, const reg_with_loc_t& reg_with_loc, uint8_t reg) { + const reg_with_loc_t& reg_with_loc, uint8_t reg) { if (std::holds_alternative(ptr_or_mapfd)) { auto ptr_or_mapfd_with_off = std::get(ptr_or_mapfd); auto offset = ptr_or_mapfd_with_off.get_offset(); - auto updated_offset = op == Bin::Op::ADD ? offset + change : offset - change; + auto updated_offset = change == interval_t::top() ? offset : offset + change; ptr_or_mapfd_with_off.set_offset(updated_offset); m_registers.insert(reg, reg_with_loc, ptr_or_mapfd_with_off); } @@ -711,13 +719,13 @@ interval_t region_domain_t::do_bin(const Bin& bin, // b is a numerical register, or a constant else if (dst_ptr_or_mapfd_opt && src_interval_opt) { update_ptr_or_mapfd(std::move(dst_ptr_or_mapfd), std::move(src_interval), - bin.op, reg, bin.dst.v); + reg, bin.dst.v); } // b is a pointer/mapfd // ra is a numerical register else if (src_ptr_or_mapfd_opt && !dst_ptr_or_mapfd_opt) { update_ptr_or_mapfd(std::move(src_ptr_or_mapfd), interval_t::top(), - bin.op, reg, bin.dst.v); + reg, bin.dst.v); } break; } @@ -754,16 +762,14 @@ interval_t region_domain_t::do_bin(const Bin& bin, } // b is a numerical register, or a constant else if (dst_ptr_or_mapfd_opt && src_interval_opt) { - update_ptr_or_mapfd(std::move(dst_ptr_or_mapfd), std::move(src_interval), - bin.op, reg, bin.dst.v); + update_ptr_or_mapfd(std::move(dst_ptr_or_mapfd), -std::move(src_interval), + reg, bin.dst.v); } break; } - default: { - m_registers -= bin.dst.v; - break; - } + default: break; } + m_registers -= bin.dst.v; return interval_t::bottom(); } @@ -773,15 +779,15 @@ void region_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t lo int offset = b.access.offset; Reg basereg = b.access.basereg; - auto it = m_registers.find(basereg.v); - if (!it) { + auto ptr_or_mapfd_opt = m_registers.find(basereg.v); + if (!ptr_or_mapfd_opt) { std::string s = std::to_string(static_cast(basereg.v)); std::string desc = std::string("\tloading from an unknown pointer, or from number - r") + s + "\n"; //std::cout << desc; m_registers -= target_reg.v; return; } - auto type_basereg = it.value(); + auto type_basereg = ptr_or_mapfd_opt.value(); if (!std::holds_alternative(type_basereg) || std::get(type_basereg).get_region() == crab::region_t::T_SHARED) { @@ -810,17 +816,21 @@ void region_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t lo m_registers -= target_reg.v; } else { + if (width != 1 && width != 2 && width != 4 && width != 8) { + m_registers -= target_reg.v; + return; + } auto ptr_offset = offset_singleton.value(); auto load_at = (uint64_t)(ptr_offset + offset); - auto it = m_stack.find(load_at); + auto ptr_or_mapfd_opt = m_stack.find(load_at); - if (!it) { + if (!ptr_or_mapfd_opt) { // no field at loaded offset in stack m_registers -= target_reg.v; return; } - auto type_loaded = it.value(); + auto type_loaded = ptr_or_mapfd_opt.value(); auto reg = reg_with_loc_t(target_reg.v, loc); m_registers.insert(target_reg.v, reg, type_loaded.first); @@ -945,6 +955,16 @@ void region_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location } } +bool region_domain_t::is_ctx_pointer(register_t reg) const { + auto type = m_registers.find(reg); + if (!type) { // not a pointer + return false; + } + auto ptr_or_mapfd_type = type.value(); + return (std::holds_alternative(ptr_or_mapfd_type) && + std::get(ptr_or_mapfd_type).get_region() == crab::region_t::T_CTX); +} + bool region_domain_t::is_stack_pointer(register_t reg) const { auto type = m_registers.find(reg); diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index 3bd2111ba..7d8ba1f79 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -166,7 +166,7 @@ class region_domain_t final { interval_t do_bin(const Bin&, const std::optional&, const std::optional&, const std::optional&, location_t); - void update_ptr_or_mapfd(crab::ptr_or_mapfd_t&&, const interval_t&&, Bin::Op, + void update_ptr_or_mapfd(crab::ptr_or_mapfd_t&&, const interval_t&&, const crab::reg_with_loc_t&, uint8_t); std::optional find_ptr_or_mapfd_type(register_t) const; @@ -177,6 +177,7 @@ class region_domain_t final { std::optional find_ptr_or_mapfd_at_loc(const crab::reg_with_loc_t&) const; [[nodiscard]] std::vector get_stack_keys() const; bool is_stack_pointer(register_t) const; + bool is_ctx_pointer(register_t) const; void adjust_bb_for_types(location_t); void print_all_register_types() const; [[nodiscard]] std::vector& get_errors() { return m_errors; } diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index f78c5210d..0722ea3fe 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -109,7 +109,9 @@ string_invariant type_domain_t::to_set() { void type_domain_t::operator()(const Undefined& u, location_t loc, int print) {} -void type_domain_t::operator()(const Un& u, location_t loc, int print) {} +void type_domain_t::operator()(const Un& u, location_t loc, int print) { + /* WARNING: The operation is not implemented yet.*/ +} void type_domain_t::operator()(const LoadMapFd& u, location_t loc, int print) { m_region(u, loc); @@ -138,6 +140,7 @@ void type_domain_t::operator()(const Call& u, location_t loc, int print) { m_errors.push_back("storing at an unknown offset in stack"); continue; } + // TODO: forget the stack at [offset, offset+width] } } } @@ -162,7 +165,7 @@ void type_domain_t::operator()(const Assume& u, location_t loc, int print) { } void type_domain_t::operator()(const ValidDivisor& s, location_t loc, int print) { - /* WARNING: The operation is not implemented yet.*/ + m_region(s, loc); } void type_domain_t::operator()(const ValidAccess& s, location_t loc, int print) { @@ -198,10 +201,10 @@ void type_domain_t::operator()(const Comparable& u, location_t loc, int print) { // an extra check just to make sure registers are not labelled both ptrs and numbers auto ptr_or_mapfd1 = maybe_ptr_or_mapfd1.value(); auto ptr_or_mapfd2 = maybe_ptr_or_mapfd1.value(); - if (is_mapfd_type(ptr_or_mapfd1) && is_mapfd_type(ptr_or_mapfd2)) { - return; - } - else if (!is_mapfd_type(ptr_or_mapfd1) && !is_mapfd_type(ptr_or_mapfd2)) { + auto is_mapfd1 = is_mapfd_type(ptr_or_mapfd1); + auto is_mapfd2 = is_mapfd_type(ptr_or_mapfd2); + if (is_mapfd1 && is_mapfd2) return; + else if (!is_mapfd1 && !is_mapfd2) { auto ptr1 = get_ptr(ptr_or_mapfd1); auto ptr2 = get_ptr(ptr_or_mapfd2); if (get_region(ptr1) == get_region(ptr2)) { @@ -226,7 +229,7 @@ void type_domain_t::operator()(const ValidStore& u, location_t loc, int print) { } void type_domain_t::operator()(const ValidSize& u, location_t loc, int print) { - m_region(u, loc); + /* WARNING: The operation is not implemented yet.*/ } void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int print) { @@ -264,11 +267,8 @@ void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int pr } } else if (std::holds_alternative(ptr_or_mapfd_basereg)) { - auto ptr_no_off = std::get(ptr_or_mapfd_basereg); - if (ptr_no_off.get_region() == region_t::T_PACKET) { - // We do not check packet ptr accesses yet - return; - } + // We do not check packet ptr accesses yet + return; } } } @@ -277,20 +277,7 @@ void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int pr } void type_domain_t::operator()(const ZeroCtxOffset& u, location_t loc, int print) { - - auto maybe_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(u.reg.v); - if (maybe_ptr_or_mapfd) { - if (std::holds_alternative(maybe_ptr_or_mapfd.value())) { - auto ptr_type_with_off = std::get(maybe_ptr_or_mapfd.value()); - if (ptr_type_with_off.get_offset() == interval_t{crab::number_t{0}}) return; - } - else if (std::holds_alternative(maybe_ptr_or_mapfd.value())) { - // We do not yet support packet ptr offsets - return; - } - } - //std::cout << "type error: Zero Offset assertion fail\n"; - m_errors.push_back("Zero Offset assertion fail"); + m_region(u, loc); } type_domain_t type_domain_t::setup_entry() { @@ -310,8 +297,14 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { src_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(r.v); } else { - auto imm = std::get(bin.v); - src_interval = interval_t{crab::number_t{static_cast(imm.v)}}; + int64_t imm; + if (bin.is64) { + imm = static_cast(std::get(bin.v).v); + } + else { + imm = static_cast(std::get(bin.v).v); + } + src_interval = interval_t{crab::number_t{imm}}; } using Op = Bin::Op; @@ -348,7 +341,6 @@ void type_domain_t::operator()(const Mem& b, location_t loc, int print) { std::string desc = std::string("\tEither loading to a number (not allowed) or storing a number (not allowed yet) - ") + s + "\n"; //std::cout << desc; m_errors.push_back(desc); - return; } } From 47cdb950f446f05d65ab23ad2b7e8c3c8ecc6a17 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Fri, 14 Jul 2023 14:10:14 -0400 Subject: [PATCH 103/373] Refactor and fix equal_to template specialization issues Refactor: Restructured the code better, and moved most of printing code to a separate file; removed redundant code; Fixed template specialization of equal_to for ptr_t and ptr_or_mapfd_t Signed-off-by: Ameer Hamza --- src/crab/common.cpp | 112 ++++++++----------------------------- src/crab/common.hpp | 66 ++++++++++++++++++---- src/crab/region_domain.cpp | 1 - src/crab/region_domain.hpp | 7 +-- src/crab/type_domain.cpp | 15 +---- src/crab/type_domain.hpp | 10 ---- src/crab/type_ostream.cpp | 63 +++++++++++++++++++++ src/crab/type_ostream.hpp | 17 ++++++ 8 files changed, 161 insertions(+), 130 deletions(-) create mode 100644 src/crab/type_ostream.cpp create mode 100644 src/crab/type_ostream.hpp diff --git a/src/crab/common.cpp b/src/crab/common.cpp index fd7d894ae..8ac692868 100644 --- a/src/crab/common.cpp +++ b/src/crab/common.cpp @@ -1,17 +1,10 @@ // Copyright (c) Prevail Verifier contributors. // SPDX-License-Identifier: MIT -#pragma once - #include "crab/common.hpp" namespace std { - template <> - struct hash { - size_t operator()(const crab::reg_with_loc_t& reg) const { return reg.hash(); } - }; - - static crab::ptr_t get_ptr(const crab::ptr_or_mapfd_t& t) { + crab::ptr_t get_ptr(const crab::ptr_or_mapfd_t& t) { return std::visit( overloaded { []( const crab::ptr_with_off_t& x ){ return crab::ptr_t{x};}, @@ -20,10 +13,32 @@ namespace std { }, t ); } -} +} // namespace std namespace crab { +bool ptr_with_off_t::operator==(const ptr_with_off_t& other) const { + return (m_r == other.m_r && m_offset == other.m_offset + && m_region_size == other.m_region_size); +} + +bool ptr_with_off_t::operator!=(const ptr_with_off_t& other) const { + return !(*this == other); +} + +bool ptr_no_off_t::operator==(const ptr_no_off_t& other) const { + return (m_r == other.m_r); +} + +bool ptr_no_off_t::operator!=(const ptr_no_off_t& other) const { + return !(*this == other); +} + +bool mapfd_t::operator==(const mapfd_t& other) const { + return (m_mapfd == other.m_mapfd && m_key_size == other.m_key_size + && m_value_size == other.m_value_size); +} + bool same_region(const ptr_t& ptr1, const ptr_t& ptr2) { return ((std::holds_alternative(ptr1) && std::holds_alternative(ptr2)) @@ -36,15 +51,6 @@ inline std::ostream& operator<<(std::ostream& o, const region_t& t) { return o; } -bool operator==(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { - return (p1.get_region() == p2.get_region() && p1.get_offset() == p2.get_offset() - && p1.get_region_size() == p2.get_region_size()); -} - -bool operator!=(const ptr_with_off_t& p1, const ptr_with_off_t& p2) { - return !(p1 == p2); -} - interval_t ptr_with_off_t::get_region_size() const { return m_region_size; } void ptr_with_off_t::set_offset(interval_t off) { m_offset = off; } @@ -57,20 +63,8 @@ ptr_with_off_t ptr_with_off_t::operator|(const ptr_with_off_t& other) const { return ptr_with_off_t(m_r, m_offset | other.m_offset, m_region_size | other.m_region_size); } -bool operator==(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { - return (p1.get_region() == p2.get_region()); -} - -bool operator!=(const ptr_no_off_t& p1, const ptr_no_off_t& p2) { - return !(p1 == p2); -} - void ptr_no_off_t::set_region(region_t r) { m_r = r; } -bool operator==(const mapfd_t& m1, const mapfd_t& m2) { - return (m1.get_value_type() == m2.get_value_type()); -} - std::ostream& operator<<(std::ostream& o, const mapfd_t& m) { m.write(o); return o; @@ -148,61 +142,3 @@ std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p) { } } // namespace crab - -void print_ptr_type(const crab::ptr_t& ptr) { - if (std::holds_alternative(ptr)) { - crab::ptr_with_off_t ptr_with_off = std::get(ptr); - std::cout << ptr_with_off; - } - else { - crab::ptr_no_off_t ptr_no_off = std::get(ptr); - std::cout << ptr_no_off; - } -} - -void print_ptr_or_mapfd_type(const crab::ptr_or_mapfd_t& ptr_or_mapfd) { - if (std::holds_alternative(ptr_or_mapfd)) { - std::cout << std::get(ptr_or_mapfd); - } - else { - auto ptr = get_ptr(ptr_or_mapfd); - print_ptr_type(ptr); - } -} - -void print_register(Reg r, std::optional& p) { - std::cout << r << " : "; - if (p) { - print_ptr_or_mapfd_type(p.value()); - } -} - -inline std::string size_(int w) { return std::string("u") + std::to_string(w * 8); } - -void print_annotated(std::ostream& o, const Call& call, std::optional& p) { - o << " "; - print_register(Reg{(uint8_t)R0_RETURN_VALUE}, p); - o << " = " << call.name << ":" << call.func << "(...)\n"; -} - -void print_annotated(std::ostream& o, const Bin& b, std::optional& p) { - o << " "; - print_register(b.dst, p); - o << " " << b.op << "= " << b.v << "\n"; -} - -void print_annotated(std::ostream& o, const LoadMapFd& u, std::optional& p) { - o << " "; - print_register(u.dst, p); - o << " = map_fd " << u.mapfd << "\n"; -} - -void print_annotated(std::ostream& o, const Mem& b, std::optional& p) { - o << " "; - print_register(std::get(b.value), p); - o << " = "; - std::string sign = b.access.offset < 0 ? " - " : " + "; - int offset = std::abs(b.access.offset); - o << "*(" << size_(b.access.width) << " *)"; - o << "(" << b.access.basereg << sign << offset << ")\n"; -} diff --git a/src/crab/common.hpp b/src/crab/common.hpp index 3d894ec38..a66fbd082 100644 --- a/src/crab/common.hpp +++ b/src/crab/common.hpp @@ -3,10 +3,13 @@ #pragma once -#include "linear_constraint.hpp" +#include +#include +#include +#include + #include "string_constraints.hpp" #include "asm_syntax.hpp" -#include "asm_ostream.hpp" constexpr int NUM_REGISTERS = 11; @@ -39,12 +42,14 @@ class ptr_no_off_t { void set_region(region_t); void write(std::ostream&) const; friend std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p); + bool operator==(const ptr_no_off_t&) const; + bool operator!=(const ptr_no_off_t&) const; }; class ptr_with_off_t { region_t m_r; interval_t m_offset; - interval_t m_region_size; + interval_t m_region_size = interval_t::top(); public: ptr_with_off_t() = default; @@ -52,8 +57,9 @@ class ptr_with_off_t { ptr_with_off_t(ptr_with_off_t &&) = default; ptr_with_off_t &operator=(const ptr_with_off_t &) = default; ptr_with_off_t &operator=(ptr_with_off_t &&) = default; - ptr_with_off_t(region_t _r, interval_t _off, interval_t _region_sz=interval_t::top()) + ptr_with_off_t(region_t _r, interval_t _off, interval_t _region_sz) : m_r(_r), m_offset(_off), m_region_size(_region_sz) {} + ptr_with_off_t(region_t _r, interval_t _off) : m_r(_r), m_offset(_off) {} ptr_with_off_t operator|(const ptr_with_off_t&) const; interval_t get_region_size() const; void set_region_size(interval_t); @@ -63,6 +69,8 @@ class ptr_with_off_t { void set_region(region_t); void write(std::ostream&) const; friend std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p); + bool operator==(const ptr_with_off_t&) const; + bool operator!=(const ptr_with_off_t&) const; }; using map_key_size_t = unsigned int; @@ -83,6 +91,8 @@ class mapfd_t { map_value_size_t value_size) : m_mapfd(mapfd), m_value_type(val_type), m_key_size(key_size), m_value_size(value_size) {} friend std::ostream& operator<<(std::ostream&, const mapfd_t&); + bool operator==(const mapfd_t&) const; + bool operator!=(const mapfd_t&) const; void write(std::ostream&) const; bool has_type_map_programs() const; @@ -115,12 +125,46 @@ using ptr_or_mapfd_types_t = std::map; using live_registers_t = std::array, 11>; using global_region_env_t = std::unordered_map; +bool same_region(const ptr_t& ptr1, const ptr_t& ptr2); + } // namespace crab -void print_ptr_or_mapfd_type(const crab::ptr_or_mapfd_t&); -void print_ptr_type(const crab::ptr_t& ptr); -void print_register(Reg r, std::optional& p); -void print_annotated(std::ostream& o, const Call& call, std::optional& p); -void print_annotated(std::ostream& o, const Bin& b, std::optional& p); -void print_annotated(std::ostream& o, const LoadMapFd& u, std::optional& p); -void print_annotated(std::ostream& o, const Mem& b, std::optional& p); + +namespace std { + template <> + struct hash { + size_t operator()(const crab::reg_with_loc_t& reg) const { return reg.hash(); } + }; + + template <> + struct equal_to { + constexpr bool operator()(const crab::ptr_t& lhs, const crab::ptr_t& rhs) const { + if (lhs.index() != rhs.index()) return false; + return std::visit( overloaded + { + []( const crab::ptr_with_off_t& x, const crab::ptr_with_off_t& y ){ return x == y;}, + []( const crab::ptr_no_off_t& x, const crab::ptr_no_off_t& y ){ return x == y;}, + []( auto& , auto& ) { return true;} + }, lhs, rhs + ); + } + }; + + template <> + struct equal_to { + constexpr bool operator()(const crab::ptr_or_mapfd_t& lhs, const crab::ptr_or_mapfd_t& rhs) const { + if (lhs.index() != rhs.index()) return false; + return std::visit( overloaded + { + []( const crab::ptr_with_off_t& x, const crab::ptr_with_off_t& y ){ return x == y;}, + []( const crab::ptr_no_off_t& x, const crab::ptr_no_off_t& y ){ return x == y;}, + []( const crab::mapfd_t& x, const crab::mapfd_t& y ){ return x == y;}, + []( auto& , auto& ) { return true;} + }, lhs, rhs + ); + } + }; + + crab::ptr_t get_ptr(const crab::ptr_or_mapfd_t& t); +} + diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index 8d46c00f8..cf0f98904 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: MIT #include "crab/region_domain.hpp" -#include "crab/common.cpp" namespace crab { diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index 7d8ba1f79..370a80def 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -3,15 +3,10 @@ #pragma once -#include -#include - #include "crab/abstract_domain.hpp" #include "crab/common.hpp" +#include "crab/type_ostream.hpp" #include "crab/cfg.hpp" -#include "linear_constraint.hpp" -#include "string_constraints.hpp" -#include #include "platform.hpp" diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 0722ea3fe..02e16221a 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -1,19 +1,7 @@ // Copyright (c) Prevail Verifier contributors. // SPDX-License-Identifier: MIT -#include "crab/type_domain.hpp" - -namespace std { - static crab::ptr_t get_ptr(const crab::ptr_or_mapfd_t& t) { - return std::visit( overloaded - { - []( const crab::ptr_with_off_t& x ){ return crab::ptr_t{x};}, - []( const crab::ptr_no_off_t& x ){ return crab::ptr_t{x};}, - []( auto& ) { return crab::ptr_t{};} - }, t - ); - } -} +#include "type_domain.hpp" namespace crab { @@ -430,7 +418,6 @@ std::ostream& operator<<(std::ostream& o, const type_domain_t& typ) { } // namespace crab - void print_annotated(std::ostream& o, const crab::type_domain_t& typ, const basic_block_t& bb, int print) { if (typ.is_bottom()) { diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index d99432492..0d67185ce 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -7,16 +7,6 @@ #include "crab/region_domain.hpp" #include "crab/common.hpp" -using crab::ptr_t; -using crab::variable_t; -using crab::ptr_with_off_t; -using crab::ptr_no_off_t; -using crab::region_domain_t; -using crab::mapfd_t; -using crab::ptr_or_mapfd_t; -using crab::region_t; -using crab::interval_t; - namespace crab { class type_domain_t final { diff --git a/src/crab/type_ostream.cpp b/src/crab/type_ostream.cpp new file mode 100644 index 000000000..c6057c12d --- /dev/null +++ b/src/crab/type_ostream.cpp @@ -0,0 +1,63 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT + +#include "crab/type_ostream.hpp" + +void print_ptr_type(const crab::ptr_t& ptr) { + if (std::holds_alternative(ptr)) { + crab::ptr_with_off_t ptr_with_off = std::get(ptr); + std::cout << ptr_with_off; + } + else { + crab::ptr_no_off_t ptr_no_off = std::get(ptr); + std::cout << ptr_no_off; + } +} + +void print_ptr_or_mapfd_type(const crab::ptr_or_mapfd_t& ptr_or_mapfd) { + if (std::holds_alternative(ptr_or_mapfd)) { + std::cout << std::get(ptr_or_mapfd); + } + else { + auto ptr = get_ptr(ptr_or_mapfd); + print_ptr_type(ptr); + } +} + +void print_register(Reg r, std::optional& p) { + std::cout << r << " : "; + if (p) { + print_ptr_or_mapfd_type(p.value()); + } +} + +inline std::string size_(int w) { return std::string("u") + std::to_string(w * 8); } + +void print_annotated(std::ostream& o, const Call& call, std::optional& p) { + o << " "; + print_register(Reg{(uint8_t)R0_RETURN_VALUE}, p); + o << " = " << call.name << ":" << call.func << "(...)\n"; +} + +void print_annotated(std::ostream& o, const Bin& b, std::optional& p) { + o << " "; + print_register(b.dst, p); + o << " " << b.op << "= " << b.v << "\n"; +} + +void print_annotated(std::ostream& o, const LoadMapFd& u, std::optional& p) { + o << " "; + print_register(u.dst, p); + o << " = map_fd " << u.mapfd << "\n"; +} + +void print_annotated(std::ostream& o, const Mem& b, std::optional& p) { + o << " "; + print_register(std::get(b.value), p); + o << " = "; + std::string sign = b.access.offset < 0 ? " - " : " + "; + int offset = std::abs(b.access.offset); + o << "*(" << size_(b.access.width) << " *)"; + o << "(" << b.access.basereg << sign << offset << ")\n"; +} + diff --git a/src/crab/type_ostream.hpp b/src/crab/type_ostream.hpp new file mode 100644 index 000000000..87ebfe4ba --- /dev/null +++ b/src/crab/type_ostream.hpp @@ -0,0 +1,17 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT + +#pragma once + +#include "string_constraints.hpp" +#include "asm_syntax.hpp" +#include "asm_ostream.hpp" +#include "crab/common.hpp" + +void print_ptr_or_mapfd_type(const crab::ptr_or_mapfd_t&); +void print_ptr_type(const crab::ptr_t& ptr); +void print_register(Reg r, std::optional& p); +void print_annotated(std::ostream& o, const Call& call, std::optional& p); +void print_annotated(std::ostream& o, const Bin& b, std::optional& p); +void print_annotated(std::ostream& o, const LoadMapFd& u, std::optional& p); +void print_annotated(std::ostream& o, const Mem& b, std::optional& p); From 16f23ab4d555c9862a7f3b309136a61e823056f5 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Sat, 15 Jul 2023 02:13:34 -0400 Subject: [PATCH 104/373] Refactor Refactor/clean up a lot of code; Simplify many pointer region checks by using functions, especially in memory operations; Move some generic checks in memory operations to type domain Signed-off-by: Ameer Hamza --- src/crab/common.cpp | 14 +- src/crab/common.hpp | 43 ++++- src/crab/region_domain.cpp | 319 ++++++++++++++----------------------- src/crab/region_domain.hpp | 10 +- src/crab/type_domain.cpp | 59 +++---- src/crab/type_domain.hpp | 2 +- src/crab/type_ostream.cpp | 31 ++-- src/crab/type_ostream.hpp | 4 +- 8 files changed, 207 insertions(+), 275 deletions(-) diff --git a/src/crab/common.cpp b/src/crab/common.cpp index 8ac692868..d0d2e31e0 100644 --- a/src/crab/common.cpp +++ b/src/crab/common.cpp @@ -3,6 +3,7 @@ #include "crab/common.hpp" +/* namespace std { crab::ptr_t get_ptr(const crab::ptr_or_mapfd_t& t) { return std::visit( overloaded @@ -14,6 +15,7 @@ namespace std { ); } } // namespace std +*/ namespace crab { @@ -39,18 +41,6 @@ bool mapfd_t::operator==(const mapfd_t& other) const { && m_value_size == other.m_value_size); } -bool same_region(const ptr_t& ptr1, const ptr_t& ptr2) { - return ((std::holds_alternative(ptr1) - && std::holds_alternative(ptr2)) - || (std::holds_alternative(ptr1) - && std::holds_alternative(ptr2))); -} - -inline std::ostream& operator<<(std::ostream& o, const region_t& t) { - o << static_cast::type>(t); - return o; -} - interval_t ptr_with_off_t::get_region_size() const { return m_region_size; } void ptr_with_off_t::set_offset(interval_t off) { m_offset = off; } diff --git a/src/crab/common.hpp b/src/crab/common.hpp index a66fbd082..849ab624b 100644 --- a/src/crab/common.hpp +++ b/src/crab/common.hpp @@ -119,13 +119,44 @@ class reg_with_loc_t { }; using ptr_or_mapfd_t = std::variant; -using ptr_or_mapfd_cells_t = std::pair; -using ptr_or_mapfd_types_t = std::map; -using live_registers_t = std::array, 11>; -using global_region_env_t = std::unordered_map; +inline bool is_mapfd_type(const std::optional& ptr_or_mapfd) { + return (ptr_or_mapfd && std::holds_alternative(*ptr_or_mapfd)); +} + +inline bool same_region(const ptr_or_mapfd_t& ptr1, const ptr_or_mapfd_t& ptr2) { + if (std::holds_alternative(ptr1) && std::holds_alternative(ptr2)) + return true; + return (std::holds_alternative(ptr1) + && std::holds_alternative(ptr2) + && std::get(ptr1).get_region() + == std::get(ptr2).get_region()); +} + +inline bool is_stack_ptr(const std::optional& ptr) { + return (ptr && std::holds_alternative(*ptr) + && std::get(*ptr).get_region() == region_t::T_STACK); +} + +inline bool is_ctx_ptr(const std::optional& ptr) { + return (ptr && std::holds_alternative(*ptr) + && std::get(*ptr).get_region() == region_t::T_CTX); +} + +inline bool is_packet_ptr(const std::optional& ptr) { + return (ptr && std::holds_alternative(*ptr)); +} + +inline bool is_shared_ptr(const std::optional& ptr) { + return (ptr && std::holds_alternative(*ptr) + && std::get(*ptr).get_region() == region_t::T_SHARED); +} + +inline std::ostream& operator<<(std::ostream& o, const region_t& t) { + o << static_cast::type>(t); + return o; +} -bool same_region(const ptr_t& ptr1, const ptr_t& ptr2); } // namespace crab @@ -165,6 +196,6 @@ namespace std { } }; - crab::ptr_t get_ptr(const crab::ptr_or_mapfd_t& t); + //crab::ptr_t get_ptr(const crab::ptr_or_mapfd_t& t); } diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index cf0f98904..f74ad1f1f 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -53,26 +53,21 @@ register_types_t register_types_t::operator|(const register_types_t& other) cons for (size_t i = 0; i < m_cur_def.size(); i++) { if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; - auto it1 = find(*(m_cur_def[i])); - auto it2 = other.find(*(other.m_cur_def[i])); - if (it1 && it2) { - ptr_or_mapfd_t ptr_or_mapfd1 = it1.value(), ptr_or_mapfd2 = it2.value(); + auto maybe_ptr1 = find(*(m_cur_def[i])); + auto maybe_ptr2 = other.find(*(other.m_cur_def[i])); + if (maybe_ptr1 && maybe_ptr2) { + ptr_or_mapfd_t ptr_or_mapfd1 = maybe_ptr1.value(), ptr_or_mapfd2 = maybe_ptr2.value(); auto reg = reg_with_loc_t((register_t)i, loc); if (ptr_or_mapfd1 == ptr_or_mapfd2) { out_vars[i] = m_cur_def[i]; } - else if (!std::holds_alternative(ptr_or_mapfd1) - && !std::holds_alternative(ptr_or_mapfd2)) { - auto ptr1 = get_ptr(ptr_or_mapfd1); - auto ptr2 = get_ptr(ptr_or_mapfd2); - if (std::holds_alternative(ptr1) - && std::holds_alternative(ptr2)) { - ptr_with_off_t ptr_with_off1 = std::get(ptr1); - ptr_with_off_t ptr_with_off2 = std::get(ptr2); - if (ptr_with_off1.get_region() == ptr_with_off2.get_region()) { - out_vars[i] = std::make_shared(reg); - (*m_region_env)[reg] = std::move(ptr_with_off1 | ptr_with_off2); - } + else if (std::holds_alternative(ptr_or_mapfd1) + && std::holds_alternative(ptr_or_mapfd2)) { + ptr_with_off_t ptr_with_off1 = std::get(ptr_or_mapfd1); + ptr_with_off_t ptr_with_off2 = std::get(ptr_or_mapfd2); + if (ptr_with_off1.get_region() == ptr_with_off2.get_region()) { + out_vars[i] = std::make_shared(reg); + (*m_region_env)[reg] = std::move(ptr_with_off1 | ptr_with_off2); } } } @@ -101,7 +96,7 @@ bool register_types_t::is_bottom() const { return m_is_bottom; } bool register_types_t::is_top() const { if (m_is_bottom) { return false; } if (m_region_env == nullptr) return true; - for (auto it : m_cur_def) { + for (auto &it : m_cur_def) { if (it != nullptr) return false; } return true; @@ -117,7 +112,7 @@ void register_types_t::print_all_register_types() const { std::cout << "\tregion types: {\n"; for (auto const& kv : *m_region_env) { std::cout << "\t\t" << kv.first << " : "; - print_ptr_or_mapfd_type(kv.second); + print_ptr_or_mapfd_type(std::cout, kv.second); std::cout << "\n"; } std::cout << "\t}\n"; @@ -410,9 +405,9 @@ void region_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int } void region_domain_t::operator()(const ZeroCtxOffset& u, location_t loc, int print) { - if (is_ctx_pointer(u.reg.v)) { - auto maybe_ptr_or_mapfd = m_registers.find(u.reg.v); - auto ctx_ptr = std::get(maybe_ptr_or_mapfd.value()); + auto maybe_ptr_or_mapfd = m_registers.find(u.reg.v); + if (is_ctx_ptr(maybe_ptr_or_mapfd)) { + auto ctx_ptr = std::get(*maybe_ptr_or_mapfd); if (ctx_ptr.get_offset() == interval_t{crab::number_t{0}}) return; } //std::cout << "type error: Zero Offset assertion fail\n"; @@ -438,10 +433,9 @@ void region_domain_t::operator()(const ValidSize& u, location_t loc, int print) void region_domain_t::operator()(const LoadMapFd &u, location_t loc, int print) { auto reg = u.dst.v; auto reg_with_loc = reg_with_loc_t(reg, loc); - const EbpfMapDescriptor& desc - = global_program_info.get().platform->get_map_descriptor(u.mapfd); - const EbpfMapValueType& map_value_type = global_program_info.get().platform-> - get_map_type(desc.type).value_type; + auto platform = global_program_info->platform; + const EbpfMapDescriptor& desc = platform->get_map_descriptor(u.mapfd); + const EbpfMapValueType& map_value_type = platform->get_map_type(desc.type).value_type; map_key_size_t map_key_size = desc.key_size; map_value_size_t map_value_size = desc.value_size; auto type = mapfd_t(u.mapfd, map_value_type, map_key_size, map_value_size); @@ -467,11 +461,12 @@ void region_domain_t::operator()(const Call &u, location_t loc, int print) { return; } auto mapfd = std::get(ptr_or_mapfd.value()); - auto map_desc = global_program_info.get().platform->get_map_descriptor(mapfd.get_mapfd()); + auto platform = global_program_info->platform; + auto map_desc = platform->get_map_descriptor(mapfd.get_mapfd()); if (mapfd.get_value_type() == EbpfMapValueType::MAP) { - const EbpfMapDescriptor& inner_map_desc = global_program_info.get().platform-> + const EbpfMapDescriptor& inner_map_desc = platform-> get_map_descriptor(map_desc.inner_map_fd); - const EbpfMapValueType& inner_map_value_type = global_program_info.get().platform-> + const EbpfMapValueType& inner_map_value_type = platform-> get_map_type(inner_map_desc.type).value_type; map_key_size_t inner_map_key_size = inner_map_desc.key_size; map_value_size_t inner_map_value_size = inner_map_desc.value_size; @@ -525,7 +520,7 @@ void region_domain_t::operator()(const ValidAccess &s, location_t loc, int print auto maybe_ptr_or_mapfd_type = m_registers.find(s.reg.v); if (maybe_ptr_or_mapfd_type) { - auto reg_ptr_or_mapfd_type = maybe_ptr_or_mapfd_type.value(); + auto reg_ptr_or_mapfd_type = *maybe_ptr_or_mapfd_type; if (std::holds_alternative(reg_ptr_or_mapfd_type)) { auto reg_with_off_ptr_type = std::get(reg_ptr_or_mapfd_type); auto offset = reg_with_off_ptr_type.get_offset(); @@ -566,7 +561,7 @@ void region_domain_t::operator()(const ValidAccess &s, location_t loc, int print void region_domain_t::operator()(const ValidStore& u, location_t loc, int print) { - bool is_stack_p = is_stack_pointer(u.mem.v); + bool is_stack_p = is_stack_ptr(m_registers.find(u.val.v)); auto maybe_ptr_type2 = m_registers.find(u.val.v); if (is_stack_p || !maybe_ptr_type2) { @@ -686,6 +681,7 @@ interval_t region_domain_t::do_bin(const Bin& bin, if (src_interval_opt) src_interval = std::move(src_interval_opt.value()); auto reg = reg_with_loc_t(bin.dst.v, loc); + interval_t to_return = interval_t::bottom(); switch (bin.op) { @@ -705,7 +701,7 @@ interval_t region_domain_t::do_bin(const Bin& bin, case Op::ADD: { // adding pointer to another if (src_ptr_or_mapfd_opt && dst_ptr_or_mapfd_opt) { - if (is_stack_pointer(bin.dst.v)) + if (is_stack_ptr(dst_ptr_or_mapfd)) m_stack.set_to_top(); else { // TODO: handle other cases properly @@ -738,22 +734,15 @@ interval_t region_domain_t::do_bin(const Bin& bin, //std::cout << "type error: mapfd registers subtraction not defined\n"; m_errors.push_back("mapfd registers subtraction not defined"); } - else if (std::holds_alternative(dst_ptr_or_mapfd) && - std::holds_alternative(src_ptr_or_mapfd)) { - auto dst_ptr_or_mapfd_with_off = std::get(dst_ptr_or_mapfd); - auto src_ptr_or_mapfd_with_off = std::get(src_ptr_or_mapfd); - if (dst_ptr_or_mapfd_with_off.get_region() - == src_ptr_or_mapfd_with_off.get_region()) { - m_registers -= bin.dst.v; - return (dst_ptr_or_mapfd_with_off.get_offset() - - src_ptr_or_mapfd_with_off.get_offset()); - } - else { - //std::cout << "type error: subtraction between pointers of different region\n"; - m_errors.push_back("subtraction between pointers of different region"); + else if (same_region(dst_ptr_or_mapfd, src_ptr_or_mapfd)) { + if (std::holds_alternative(dst_ptr_or_mapfd) && + std::holds_alternative(src_ptr_or_mapfd)) { + auto dst_ptr_with_off = std::get(dst_ptr_or_mapfd); + auto src_ptr_with_off = std::get(src_ptr_or_mapfd); + to_return = dst_ptr_with_off.get_offset() - src_ptr_with_off.get_offset(); } } - else if (!same_region(get_ptr(dst_ptr_or_mapfd), get_ptr(src_ptr_or_mapfd))) { + else { //std::cout << "type error: subtraction between pointers of different region\n"; m_errors.push_back("subtraction between pointers of different region"); } @@ -768,109 +757,94 @@ interval_t region_domain_t::do_bin(const Bin& bin, } default: break; } - m_registers -= bin.dst.v; - return interval_t::bottom(); + return to_return; } -void region_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc) { +void region_domain_t::do_load(const Mem& b, const Reg& target_reg, bool unknown_ptr, + location_t loc) { + + if (unknown_ptr) { + m_registers -= target_reg.v; + return; + } int width = b.access.width; int offset = b.access.offset; Reg basereg = b.access.basereg; auto ptr_or_mapfd_opt = m_registers.find(basereg.v); - if (!ptr_or_mapfd_opt) { - std::string s = std::to_string(static_cast(basereg.v)); - std::string desc = std::string("\tloading from an unknown pointer, or from number - r") + s + "\n"; - //std::cout << desc; + bool is_stack_p = is_stack_ptr(ptr_or_mapfd_opt); + bool is_ctx_p = is_ctx_ptr(ptr_or_mapfd_opt); + if (!is_ctx_p && !is_stack_p) { + // loading from either packet or shared region or mapfd does not happen in region domain m_registers -= target_reg.v; return; } - auto type_basereg = ptr_or_mapfd_opt.value(); - if (!std::holds_alternative(type_basereg) - || std::get(type_basereg).get_region() == crab::region_t::T_SHARED) { - // loading from either packet, shared region or mapfd does not happen in region domain - m_registers -= target_reg.v; - return; - } - - auto type_with_off = std::get(type_basereg); + auto type_with_off = std::get(*ptr_or_mapfd_opt); auto p_offset = type_with_off.get_offset(); auto offset_singleton = p_offset.singleton(); - switch (type_with_off.get_region()) { - case crab::region_t::T_STACK: { - if (!offset_singleton) { - for (auto const& k : m_stack.get_keys()) { - auto start = p_offset.lb(); - auto end = p_offset.ub()+number_t{offset+width-1}; - interval_t range{start, end}; - if (range[number_t{(int)k}]) { - //std::cout << "stack load at unknown offset, and offset range contains pointers\n"; - m_errors.push_back("stack load at unknown offset, and offset range contains pointers"); - break; - } + if (is_stack_p) { + if (!offset_singleton) { + for (auto const& k : m_stack.get_keys()) { + auto start = p_offset.lb(); + auto end = p_offset.ub()+number_t{offset+width-1}; + interval_t range{start, end}; + if (range[number_t{(int)k}]) { + //std::cout << "stack load at unknown offset, and offset range contains pointers\n"; + m_errors.push_back("stack load at unknown offset, and offset range contains pointers"); + break; } + } + m_registers -= target_reg.v; + } + else { + if (width != 1 && width != 2 && width != 4 && width != 8) { m_registers -= target_reg.v; + return; } - else { - if (width != 1 && width != 2 && width != 4 && width != 8) { - m_registers -= target_reg.v; - return; - } - auto ptr_offset = offset_singleton.value(); - auto load_at = (uint64_t)(ptr_offset + offset); + auto ptr_offset = offset_singleton.value(); + auto load_at = (uint64_t)(ptr_offset + offset); - auto ptr_or_mapfd_opt = m_stack.find(load_at); - - if (!ptr_or_mapfd_opt) { - // no field at loaded offset in stack - m_registers -= target_reg.v; - return; - } - auto type_loaded = ptr_or_mapfd_opt.value(); - - auto reg = reg_with_loc_t(target_reg.v, loc); - m_registers.insert(target_reg.v, reg, type_loaded.first); + auto loaded = m_stack.find(load_at); + if (!loaded) { + // no field at loaded offset in stack + m_registers -= target_reg.v; + return; } - break; + + auto reg = reg_with_loc_t(target_reg.v, loc); + m_registers.insert(target_reg.v, reg, (*loaded).first); } - case crab::region_t::T_CTX: { - - if (!offset_singleton) { - for (auto const& k : m_ctx->get_keys()) { - auto start = p_offset.lb(); - auto end = p_offset.ub()+crab::bound_t{offset+width-1}; - interval_t range{start, end}; - if (range[number_t{(int)k}]) { - //std::cout << "ctx load at unknown offset, and offset range contains pointers\n"; - m_errors.push_back("ctx load at unknown offset, and offset range contains pointers"); - break; - } + } + else { + if (!offset_singleton) { + for (auto const& k : m_ctx->get_keys()) { + auto start = p_offset.lb(); + auto end = p_offset.ub()+crab::bound_t{offset+width-1}; + interval_t range{start, end}; + if (range[number_t{(int)k}]) { + //std::cout << "ctx load at unknown offset, and offset range contains pointers\n"; + m_errors.push_back("ctx load at unknown offset, and offset range contains pointers"); + break; } - m_registers -= target_reg.v; } - else { - auto ptr_offset = offset_singleton.value(); - auto load_at = (uint64_t)(ptr_offset + offset); - auto it = m_ctx->find(load_at); - - if (!it) { - // no field at loaded offset in ctx - m_registers -= target_reg.v; - return; - } - ptr_no_off_t type_loaded = it.value(); + m_registers -= target_reg.v; + } + else { + auto ptr_offset = offset_singleton.value(); + auto load_at = (uint64_t)(ptr_offset + offset); - auto reg = reg_with_loc_t(target_reg.v, loc); - m_registers.insert(target_reg.v, reg, type_loaded); + auto loaded = m_ctx->find(load_at); + if (!loaded) { + // no field at loaded offset in ctx + m_registers -= target_reg.v; + return; } - break; - } - default: { - assert(false); + auto reg = reg_with_loc_t(target_reg.v, loc); + m_registers.insert(target_reg.v, reg, *loaded); } } } @@ -885,94 +859,45 @@ void region_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location Reg basereg = b.access.basereg; int width = b.access.width; - // TODO: move generic checks to type domain auto maybe_basereg_type = m_registers.find(basereg.v); - if (!maybe_basereg_type) { - std::string s = std::to_string(static_cast(basereg.v)); - std::string desc = std::string("\tstoring at an unknown pointer, or from number - r") + s + "\n"; - //std::cout << desc; - m_errors.push_back(desc); - return; - } auto basereg_type = maybe_basereg_type.value(); auto targetreg_type = m_registers.find(target_reg.v); - if (std::holds_alternative(basereg_type)) { - // base register is either CTX_P, STACK_P or SHARED_P - auto basereg_type_with_off = std::get(basereg_type); + bool is_ctx_p = is_ctx_ptr(maybe_basereg_type); + bool is_shared_p = is_shared_ptr(maybe_basereg_type); + bool is_packet_p = is_packet_ptr(maybe_basereg_type); + bool is_mapfd = is_mapfd_type(maybe_basereg_type); - if (basereg_type_with_off.get_region() == crab::region_t::T_STACK) { - auto offset_singleton = basereg_type_with_off.get_offset().singleton(); - if (!offset_singleton) { - //std::cout << "type error: storing to a pointer with unknown offset\n"; - m_errors.push_back("storing to a pointer with unknown offset"); - return; - } - auto store_at = (uint64_t)offset+(uint64_t)offset_singleton.value(); - // type of basereg is STACK_P - auto overlapping_cells = m_stack.find_overlapping_cells(store_at, width); - m_stack -= overlapping_cells; - - // if targetreg_type is empty, we are storing a number - if (!targetreg_type) return; - - auto type_to_store = targetreg_type.value(); - m_stack.store(store_at, type_to_store, width); - } - else if (basereg_type_with_off.get_region() == crab::region_t::T_CTX) { - // type of basereg is CTX_P - if (targetreg_type) { - std::string s = std::to_string(static_cast(target_reg.v)); - std::string desc = std::string("\twe cannot store a pointer, r") + s + ", into ctx\n"; - //std::cout << desc; - m_errors.push_back(desc); - return; - } - } - else { - // type of basereg is SHARED_P - if (targetreg_type) { - //std::cout << "type error: we cannot store a pointer into shared\n"; - m_errors.push_back("we cannot store a pointer into shared"); - } - } + if (is_mapfd) { + m_errors.push_back("storing into a mapfd register is not defined"); + return; } - else if (std::holds_alternative(basereg_type)) { - // base register type is a PACKET_P + if (is_shared_p || is_packet_p || is_ctx_p) { if (targetreg_type) { - std::string s = std::to_string(static_cast(target_reg.v)); - std::string desc = std::string("\twe cannot store a pointer, r") + s + ", into packet\n"; - //std::cout << desc; - m_errors.push_back(desc); + m_errors.push_back("storing a pointer into a shared, packet or ctx pointer"); + return; + } + else { + // storing a number into a region does not affect the region return; } } - else { - //std::cout << "type error: we cannot store a pointer into a mapfd\n"; - m_errors.push_back("we cannot store a pointer into a mapfd"); - return; - } -} -bool region_domain_t::is_ctx_pointer(register_t reg) const { - auto type = m_registers.find(reg); - if (!type) { // not a pointer - return false; + // if the code reaches here, we are storing into a stack pointer + auto basereg_type_with_off = std::get(basereg_type); + auto offset_singleton = basereg_type_with_off.get_offset().singleton(); + if (!offset_singleton) { + //std::cout << "type error: storing to a pointer with unknown offset\n"; + m_errors.push_back("storing to a pointer with unknown offset"); + return; } - auto ptr_or_mapfd_type = type.value(); - return (std::holds_alternative(ptr_or_mapfd_type) && - std::get(ptr_or_mapfd_type).get_region() == crab::region_t::T_CTX); -} - + auto store_at = (uint64_t)offset+(uint64_t)offset_singleton.value(); + auto overlapping_cells = m_stack.find_overlapping_cells(store_at, width); + m_stack -= overlapping_cells; -bool region_domain_t::is_stack_pointer(register_t reg) const { - auto type = m_registers.find(reg); - if (!type) { // not a pointer - return false; - } - auto ptr_or_mapfd_type = type.value(); - return (std::holds_alternative(ptr_or_mapfd_type) && - std::get(ptr_or_mapfd_type).get_region() == crab::region_t::T_STACK); + // if targetreg_type is empty, we are storing a number + if (!targetreg_type) return; + m_stack.store(store_at, *targetreg_type, width); } void region_domain_t::adjust_bb_for_types(location_t loc) { diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index 370a80def..8bcefa56d 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -25,6 +25,9 @@ class ctx_t { std::optional find(uint64_t key) const; }; +using ptr_or_mapfd_cells_t = std::pair; +using ptr_or_mapfd_types_t = std::map; + class stack_t { ptr_or_mapfd_types_t m_ptrs; bool m_is_bottom; @@ -51,6 +54,9 @@ class stack_t { size_t size() const; }; +using live_registers_t = std::array, 11>; +using global_region_env_t = std::unordered_map; + class register_types_t { live_registers_t m_cur_def; @@ -156,7 +162,7 @@ class region_domain_t final { string_invariant to_set(); void set_require_check(check_require_func_t f) {} - void do_load(const Mem&, const Reg&, location_t); + void do_load(const Mem&, const Reg&, bool, location_t); void do_mem_store(const Mem&, const Reg&, location_t); interval_t do_bin(const Bin&, const std::optional&, const std::optional&, @@ -171,8 +177,6 @@ class region_domain_t final { std::optional find_in_stack(uint64_t key) const; std::optional find_ptr_or_mapfd_at_loc(const crab::reg_with_loc_t&) const; [[nodiscard]] std::vector get_stack_keys() const; - bool is_stack_pointer(register_t) const; - bool is_ctx_pointer(register_t) const; void adjust_bb_for_types(location_t); void print_all_register_types() const; [[nodiscard]] std::vector& get_errors() { return m_errors; } diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 02e16221a..90983e3d6 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -168,37 +168,14 @@ void type_domain_t::operator()(const Assert& u, location_t loc, int print) { std::visit([this, loc, print](const auto& v) { std::apply(*this, std::make_tuple(v, loc, print)); }, u.cst); } -static bool is_mapfd_type(const ptr_or_mapfd_t& ptr_or_mapfd) { - return (std::holds_alternative(ptr_or_mapfd)); -} - -static region_t get_region(const ptr_t& ptr) { - if (std::holds_alternative(ptr)) { - return std::get(ptr).get_region(); - } - else { - return std::get(ptr).get_region(); - } -} - void type_domain_t::operator()(const Comparable& u, location_t loc, int print) { auto maybe_ptr_or_mapfd1 = m_region.find_ptr_or_mapfd_type(u.r1.v); auto maybe_ptr_or_mapfd2 = m_region.find_ptr_or_mapfd_type(u.r2.v); if (maybe_ptr_or_mapfd1 && maybe_ptr_or_mapfd2) { // an extra check just to make sure registers are not labelled both ptrs and numbers - auto ptr_or_mapfd1 = maybe_ptr_or_mapfd1.value(); - auto ptr_or_mapfd2 = maybe_ptr_or_mapfd1.value(); - auto is_mapfd1 = is_mapfd_type(ptr_or_mapfd1); - auto is_mapfd2 = is_mapfd_type(ptr_or_mapfd2); - if (is_mapfd1 && is_mapfd2) return; - else if (!is_mapfd1 && !is_mapfd2) { - auto ptr1 = get_ptr(ptr_or_mapfd1); - auto ptr2 = get_ptr(ptr_or_mapfd2); - if (get_region(ptr1) == get_region(ptr2)) { - return; - } - } + if (is_mapfd_type(maybe_ptr_or_mapfd1) && is_mapfd_type(maybe_ptr_or_mapfd2)) return; + if (same_region(*maybe_ptr_or_mapfd1, *maybe_ptr_or_mapfd2)) return; } else if (!maybe_ptr_or_mapfd1 && !maybe_ptr_or_mapfd2) { // all other cases when we do not have a ptr or mapfd, the type is a number @@ -226,16 +203,15 @@ void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int pr auto maybe_ptr_or_mapfd_basereg = m_region.find_ptr_or_mapfd_type(u.access_reg.v); auto maybe_mapfd = m_region.find_ptr_or_mapfd_type(u.map_fd_reg.v); if (maybe_ptr_or_mapfd_basereg && maybe_mapfd) { - auto ptr_or_mapfd_basereg = maybe_ptr_or_mapfd_basereg.value(); - auto mapfd = maybe_mapfd.value(); - if (is_mapfd_type(mapfd)) { - auto mapfd_type = std::get(mapfd); + if (is_mapfd_type(maybe_mapfd)) { + auto mapfd_type = std::get(*maybe_mapfd); if (u.key) { width = (int)mapfd_type.get_key_size(); } else { width = (int)mapfd_type.get_value_size(); } + auto ptr_or_mapfd_basereg = maybe_ptr_or_mapfd_basereg.value(); if (std::holds_alternative(ptr_or_mapfd_basereg)) { auto ptr_with_off = std::get(ptr_or_mapfd_basereg); if (ptr_with_off.get_region() == region_t::T_STACK) { @@ -308,21 +284,30 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { m_region.do_bin(bin, src_interval, src_ptr_or_mapfd, dst_ptr_or_mapfd, loc); } -void type_domain_t::do_load(const Mem& b, const Reg& target_reg, location_t loc, int print) { - m_region.do_load(b, target_reg, loc); +void type_domain_t::do_load(const Mem& b, const Reg& target_reg, bool unknown_ptr, + location_t loc, int print) { + m_region.do_load(b, target_reg, unknown_ptr, loc); } void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t loc, int print) { - m_region.do_mem_store(b, target_reg, loc); } void type_domain_t::operator()(const Mem& b, location_t loc, int print) { if (std::holds_alternative(b.value)) { + auto basereg = std::get(b.value); + auto ptr_or_mapfd_opt = m_region.find_ptr_or_mapfd_type(basereg.v); + bool unknown_ptr = !ptr_or_mapfd_opt.has_value(); + if (unknown_ptr) { + std::string s = std::to_string(static_cast(basereg.v)); + m_errors.push_back( + std::string("load/store using an unknown pointer, or number - r") + s); + } + if (b.is_load) { - do_load(b, std::get(b.value), loc, print); - } else { - do_mem_store(b, std::get(b.value), loc, print); + do_load(b, basereg, unknown_ptr, loc, print); + } else if (!unknown_ptr) { + do_mem_store(b, basereg, loc, print); } } else { std::string s = std::to_string(static_cast(std::get(b.value).v)); @@ -357,7 +342,7 @@ void type_domain_t::print_ctx() const { auto ptr = m_region.find_in_ctx(k); if (ptr) { std::cout << " " << k << ": "; - print_ptr_type(ptr.value()); + print_ptr_type(std::cout, ptr_or_mapfd_t{ptr.value()}); std::cout << ",\n"; } } @@ -374,7 +359,7 @@ void type_domain_t::print_stack() const { int width = ptr_or_mapfd_cells.second; auto ptr_or_mapfd = ptr_or_mapfd_cells.first; std::cout << " [" << k << "-" << k+width-1 << "] : "; - print_ptr_or_mapfd_type(ptr_or_mapfd); + print_ptr_or_mapfd_type(std::cout, ptr_or_mapfd); std::cout << ",\n"; } } diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 0d67185ce..d27c29f80 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -86,7 +86,7 @@ class type_domain_t final { private: - void do_load(const Mem&, const Reg&, location_t, int print = 0); + void do_load(const Mem&, const Reg&, bool, location_t, int print = 0); void do_mem_store(const Mem&, const Reg&, location_t, int print = 0); void report_type_error(std::string, location_t); void print_registers() const; diff --git a/src/crab/type_ostream.cpp b/src/crab/type_ostream.cpp index c6057c12d..47c7e2001 100644 --- a/src/crab/type_ostream.cpp +++ b/src/crab/type_ostream.cpp @@ -3,31 +3,28 @@ #include "crab/type_ostream.hpp" -void print_ptr_type(const crab::ptr_t& ptr) { +void print_ptr_type(std::ostream& o, const crab::ptr_or_mapfd_t& ptr) { if (std::holds_alternative(ptr)) { - crab::ptr_with_off_t ptr_with_off = std::get(ptr); - std::cout << ptr_with_off; + o << std::get(ptr); } - else { - crab::ptr_no_off_t ptr_no_off = std::get(ptr); - std::cout << ptr_no_off; + else if (std::holds_alternative(ptr)) { + o << std::get(ptr); } } -void print_ptr_or_mapfd_type(const crab::ptr_or_mapfd_t& ptr_or_mapfd) { +void print_ptr_or_mapfd_type(std::ostream& o, const crab::ptr_or_mapfd_t& ptr_or_mapfd) { if (std::holds_alternative(ptr_or_mapfd)) { - std::cout << std::get(ptr_or_mapfd); + o << std::get(ptr_or_mapfd); } else { - auto ptr = get_ptr(ptr_or_mapfd); - print_ptr_type(ptr); + print_ptr_type(o, ptr_or_mapfd); } } -void print_register(Reg r, std::optional& p) { - std::cout << r << " : "; +void print_register(std::ostream& o, Reg r, std::optional& p) { + o << r << " : "; if (p) { - print_ptr_or_mapfd_type(p.value()); + print_ptr_or_mapfd_type(o, p.value()); } } @@ -35,25 +32,25 @@ inline std::string size_(int w) { return std::string("u") + std::to_string(w * 8 void print_annotated(std::ostream& o, const Call& call, std::optional& p) { o << " "; - print_register(Reg{(uint8_t)R0_RETURN_VALUE}, p); + print_register(o, Reg{(uint8_t)R0_RETURN_VALUE}, p); o << " = " << call.name << ":" << call.func << "(...)\n"; } void print_annotated(std::ostream& o, const Bin& b, std::optional& p) { o << " "; - print_register(b.dst, p); + print_register(o, b.dst, p); o << " " << b.op << "= " << b.v << "\n"; } void print_annotated(std::ostream& o, const LoadMapFd& u, std::optional& p) { o << " "; - print_register(u.dst, p); + print_register(o, u.dst, p); o << " = map_fd " << u.mapfd << "\n"; } void print_annotated(std::ostream& o, const Mem& b, std::optional& p) { o << " "; - print_register(std::get(b.value), p); + print_register(o, std::get(b.value), p); o << " = "; std::string sign = b.access.offset < 0 ? " - " : " + "; int offset = std::abs(b.access.offset); diff --git a/src/crab/type_ostream.hpp b/src/crab/type_ostream.hpp index 87ebfe4ba..6761d9724 100644 --- a/src/crab/type_ostream.hpp +++ b/src/crab/type_ostream.hpp @@ -8,8 +8,8 @@ #include "asm_ostream.hpp" #include "crab/common.hpp" -void print_ptr_or_mapfd_type(const crab::ptr_or_mapfd_t&); -void print_ptr_type(const crab::ptr_t& ptr); +void print_ptr_or_mapfd_type(std::ostream&, const crab::ptr_or_mapfd_t&); +void print_ptr_type(std::ostream&, const crab::ptr_or_mapfd_t& ptr); void print_register(Reg r, std::optional& p); void print_annotated(std::ostream& o, const Call& call, std::optional& p); void print_annotated(std::ostream& o, const Bin& b, std::optional& p); From 8401e6d8b31579e3932ccd81a599415c5a3c19be Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Sat, 15 Jul 2023 16:36:51 -0400 Subject: [PATCH 105/373] Fix an error in Mem operation that slipped during previous commit Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 90983e3d6..73f1806be 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -295,7 +295,8 @@ void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t void type_domain_t::operator()(const Mem& b, location_t loc, int print) { if (std::holds_alternative(b.value)) { - auto basereg = std::get(b.value); + auto targetreg = std::get(b.value); + auto basereg = b.access.basereg; auto ptr_or_mapfd_opt = m_region.find_ptr_or_mapfd_type(basereg.v); bool unknown_ptr = !ptr_or_mapfd_opt.has_value(); if (unknown_ptr) { @@ -305,16 +306,12 @@ void type_domain_t::operator()(const Mem& b, location_t loc, int print) { } if (b.is_load) { - do_load(b, basereg, unknown_ptr, loc, print); + do_load(b, targetreg, unknown_ptr, loc, print); } else if (!unknown_ptr) { - do_mem_store(b, basereg, loc, print); + do_mem_store(b, targetreg, loc, print); } - } else { - std::string s = std::to_string(static_cast(std::get(b.value).v)); - std::string desc = std::string("\tEither loading to a number (not allowed) or storing a number (not allowed yet) - ") + s + "\n"; - //std::cout << desc; - m_errors.push_back(desc); } + else {} } // the method does not work well as it requires info about the label of basic block we are in From 46c198a5410ee4c2a9affc251adf85a6d3265dfe Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Sun, 16 Jul 2023 02:03:39 -0400 Subject: [PATCH 106/373] Better handling of map_fd Much better handling for map_fd, that is closer to the PREVAIL implementation, in order to avoid possible errors; Remove redundant implementation and code; Fix joins with respect to map_fd Signed-off-by: Ameer Hamza --- src/crab/common.cpp | 8 +- src/crab/common.hpp | 17 ++-- src/crab/region_domain.cpp | 174 ++++++++++++++++++++++++++++--------- src/crab/region_domain.hpp | 5 ++ src/crab/type_domain.cpp | 9 +- 5 files changed, 148 insertions(+), 65 deletions(-) diff --git a/src/crab/common.cpp b/src/crab/common.cpp index d0d2e31e0..f27047e92 100644 --- a/src/crab/common.cpp +++ b/src/crab/common.cpp @@ -37,8 +37,12 @@ bool ptr_no_off_t::operator!=(const ptr_no_off_t& other) const { } bool mapfd_t::operator==(const mapfd_t& other) const { - return (m_mapfd == other.m_mapfd && m_key_size == other.m_key_size - && m_value_size == other.m_value_size); + return (m_mapfd == other.m_mapfd); +} + +mapfd_t mapfd_t::operator|(const mapfd_t& other) const { + auto value_type = m_value_type == other.m_value_type ? m_value_type : EbpfMapValueType::ANY; + return mapfd_t(m_mapfd | other.m_mapfd, value_type); } interval_t ptr_with_off_t::get_region_size() const { return m_region_size; } diff --git a/src/crab/common.hpp b/src/crab/common.hpp index 849ab624b..e3a3d5524 100644 --- a/src/crab/common.hpp +++ b/src/crab/common.hpp @@ -73,23 +73,18 @@ class ptr_with_off_t { bool operator!=(const ptr_with_off_t&) const; }; -using map_key_size_t = unsigned int; -using map_value_size_t = unsigned int; - class mapfd_t { - int m_mapfd; + interval_t m_mapfd; EbpfMapValueType m_value_type; - map_key_size_t m_key_size; - map_value_size_t m_value_size; public: mapfd_t(const mapfd_t&) = default; mapfd_t(mapfd_t&&) = default; mapfd_t &operator=(const mapfd_t&) = default; mapfd_t &operator=(mapfd_t&&) = default; - mapfd_t(int mapfd, EbpfMapValueType val_type, map_key_size_t key_size, - map_value_size_t value_size) - : m_mapfd(mapfd), m_value_type(val_type), m_key_size(key_size), m_value_size(value_size) {} + mapfd_t operator|(const mapfd_t&) const; + mapfd_t(interval_t mapfd, EbpfMapValueType val_type) + : m_mapfd(mapfd), m_value_type(val_type) {} friend std::ostream& operator<<(std::ostream&, const mapfd_t&); bool operator==(const mapfd_t&) const; bool operator!=(const mapfd_t&) const; @@ -97,9 +92,7 @@ class mapfd_t { bool has_type_map_programs() const; [[nodiscard]] EbpfMapValueType get_value_type() const { return m_value_type; } - [[nodiscard]] map_key_size_t get_key_size() const { return m_key_size; } - [[nodiscard]] map_value_size_t get_value_size() const { return m_value_size; } - [[nodiscard]] int get_mapfd() const { return m_mapfd; } + [[nodiscard]] interval_t get_mapfd() const { return m_mapfd; } }; using ptr_t = std::variant; diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index f74ad1f1f..426b13495 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -61,13 +61,23 @@ register_types_t register_types_t::operator|(const register_types_t& other) cons if (ptr_or_mapfd1 == ptr_or_mapfd2) { out_vars[i] = m_cur_def[i]; } - else if (std::holds_alternative(ptr_or_mapfd1) - && std::holds_alternative(ptr_or_mapfd2)) { - ptr_with_off_t ptr_with_off1 = std::get(ptr_or_mapfd1); - ptr_with_off_t ptr_with_off2 = std::get(ptr_or_mapfd2); - if (ptr_with_off1.get_region() == ptr_with_off2.get_region()) { - out_vars[i] = std::make_shared(reg); - (*m_region_env)[reg] = std::move(ptr_with_off1 | ptr_with_off2); + else { + auto shared_reg = std::make_shared(reg); + if (std::holds_alternative(ptr_or_mapfd1) + && std::holds_alternative(ptr_or_mapfd2)) { + ptr_with_off_t ptr_with_off1 = std::get(ptr_or_mapfd1); + ptr_with_off_t ptr_with_off2 = std::get(ptr_or_mapfd2); + if (ptr_with_off1.get_region() == ptr_with_off2.get_region()) { + out_vars[i] = shared_reg; + (*m_region_env)[reg] = std::move(ptr_with_off1 | ptr_with_off2); + } + } + else if (std::holds_alternative(ptr_or_mapfd1) + && std::holds_alternative(ptr_or_mapfd2)) { + mapfd_t mapfd1 = std::get(ptr_or_mapfd1); + mapfd_t mapfd2 = std::get(ptr_or_mapfd2); + out_vars[i] = shared_reg; + (*m_region_env)[reg] = std::move(mapfd1 | mapfd2); } } } @@ -160,7 +170,7 @@ stack_t stack_t::operator|(const stack_t& other) const { auto maybe_ptr_or_mapfd_cells = other.find(kv.first); if (maybe_ptr_or_mapfd_cells) { auto ptr_or_mapfd_cells1 = kv.second; - auto ptr_or_mapfd_cells2 = maybe_ptr_or_mapfd_cells.value(); + auto ptr_or_mapfd_cells2 = *maybe_ptr_or_mapfd_cells; auto ptr_or_mapfd1 = ptr_or_mapfd_cells1.first; auto ptr_or_mapfd2 = ptr_or_mapfd_cells2.first; int width1 = ptr_or_mapfd_cells1.second; @@ -178,6 +188,12 @@ stack_t stack_t::operator|(const stack_t& other) const { = std::make_pair(ptr_with_off1 | ptr_with_off2, width_joined); } } + else if (std::holds_alternative(ptr_or_mapfd1) && + std::holds_alternative(ptr_or_mapfd2)) { + auto mapfd1 = std::get(ptr_or_mapfd1); + auto mapfd2 = std::get(ptr_or_mapfd2); + out_ptrs[kv.first] = std::make_pair(mapfd1 | mapfd2, width_joined); + } } } return stack_t(std::move(out_ptrs), false); @@ -430,19 +446,95 @@ void region_domain_t::operator()(const ValidSize& u, location_t loc, int print) /* WARNING: The operation is not implemented yet.*/ } -void region_domain_t::operator()(const LoadMapFd &u, location_t loc, int print) { - auto reg = u.dst.v; - auto reg_with_loc = reg_with_loc_t(reg, loc); - auto platform = global_program_info->platform; - const EbpfMapDescriptor& desc = platform->get_map_descriptor(u.mapfd); +// Get the start and end of the range of possible map fd values. +// In the future, it would be cleaner to use a set rather than an interval +// for map fds. +bool region_domain_t::get_map_fd_range(const Reg& map_fd_reg, int32_t* start_fd, int32_t* end_fd) const { + auto maybe_type = m_registers.find(map_fd_reg.v); + if (!is_mapfd_type(maybe_type)) return false; + auto mapfd_type = std::get(*maybe_type); + const interval_t& mapfd_interval = mapfd_type.get_mapfd(); + auto lb = mapfd_interval.lb().number(); + auto ub = mapfd_interval.ub().number(); + if (!lb || !lb->fits_sint32() || !ub || !ub->fits_sint32()) + return false; + *start_fd = (int32_t)lb.value(); + *end_fd = (int32_t)ub.value(); + + // Cap the maximum range we'll check. + const int max_range = 32; + return (*mapfd_interval.finite_size() < max_range); +} + +// All maps in the range must have the same type for us to use it. +std::optional region_domain_t::get_map_type(const Reg& map_fd_reg) const { + int32_t start_fd, end_fd; + if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) + return std::optional(); + + std::optional type; + for (int32_t map_fd = start_fd; map_fd <= end_fd; map_fd++) { + EbpfMapDescriptor* map = &global_program_info->platform->get_map_descriptor(map_fd); + if (map == nullptr) + return std::optional(); + if (!type.has_value()) + type = map->type; + else if (map->type != *type) + return std::optional(); + } + return type; +} + +// All maps in the range must have the same inner map fd for us to use it. +std::optional region_domain_t::get_map_inner_map_fd(const Reg& map_fd_reg) const { + int32_t start_fd, end_fd; + if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) + return std::optional(); + + std::optional inner_map_fd; + for (int map_fd = start_fd; map_fd <= end_fd; map_fd++) { + EbpfMapDescriptor* map = &global_program_info->platform->get_map_descriptor(map_fd); + if (map == nullptr) + return std::optional(); + if (!inner_map_fd.has_value()) + inner_map_fd = map->inner_map_fd; + else if (map->type != *inner_map_fd) + return std::optional(); + } + return inner_map_fd; +} + +// We can deal with a range of value sizes. +interval_t region_domain_t::get_map_value_size(const Reg& map_fd_reg) const { + int start_fd, end_fd; + if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) + return interval_t::top(); + + interval_t result = crab::interval_t::bottom(); + for (int map_fd = start_fd; map_fd <= end_fd; map_fd++) { + if (EbpfMapDescriptor* map = &global_program_info->platform->get_map_descriptor(map_fd)) + result = result | crab::interval_t(number_t(map->value_size)); + else + return interval_t::top(); + } + return result; +} + +void region_domain_t::do_load_mapfd(const register_t& dst_reg, int mapfd, location_t loc) { + auto reg_with_loc = reg_with_loc_t(dst_reg, loc); + const auto& platform = global_program_info->platform; + const EbpfMapDescriptor& desc = platform->get_map_descriptor(mapfd); const EbpfMapValueType& map_value_type = platform->get_map_type(desc.type).value_type; - map_key_size_t map_key_size = desc.key_size; - map_value_size_t map_value_size = desc.value_size; - auto type = mapfd_t(u.mapfd, map_value_type, map_key_size, map_value_size); - m_registers.insert(reg, reg_with_loc, type); + auto mapfd_interval = interval_t{number_t{mapfd}}; + auto type = mapfd_t(mapfd_interval, map_value_type); + m_registers.insert(dst_reg, reg_with_loc, type); +} + +void region_domain_t::operator()(const LoadMapFd &u, location_t loc, int print) { + do_load_mapfd((register_t)u.dst.v, u.mapfd, loc); } -void region_domain_t::operator()(const Call &u, location_t loc, int print) { +void region_domain_t::operator()(const Call& u, location_t loc, int print) { std::optional maybe_fd_reg{}; for (ArgSingle param : u.singles) { if (param.kind == ArgSingle::Kind::MAP_FD) maybe_fd_reg = param.reg; @@ -451,38 +543,34 @@ void region_domain_t::operator()(const Call &u, location_t loc, int print) { register_t r0_reg{R0_RETURN_VALUE}; auto r0 = reg_with_loc_t(r0_reg, loc); if (u.is_map_lookup) { - if (!maybe_fd_reg) { - m_registers -= r0_reg; - return; - } - auto ptr_or_mapfd = m_registers.find(maybe_fd_reg->v); - if (!ptr_or_mapfd || !std::holds_alternative(ptr_or_mapfd.value())) { - m_registers -= r0_reg; - return; - } - auto mapfd = std::get(ptr_or_mapfd.value()); - auto platform = global_program_info->platform; - auto map_desc = platform->get_map_descriptor(mapfd.get_mapfd()); - if (mapfd.get_value_type() == EbpfMapValueType::MAP) { - const EbpfMapDescriptor& inner_map_desc = platform-> - get_map_descriptor(map_desc.inner_map_fd); - const EbpfMapValueType& inner_map_value_type = platform-> - get_map_type(inner_map_desc.type).value_type; - map_key_size_t inner_map_key_size = inner_map_desc.key_size; - map_value_size_t inner_map_value_size = inner_map_desc.value_size; - auto type = mapfd_t(map_desc.inner_map_fd, inner_map_value_type, - inner_map_key_size, inner_map_value_size); - m_registers.insert(r0_reg, r0, type); + if (maybe_fd_reg) { + if (auto map_type = get_map_type(*maybe_fd_reg)) { + if (global_program_info->platform->get_map_type(*map_type).value_type + == EbpfMapValueType::MAP) { + if (auto inner_map_fd = get_map_inner_map_fd(*maybe_fd_reg)) { + do_load_mapfd(r0_reg, (int)*inner_map_fd, loc); + goto out; + } + } else { + auto type = ptr_with_off_t(crab::region_t::T_SHARED, interval_t{number_t{0}}, + get_map_value_size(*maybe_fd_reg)); + m_registers.insert(r0_reg, r0, type); + } + } } else { - auto type = ptr_with_off_t(crab::region_t::T_SHARED, interval_t{number_t{0}}, - interval_t{mapfd.get_value_size()}); + auto type = ptr_with_off_t( + crab::region_t::T_SHARED, interval_t{number_t{0}}, crab::interval_t::top()); m_registers.insert(r0_reg, r0, type); } } else { m_registers -= r0_reg; } +out: + if (u.reallocate_packet) { + // forget packet pointers + } } void region_domain_t::operator()(const Callx &u, location_t loc, int print) { @@ -637,7 +725,7 @@ void region_domain_t::operator()(const TypeConstraint& s, location_t loc, int pr || s.types == TypeGroup::mem_or_num) return; } - //std::cout << "type error: type constraint assert fail: " << s << "\n"; + //std::cout << "type error: type constraint assert fail\n"; m_errors.push_back("type constraint assert fail"); } diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index 8bcefa56d..e4a4d44c8 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -162,6 +162,11 @@ class region_domain_t final { string_invariant to_set(); void set_require_check(check_require_func_t f) {} + interval_t get_map_value_size(const Reg&) const; + bool get_map_fd_range(const Reg&, int32_t*, int32_t*) const; + std::optional get_map_type(const Reg&) const; + std::optional get_map_inner_map_fd(const Reg&) const; + void do_load_mapfd(const register_t&, int, location_t); void do_load(const Mem&, const Reg&, bool, location_t); void do_mem_store(const Mem&, const Reg&, location_t); interval_t do_bin(const Bin&, const std::optional&, diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 73f1806be..d5d80813f 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -199,18 +199,10 @@ void type_domain_t::operator()(const ValidSize& u, location_t loc, int print) { void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int print) { - int width; auto maybe_ptr_or_mapfd_basereg = m_region.find_ptr_or_mapfd_type(u.access_reg.v); auto maybe_mapfd = m_region.find_ptr_or_mapfd_type(u.map_fd_reg.v); if (maybe_ptr_or_mapfd_basereg && maybe_mapfd) { if (is_mapfd_type(maybe_mapfd)) { - auto mapfd_type = std::get(*maybe_mapfd); - if (u.key) { - width = (int)mapfd_type.get_key_size(); - } - else { - width = (int)mapfd_type.get_value_size(); - } auto ptr_or_mapfd_basereg = maybe_ptr_or_mapfd_basereg.value(); if (std::holds_alternative(ptr_or_mapfd_basereg)) { auto ptr_with_off = std::get(ptr_or_mapfd_basereg); @@ -234,6 +226,7 @@ void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int pr // We do not check packet ptr accesses yet return; } + m_errors.push_back("Only stack or packet can be used as a parameter"); } } //std::cout << "type error: valid map key value assertion failed\n"; From 31fbb94384d909bd37723d9c7e941125dafb65ff Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Sun, 16 Jul 2023 02:20:53 -0400 Subject: [PATCH 107/373] Support for packet pointer invalidate Signed-off-by: Ameer Hamza --- src/crab/region_domain.cpp | 11 ++++++++++- src/crab/region_domain.hpp | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index 426b13495..a938d552b 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -37,6 +37,15 @@ std::optional ctx_t::find(uint64_t key) const { return it->second; } +void register_types_t::forget_packet_ptrs() { + for (auto r = register_t{0}; r <= register_t{10}; ++r) { + auto reg = find(r); + if (is_packet_ptr(reg)) { + operator-=(r); + } + } +} + register_types_t register_types_t::operator|(const register_types_t& other) const { if (is_bottom() || other.is_top()) { return other; @@ -569,7 +578,7 @@ void region_domain_t::operator()(const Call& u, location_t loc, int print) { } out: if (u.reallocate_packet) { - // forget packet pointers + m_registers.forget_packet_ptrs(); } } diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index e4a4d44c8..0a4759e03 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -83,6 +83,7 @@ class register_types_t { std::optional find(reg_with_loc_t reg) const; std::optional find(register_t key) const; [[nodiscard]] live_registers_t &get_vars() { return m_cur_def; } + void forget_packet_ptrs(); void adjust_bb_for_registers(location_t loc); void print_all_register_types() const; }; From b77c3c1e56a562860dbb93f693324f15fdc93a02 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Sun, 16 Jul 2023 02:37:15 -0400 Subject: [PATCH 108/373] Fix undefined reference error for a function Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 2 +- src/crab/type_ostream.cpp | 2 +- src/crab/type_ostream.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index d5d80813f..66029dfec 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -318,7 +318,7 @@ void type_domain_t::print_registers() const { auto maybe_ptr_or_mapfd_type = m_region.find_ptr_or_mapfd_type(reg); if (maybe_ptr_or_mapfd_type) { std::cout << " "; - print_register(Reg{(uint8_t)reg}, maybe_ptr_or_mapfd_type); + print_register(std::cout, Reg{(uint8_t)reg}, maybe_ptr_or_mapfd_type); std::cout << "\n"; } } diff --git a/src/crab/type_ostream.cpp b/src/crab/type_ostream.cpp index 47c7e2001..041b4ca93 100644 --- a/src/crab/type_ostream.cpp +++ b/src/crab/type_ostream.cpp @@ -21,7 +21,7 @@ void print_ptr_or_mapfd_type(std::ostream& o, const crab::ptr_or_mapfd_t& ptr_or } } -void print_register(std::ostream& o, Reg r, std::optional& p) { +void print_register(std::ostream& o, const Reg& r, std::optional& p) { o << r << " : "; if (p) { print_ptr_or_mapfd_type(o, p.value()); diff --git a/src/crab/type_ostream.hpp b/src/crab/type_ostream.hpp index 6761d9724..11e7e2303 100644 --- a/src/crab/type_ostream.hpp +++ b/src/crab/type_ostream.hpp @@ -10,7 +10,7 @@ void print_ptr_or_mapfd_type(std::ostream&, const crab::ptr_or_mapfd_t&); void print_ptr_type(std::ostream&, const crab::ptr_or_mapfd_t& ptr); -void print_register(Reg r, std::optional& p); +void print_register(std::ostream& o, const Reg& r, std::optional& p); void print_annotated(std::ostream& o, const Call& call, std::optional& p); void print_annotated(std::ostream& o, const Bin& b, std::optional& p); void print_annotated(std::ostream& o, const LoadMapFd& u, std::optional& p); From 8955fe6e3dbdfd2d11a58aace156047023b774b1 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Fri, 30 Jun 2023 12:58:22 -0400 Subject: [PATCH 109/373] using mock_interval_t instead of interval_t Changed from using interval_t to mock_interval_t due to issues with storing intervals directly, as the default constructor is by default private; Made the default constructor for interval_t private, as it is in main PREVAIL Refactoring needed for changes added; Signed-off-by: Ameer Hamza --- src/crab/common.cpp | 22 +++++++++++++------ src/crab/common.hpp | 45 +++++++++++++++++++++++++++----------- src/crab/interval.hpp | 4 ++-- src/crab/region_domain.cpp | 23 +++++++++++-------- src/crab/type_domain.cpp | 4 ++-- 5 files changed, 65 insertions(+), 33 deletions(-) diff --git a/src/crab/common.cpp b/src/crab/common.cpp index f27047e92..afd8000c0 100644 --- a/src/crab/common.cpp +++ b/src/crab/common.cpp @@ -19,6 +19,10 @@ namespace std { namespace crab { +bool mock_interval_t::operator==(const mock_interval_t& other) const { + return (to_interval() == other.to_interval()); +} + bool ptr_with_off_t::operator==(const ptr_with_off_t& other) const { return (m_r == other.m_r && m_offset == other.m_offset && m_region_size == other.m_region_size); @@ -42,19 +46,23 @@ bool mapfd_t::operator==(const mapfd_t& other) const { mapfd_t mapfd_t::operator|(const mapfd_t& other) const { auto value_type = m_value_type == other.m_value_type ? m_value_type : EbpfMapValueType::ANY; - return mapfd_t(m_mapfd | other.m_mapfd, value_type); + const auto& mock_i = mock_interval_t(m_mapfd.to_interval() | other.m_mapfd.to_interval()); + return mapfd_t(std::move(mock_i), value_type); } -interval_t ptr_with_off_t::get_region_size() const { return m_region_size; } +mock_interval_t ptr_with_off_t::get_region_size() const { return m_region_size; } -void ptr_with_off_t::set_offset(interval_t off) { m_offset = off; } +void ptr_with_off_t::set_offset(mock_interval_t off) { m_offset = off; } -void ptr_with_off_t::set_region_size(interval_t region_sz) { m_region_size = region_sz; } +void ptr_with_off_t::set_region_size(mock_interval_t region_sz) { m_region_size = region_sz; } void ptr_with_off_t::set_region(region_t r) { m_r = r; } ptr_with_off_t ptr_with_off_t::operator|(const ptr_with_off_t& other) const { - return ptr_with_off_t(m_r, m_offset | other.m_offset, m_region_size | other.m_region_size); + const auto& mock_o = mock_interval_t(m_offset.to_interval() | other.m_offset.to_interval()); + const auto& mock_r_s = mock_interval_t( + m_region_size.to_interval() | other.m_region_size.to_interval()); + return ptr_with_off_t(m_r, std::move(mock_o), std::move(mock_r_s)); } void ptr_no_off_t::set_region(region_t r) { m_r = r; } @@ -116,8 +124,8 @@ inline std::string get_reg_ptr(const region_t& r) { } void ptr_with_off_t::write(std::ostream& o) const { - o << get_reg_ptr(m_r) << "<" << m_offset; - if (m_region_size.lb() >= number_t{0}) o << "," << m_region_size; + o << get_reg_ptr(m_r) << "<" << m_offset.to_interval(); + if (m_region_size.lb() >= number_t{0}) o << "," << m_region_size.to_interval(); o << ">"; } diff --git a/src/crab/common.hpp b/src/crab/common.hpp index e3a3d5524..e7f21acc2 100644 --- a/src/crab/common.hpp +++ b/src/crab/common.hpp @@ -46,10 +46,30 @@ class ptr_no_off_t { bool operator!=(const ptr_no_off_t&) const; }; +class mock_interval_t { + bound_t _lb; + bound_t _ub; + + public: + static mock_interval_t top() { + return mock_interval_t(bound_t::minus_infinity(), bound_t::plus_infinity()); + } + [[nodiscard]] bound_t lb() const { return _lb; } + [[nodiscard]] bound_t ub() const { return _ub; } + mock_interval_t(bound_t lb, bound_t ub) : _lb(lb), _ub(ub) {}; + mock_interval_t() : _lb(bound_t::minus_infinity()), _ub(bound_t::plus_infinity()) {} + mock_interval_t(const mock_interval_t& c) = default; + mock_interval_t(const bound_t& b) : _lb(b), _ub(b) {} + mock_interval_t& operator=(const mock_interval_t& o) = default; + bool operator==(const mock_interval_t& o) const; + mock_interval_t(const interval_t& i) : _lb(i.lb()), _ub(i.ub()) {} + interval_t to_interval() const { return std::move(interval_t(_lb, _ub)); } +}; + class ptr_with_off_t { region_t m_r; - interval_t m_offset; - interval_t m_region_size = interval_t::top(); + mock_interval_t m_offset; + mock_interval_t m_region_size = mock_interval_t::top(); public: ptr_with_off_t() = default; @@ -57,14 +77,15 @@ class ptr_with_off_t { ptr_with_off_t(ptr_with_off_t &&) = default; ptr_with_off_t &operator=(const ptr_with_off_t &) = default; ptr_with_off_t &operator=(ptr_with_off_t &&) = default; - ptr_with_off_t(region_t _r, interval_t _off, interval_t _region_sz) + ptr_with_off_t(region_t _r, mock_interval_t _off, + mock_interval_t _region_sz = mock_interval_t::top()) : m_r(_r), m_offset(_off), m_region_size(_region_sz) {} - ptr_with_off_t(region_t _r, interval_t _off) : m_r(_r), m_offset(_off) {} + ptr_with_off_t(region_t _r, mock_interval_t _off) : m_r(_r), m_offset(_off) {} ptr_with_off_t operator|(const ptr_with_off_t&) const; - interval_t get_region_size() const; - void set_region_size(interval_t); - [[nodiscard]] interval_t get_offset() const { return m_offset; } - void set_offset(interval_t); + mock_interval_t get_region_size() const; + void set_region_size(mock_interval_t); + [[nodiscard]] mock_interval_t get_offset() const { return m_offset; } + void set_offset(mock_interval_t); [[nodiscard]] region_t get_region() const { return m_r; } void set_region(region_t); void write(std::ostream&) const; @@ -74,7 +95,7 @@ class ptr_with_off_t { }; class mapfd_t { - interval_t m_mapfd; + mock_interval_t m_mapfd; EbpfMapValueType m_value_type; public: @@ -83,7 +104,7 @@ class mapfd_t { mapfd_t &operator=(const mapfd_t&) = default; mapfd_t &operator=(mapfd_t&&) = default; mapfd_t operator|(const mapfd_t&) const; - mapfd_t(interval_t mapfd, EbpfMapValueType val_type) + mapfd_t(mock_interval_t mapfd, EbpfMapValueType val_type) : m_mapfd(mapfd), m_value_type(val_type) {} friend std::ostream& operator<<(std::ostream&, const mapfd_t&); bool operator==(const mapfd_t&) const; @@ -92,7 +113,7 @@ class mapfd_t { bool has_type_map_programs() const; [[nodiscard]] EbpfMapValueType get_value_type() const { return m_value_type; } - [[nodiscard]] interval_t get_mapfd() const { return m_mapfd; } + [[nodiscard]] mock_interval_t get_mapfd() const { return m_mapfd; } }; using ptr_t = std::variant; @@ -150,7 +171,6 @@ inline std::ostream& operator<<(std::ostream& o, const region_t& t) { return o; } - } // namespace crab @@ -191,4 +211,3 @@ namespace std { //crab::ptr_t get_ptr(const crab::ptr_or_mapfd_t& t); } - diff --git a/src/crab/interval.hpp b/src/crab/interval.hpp index 972f4f8bf..47878de00 100644 --- a/src/crab/interval.hpp +++ b/src/crab/interval.hpp @@ -232,8 +232,6 @@ class interval_t final { bound_t _ub; public: - interval_t() : _lb(number_t{0}), _ub(-1) {} - static interval_t top() { return interval_t(bound_t::minus_infinity(), bound_t::plus_infinity()); } static interval_t bottom() { return interval_t(); } @@ -241,6 +239,8 @@ class interval_t final { [[nodiscard]] std::optional finite_size() const { return (_ub - _lb).number(); } private: + interval_t() : _lb(number_t{0}), _ub(-1) {} + static number_t abs(const number_t& x) { return x < 0 ? -x : x; } static number_t max(const number_t& x, const number_t& y) { return x.operator<=(y) ? y : x; } diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index a938d552b..8c9a2fd4a 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -462,7 +462,7 @@ bool region_domain_t::get_map_fd_range(const Reg& map_fd_reg, int32_t* start_fd, auto maybe_type = m_registers.find(map_fd_reg.v); if (!is_mapfd_type(maybe_type)) return false; auto mapfd_type = std::get(*maybe_type); - const interval_t& mapfd_interval = mapfd_type.get_mapfd(); + const auto& mapfd_interval = mapfd_type.get_mapfd().to_interval(); auto lb = mapfd_interval.lb().number(); auto ub = mapfd_interval.ub().number(); if (!lb || !lb->fits_sint32() || !ub || !ub->fits_sint32()) @@ -621,7 +621,7 @@ void region_domain_t::operator()(const ValidAccess &s, location_t loc, int print if (std::holds_alternative(reg_ptr_or_mapfd_type)) { auto reg_with_off_ptr_type = std::get(reg_ptr_or_mapfd_type); auto offset = reg_with_off_ptr_type.get_offset(); - auto offset_to_check = offset+interval_t{s.offset}; + auto offset_to_check = offset.to_interval()+interval_t{s.offset}; auto offset_lb = offset_to_check.lb(); auto offset_plus_width_ub = offset_to_check.ub()+crab::bound_t{width}; if (reg_with_off_ptr_type.get_region() == crab::region_t::T_STACK) { @@ -678,9 +678,12 @@ region_domain_t&& region_domain_t::setup_entry() { auto r1 = reg_with_loc_t(R1_ARG, std::make_pair(label_t::entry, static_cast(0))); auto r10 = reg_with_loc_t(R10_STACK_POINTER, std::make_pair(label_t::entry, static_cast(0))); - typ.insert(R1_ARG, r1, ptr_with_off_t(crab::region_t::T_CTX, interval_t{number_t{0}})); + typ.insert(R1_ARG, r1, + ptr_with_off_t(crab::region_t::T_CTX, + mock_interval_t{number_t{0}}, mock_interval_t{number_t{-1}})); typ.insert(R10_STACK_POINTER, r10, - ptr_with_off_t(crab::region_t::T_STACK, interval_t{number_t{512}})); + ptr_with_off_t(crab::region_t::T_STACK, + mock_interval_t{number_t{512}}, mock_interval_t{number_t{-1}})); static region_domain_t inv(std::move(typ), crab::stack_t::top(), ctx); return std::move(inv); @@ -743,7 +746,8 @@ void region_domain_t::update_ptr_or_mapfd(ptr_or_mapfd_t&& ptr_or_mapfd, const i if (std::holds_alternative(ptr_or_mapfd)) { auto ptr_or_mapfd_with_off = std::get(ptr_or_mapfd); auto offset = ptr_or_mapfd_with_off.get_offset(); - auto updated_offset = change == interval_t::top() ? offset : offset + change; + auto updated_offset = + change == mock_interval_t::top() ? offset : offset.to_interval() + change; ptr_or_mapfd_with_off.set_offset(updated_offset); m_registers.insert(reg, reg_with_loc, ptr_or_mapfd_with_off); } @@ -772,7 +776,7 @@ interval_t region_domain_t::do_bin(const Bin& bin, return interval_t::bottom(); ptr_or_mapfd_t src_ptr_or_mapfd, dst_ptr_or_mapfd; - interval_t src_interval; + interval_t src_interval = interval_t::bottom(); if (src_ptr_or_mapfd_opt) src_ptr_or_mapfd = std::move(src_ptr_or_mapfd_opt.value()); if (dst_ptr_or_mapfd_opt) dst_ptr_or_mapfd = std::move(dst_ptr_or_mapfd_opt.value()); if (src_interval_opt) src_interval = std::move(src_interval_opt.value()); @@ -836,7 +840,8 @@ interval_t region_domain_t::do_bin(const Bin& bin, std::holds_alternative(src_ptr_or_mapfd)) { auto dst_ptr_with_off = std::get(dst_ptr_or_mapfd); auto src_ptr_with_off = std::get(src_ptr_or_mapfd); - to_return = dst_ptr_with_off.get_offset() - src_ptr_with_off.get_offset(); + to_return = + dst_ptr_with_off.get_offset().to_interval() - src_ptr_with_off.get_offset().to_interval(); } } else { @@ -880,7 +885,7 @@ void region_domain_t::do_load(const Mem& b, const Reg& target_reg, bool unknown_ auto type_with_off = std::get(*ptr_or_mapfd_opt); auto p_offset = type_with_off.get_offset(); - auto offset_singleton = p_offset.singleton(); + auto offset_singleton = p_offset.to_interval().singleton(); if (is_stack_p) { if (!offset_singleton) { @@ -982,7 +987,7 @@ void region_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location // if the code reaches here, we are storing into a stack pointer auto basereg_type_with_off = std::get(basereg_type); - auto offset_singleton = basereg_type_with_off.get_offset().singleton(); + auto offset_singleton = basereg_type_with_off.get_offset().to_interval().singleton(); if (!offset_singleton) { //std::cout << "type error: storing to a pointer with unknown offset\n"; m_errors.push_back("storing to a pointer with unknown offset"); diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 66029dfec..bdb5d2e64 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -122,7 +122,7 @@ void type_domain_t::operator()(const Call& u, location_t loc, int print) { if (std::holds_alternative(ptr_or_mapfd)) { auto ptr_with_off = std::get(ptr_or_mapfd); if (ptr_with_off.get_region() == region_t::T_STACK) { - auto offset_singleton = ptr_with_off.get_offset().singleton(); + auto offset_singleton = ptr_with_off.get_offset().to_interval().singleton(); if (!offset_singleton) { //std::cout << "type error: storing at an unknown offset in stack\n"; m_errors.push_back("storing at an unknown offset in stack"); @@ -207,7 +207,7 @@ void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int pr if (std::holds_alternative(ptr_or_mapfd_basereg)) { auto ptr_with_off = std::get(ptr_or_mapfd_basereg); if (ptr_with_off.get_region() == region_t::T_STACK) { - auto offset_singleton = ptr_with_off.get_offset().singleton(); + auto offset_singleton = ptr_with_off.get_offset().to_interval().singleton(); if (!offset_singleton) { //std::cout << "type error: reading the stack at an unknown offset\n"; m_errors.push_back("reading the stack at an unknown offset"); From 79623c27bb8f4b134ed4a2b362a43a05092570a2 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Thu, 20 Jul 2023 14:51:34 -0400 Subject: [PATCH 110/373] Fix Comparable check according to how PREVAIL does Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index bdb5d2e64..379c9700c 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -175,10 +175,12 @@ void type_domain_t::operator()(const Comparable& u, location_t loc, int print) { if (maybe_ptr_or_mapfd1 && maybe_ptr_or_mapfd2) { // an extra check just to make sure registers are not labelled both ptrs and numbers if (is_mapfd_type(maybe_ptr_or_mapfd1) && is_mapfd_type(maybe_ptr_or_mapfd2)) return; - if (same_region(*maybe_ptr_or_mapfd1, *maybe_ptr_or_mapfd2)) return; + if (!is_shared_ptr(maybe_ptr_or_mapfd1) + && same_region(*maybe_ptr_or_mapfd1, *maybe_ptr_or_mapfd2)) return; } - else if (!maybe_ptr_or_mapfd1 && !maybe_ptr_or_mapfd2) { - // all other cases when we do not have a ptr or mapfd, the type is a number + else if (!maybe_ptr_or_mapfd2) { + // two numbers can be compared + // if r1 is a pointer, r2 must be a number return; } //std::cout << "type error: Non-comparable types\n"; @@ -363,7 +365,7 @@ void type_domain_t::adjust_bb_for_types(location_t loc) { void type_domain_t::operator()(const basic_block_t& bb, int print) { if (print != 0) { - print_annotated(std::cout, *this, bb, print); + //print_annotated(std::cout, *this, bb, print); return; } From 1dd59368e686d9f37a69add2df4c584b706f3d42 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Thu, 20 Jul 2023 15:22:08 -0400 Subject: [PATCH 111/373] A fix in memory store A case was missing where a constant number is stored. Although it does not actually store anything in the region domain, we still need to forget overlapping cells Signed-off-by: Ameer Hamza --- src/crab/region_domain.cpp | 12 ++++++++---- src/crab/region_domain.hpp | 2 +- src/crab/type_domain.cpp | 35 ++++++++++++++++------------------- src/crab/type_domain.hpp | 2 +- 4 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index 8c9a2fd4a..5f27c350a 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -955,15 +955,19 @@ void region_domain_t::operator()(const Mem& m, location_t loc, int print) { // nothing to do here } -void region_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t loc) { +void region_domain_t::do_mem_store(const Mem& b, location_t loc) { + std::optional targetreg_type = {}; + bool target_is_reg = std::holds_alternative(b.value); + if (target_is_reg) { + auto target_reg = std::get(b.value); + targetreg_type = m_registers.find(target_reg.v); + } int offset = b.access.offset; Reg basereg = b.access.basereg; int width = b.access.width; auto maybe_basereg_type = m_registers.find(basereg.v); - auto basereg_type = maybe_basereg_type.value(); - auto targetreg_type = m_registers.find(target_reg.v); bool is_ctx_p = is_ctx_ptr(maybe_basereg_type); bool is_shared_p = is_shared_ptr(maybe_basereg_type); @@ -986,7 +990,7 @@ void region_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location } // if the code reaches here, we are storing into a stack pointer - auto basereg_type_with_off = std::get(basereg_type); + auto basereg_type_with_off = std::get(*maybe_basereg_type); auto offset_singleton = basereg_type_with_off.get_offset().to_interval().singleton(); if (!offset_singleton) { //std::cout << "type error: storing to a pointer with unknown offset\n"; diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index 0a4759e03..bde61cf82 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -169,7 +169,7 @@ class region_domain_t final { std::optional get_map_inner_map_fd(const Reg&) const; void do_load_mapfd(const register_t&, int, location_t); void do_load(const Mem&, const Reg&, bool, location_t); - void do_mem_store(const Mem&, const Reg&, location_t); + void do_mem_store(const Mem&, location_t); interval_t do_bin(const Bin&, const std::optional&, const std::optional&, const std::optional&, location_t); diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 379c9700c..7dda3895b 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -284,29 +284,26 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg, bool unknown_pt m_region.do_load(b, target_reg, unknown_ptr, loc); } -void type_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, location_t loc, int print) { - m_region.do_mem_store(b, target_reg, loc); +void type_domain_t::do_mem_store(const Mem& b, location_t loc, int print) { + m_region.do_mem_store(b, loc); } void type_domain_t::operator()(const Mem& b, location_t loc, int print) { - if (std::holds_alternative(b.value)) { + auto basereg = b.access.basereg; + auto ptr_or_mapfd_opt = m_region.find_ptr_or_mapfd_type(basereg.v); + bool unknown_ptr = !ptr_or_mapfd_opt.has_value(); + if (unknown_ptr) { + std::string s = std::to_string(static_cast(basereg.v)); + m_errors.push_back( + std::string("load/store using an unknown pointer, or number - r") + s); + } + if (!unknown_ptr && !b.is_load) { + do_mem_store(b, loc, print); + } + else if (std::holds_alternative(b.value)) { auto targetreg = std::get(b.value); - auto basereg = b.access.basereg; - auto ptr_or_mapfd_opt = m_region.find_ptr_or_mapfd_type(basereg.v); - bool unknown_ptr = !ptr_or_mapfd_opt.has_value(); - if (unknown_ptr) { - std::string s = std::to_string(static_cast(basereg.v)); - m_errors.push_back( - std::string("load/store using an unknown pointer, or number - r") + s); - } - - if (b.is_load) { - do_load(b, targetreg, unknown_ptr, loc, print); - } else if (!unknown_ptr) { - do_mem_store(b, targetreg, loc, print); - } + if (b.is_load) do_load(b, targetreg, unknown_ptr, loc, print); } - else {} } // the method does not work well as it requires info about the label of basic block we are in @@ -365,7 +362,7 @@ void type_domain_t::adjust_bb_for_types(location_t loc) { void type_domain_t::operator()(const basic_block_t& bb, int print) { if (print != 0) { - //print_annotated(std::cout, *this, bb, print); + print_annotated(std::cout, *this, bb, print); return; } diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index d27c29f80..864b14be5 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -87,7 +87,7 @@ class type_domain_t final { private: void do_load(const Mem&, const Reg&, bool, location_t, int print = 0); - void do_mem_store(const Mem&, const Reg&, location_t, int print = 0); + void do_mem_store(const Mem&, location_t, int print = 0); void report_type_error(std::string, location_t); void print_registers() const; void adjust_bb_for_types(location_t); From 25ccc59089bcc3df086281d5ecf34fbcad9c7dc1 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Fri, 21 Jul 2023 01:09:04 -0400 Subject: [PATCH 112/373] Add initial structure for Comparable Many checks have not been implemented because either those require other domains not yet implemented, or require storing values for pointers Signed-off-by: Ameer Hamza --- src/crab/region_domain.cpp | 4 ++++ src/crab/region_domain.hpp | 1 + src/crab/type_domain.cpp | 23 +++++++++++++++++++++-- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index 5f27c350a..330b126d3 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -321,6 +321,10 @@ std::optional region_domain_t::find_ptr_or_mapfd_at_loc(const re return m_registers.find(reg); } +void region_domain_t::set_registers_to_top() { + m_registers.set_to_top(); +} + size_t region_domain_t::ctx_size() const { return m_ctx->get_size(); } diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index bde61cf82..86df05ebf 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -183,6 +183,7 @@ class region_domain_t final { std::optional find_in_stack(uint64_t key) const; std::optional find_ptr_or_mapfd_at_loc(const crab::reg_with_loc_t&) const; [[nodiscard]] std::vector get_stack_keys() const; + void set_registers_to_top(); void adjust_bb_for_types(location_t); void print_all_register_types() const; [[nodiscard]] std::vector& get_errors() { return m_errors; } diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 7dda3895b..ac33c70df 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -148,8 +148,27 @@ void type_domain_t::operator()(const Packet& u, location_t loc, int print) { m_region(u, loc); } -void type_domain_t::operator()(const Assume& u, location_t loc, int print) { - /* WARNING: The operation is not implemented yet.*/ +void type_domain_t::operator()(const Assume& s, location_t loc, int print) { + Condition cond = s.cond; + auto right = cond.right; + const auto& maybe_left_type = m_region.find_ptr_or_mapfd_type(cond.left.v); + if (std::holds_alternative(right)) { + const auto& right_reg = std::get(right); + const auto& maybe_right_type = m_region.find_ptr_or_mapfd_type(right_reg.v); + if (maybe_left_type && maybe_right_type) { + // both pointers + } + else if (!maybe_left_type && !maybe_right_type) { + // both numbers + } + else { + // We should only reach here if `--assume-assert` is off + assert(!thread_local_options.assume_assertions || is_bottom()); + // be sound in any case, it happens to flush out bugs: + m_region.set_registers_to_top(); + } + } + else {} } void type_domain_t::operator()(const ValidDivisor& s, location_t loc, int print) { From de89bc6bf872c6ccbbc6cd825d1edfe2830be8f7 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Wed, 16 Aug 2023 00:19:56 -0400 Subject: [PATCH 113/373] Refactoring, and some required fixes Removing redundant code related to printing not being used; Fixing error reporting; Some extra checks needed as interval domain has not been added yet; Signed-off-by: Ameer Hamza --- src/crab/region_domain.cpp | 27 +++++++++++---------------- src/crab/region_domain.hpp | 2 +- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index 330b126d3..d3aefd20d 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -127,16 +127,6 @@ void register_types_t::insert(register_t reg, const reg_with_loc_t& reg_with_loc m_cur_def[reg] = std::make_shared(reg_with_loc); } -void register_types_t::print_all_register_types() const { - std::cout << "\tregion types: {\n"; - for (auto const& kv : *m_region_env) { - std::cout << "\t\t" << kv.first << " : "; - print_ptr_or_mapfd_type(std::cout, kv.second); - std::cout << "\n"; - } - std::cout << "\t}\n"; -} - std::optional register_types_t::find(reg_with_loc_t reg) const { auto it = m_region_env->find(reg); if (it == m_region_env->end()) return {}; @@ -737,8 +727,7 @@ void region_domain_t::operator()(const TypeConstraint& s, location_t loc, int pr else { // if we don't know the type, we assume it is a number if (s.types == TypeGroup::number || s.types == TypeGroup::ptr_or_num - || s.types == TypeGroup::non_map_fd || s.types == TypeGroup::ptr_or_num - || s.types == TypeGroup::mem_or_num) + || s.types == TypeGroup::non_map_fd || s.types == TypeGroup::mem_or_num) return; } //std::cout << "type error: type constraint assert fail\n"; @@ -827,6 +816,16 @@ interval_t region_domain_t::do_bin(const Bin& bin, update_ptr_or_mapfd(std::move(src_ptr_or_mapfd), interval_t::top(), reg, bin.dst.v); } + else if (dst_ptr_or_mapfd_opt && !src_ptr_or_mapfd_opt) { + if (std::holds_alternative(dst_ptr_or_mapfd)) { + auto updated_type = std::get(dst_ptr_or_mapfd); + updated_type.set_offset(mock_interval_t::top()); + m_registers.insert(bin.dst.v, reg, updated_type); + } + else if (std::holds_alternative(dst_ptr_or_mapfd)) { + m_registers.insert(bin.dst.v, reg, dst_ptr_or_mapfd); + } + } break; } // ra -= b, where ra is a pointer/mapfd @@ -1014,8 +1013,4 @@ void region_domain_t::adjust_bb_for_types(location_t loc) { m_registers.adjust_bb_for_registers(loc); } -void region_domain_t::print_all_register_types() const { - m_registers.print_all_register_types(); -} - } // namespace crab diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index 86df05ebf..bdebaa29b 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -85,7 +85,6 @@ class register_types_t { [[nodiscard]] live_registers_t &get_vars() { return m_cur_def; } void forget_packet_ptrs(); void adjust_bb_for_registers(location_t loc); - void print_all_register_types() const; }; class region_domain_t final { @@ -187,6 +186,7 @@ class region_domain_t final { void adjust_bb_for_types(location_t); void print_all_register_types() const; [[nodiscard]] std::vector& get_errors() { return m_errors; } + void reset_errors() { m_errors.clear(); } }; // end region_domain_t } // namespace crab From 971e046928c1941903737c947c0dc382ddca080b Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Fri, 18 Aug 2023 02:14:11 -0400 Subject: [PATCH 114/373] Added support for computing width in ValidMapKeyValue Signed-off-by: Ameer Hamza --- src/crab/region_domain.cpp | 16 ++++++++++++++++ src/crab/region_domain.hpp | 1 + src/crab/type_domain.cpp | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index d3aefd20d..d04381483 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -507,6 +507,22 @@ std::optional region_domain_t::get_map_inner_map_fd(const Reg& map_fd_ return inner_map_fd; } +// We can deal with a range of key sizes. +interval_t region_domain_t::get_map_key_size(const Reg& map_fd_reg) const { + int start_fd, end_fd; + if (!get_map_fd_range(map_fd_reg, &start_fd, &end_fd)) + return interval_t::top(); + + interval_t result = interval_t::bottom(); + for (int map_fd = start_fd; map_fd <= end_fd; map_fd++) { + if (EbpfMapDescriptor* map = &global_program_info->platform->get_map_descriptor(map_fd)) + result = result | interval_t(number_t(map->key_size)); + else + return interval_t::top(); + } + return result; +} + // We can deal with a range of value sizes. interval_t region_domain_t::get_map_value_size(const Reg& map_fd_reg) const { int start_fd, end_fd; diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index bdebaa29b..2b74607b1 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -164,6 +164,7 @@ class region_domain_t final { interval_t get_map_value_size(const Reg&) const; bool get_map_fd_range(const Reg&, int32_t*, int32_t*) const; + interval_t get_map_key_size(const Reg&) const; std::optional get_map_type(const Reg&) const; std::optional get_map_inner_map_fd(const Reg&) const; void do_load_mapfd(const register_t&, int, location_t); diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index ac33c70df..acf5eb0ad 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -220,6 +220,25 @@ void type_domain_t::operator()(const ValidSize& u, location_t loc, int print) { void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int print) { + // TODO: move map-related function to common + //auto fd_type = m_region.get_map_type(u.map_fd_reg); + + int width; + if (u.key) { + auto key_size = m_region.get_map_key_size(u.map_fd_reg).singleton(); + if (!key_size.has_value()) { + m_errors.push_back("Map key size is not singleton"); + return; + } + width = (int)key_size.value(); + } else { + auto value_size = m_region.get_map_value_size(u.map_fd_reg).singleton(); + if (!value_size.has_value()) { + m_errors.push_back("Map value size is not singleton"); + return; + } + width = (int)value_size.value(); + } auto maybe_ptr_or_mapfd_basereg = m_region.find_ptr_or_mapfd_type(u.access_reg.v); auto maybe_mapfd = m_region.find_ptr_or_mapfd_type(u.map_fd_reg.v); if (maybe_ptr_or_mapfd_basereg && maybe_mapfd) { From 74d5aa14d9cf78f135b0d469a27dfb82802d99fe Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Fri, 21 Jul 2023 01:09:04 -0400 Subject: [PATCH 115/373] Initial implementation for offset domain Added all abstract transformers, including Bin, Mem, Assume, Assert; Added join implementation; Support for all types of packet pointers, including meta pointers; Printing offsets of types from Type Domain --- src/config.hpp | 2 +- src/crab/abstract_domain.cpp | 2 + src/crab/offset_domain.cpp | 718 +++++++++++++++++++++++++++++++++++ src/crab/offset_domain.hpp | 235 ++++++++++++ src/crab/type_domain.cpp | 46 ++- src/crab/type_domain.hpp | 12 +- src/crab_verifier.cpp | 1 + 7 files changed, 997 insertions(+), 19 deletions(-) create mode 100644 src/crab/offset_domain.cpp create mode 100644 src/crab/offset_domain.hpp diff --git a/src/config.hpp b/src/config.hpp index e0da39ee2..c425d8019 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MIT #pragma once -enum class abstract_domain_kind { EBPF_DOMAIN, TYPE_DOMAIN, REGION_DOMAIN }; +enum class abstract_domain_kind { EBPF_DOMAIN, TYPE_DOMAIN }; struct ebpf_verifier_options_t { bool check_termination; diff --git a/src/crab/abstract_domain.cpp b/src/crab/abstract_domain.cpp index c5d18f8e2..392a130f0 100644 --- a/src/crab/abstract_domain.cpp +++ b/src/crab/abstract_domain.cpp @@ -2,6 +2,7 @@ #include "ebpf_domain.hpp" #include "type_domain.hpp" #include "region_domain.hpp" +#include "offset_domain.hpp" template abstract_domain_t::abstract_domain_model::abstract_domain_model(Domain abs_val) @@ -294,3 +295,4 @@ std::ostream& operator<<(std::ostream& o, const abstract_domain_t& dom) { template abstract_domain_t::abstract_domain_t(crab::ebpf_domain_t); template abstract_domain_t::abstract_domain_t(crab::type_domain_t); template abstract_domain_t::abstract_domain_t(crab::region_domain_t); +template abstract_domain_t::abstract_domain_t(crab::offset_domain_t); diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp new file mode 100644 index 000000000..5b11590b0 --- /dev/null +++ b/src/crab/offset_domain.cpp @@ -0,0 +1,718 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT + +#include "crab/offset_domain.hpp" + +namespace crab { + +bool dist_t::operator==(const dist_t& d) const { + return (m_dist == d.m_dist && m_slack == d.m_slack); +} + +void dist_t::write(std::ostream& o) const { + if (m_slack != -1) + o << "s" << m_slack << "+"; + if (m_dist >= 0) + o << "begin+" << m_dist; + else if (m_dist >= -4098) + o << "meta"; + else + o << "end-" << (-1)*m_dist-4099; +} + +std::ostream& operator<<(std::ostream& o, const dist_t& d) { + d.write(o); + return o; +} + +void registers_state_t::insert(register_t reg, const reg_with_loc_t& reg_with_loc, const dist_t& dist) { + (*m_offset_env)[reg_with_loc] = dist; + m_cur_def[reg] = std::make_shared(reg_with_loc); +} + +std::optional registers_state_t::find(reg_with_loc_t reg) const { + auto it = m_offset_env->find(reg); + if (it == m_offset_env->end()) return {}; + return it->second; +} + +std::optional registers_state_t::find(register_t key) const { + if (m_cur_def[key] == nullptr) return {}; + const reg_with_loc_t& reg = *(m_cur_def[key]); + return find(reg); +} + +void registers_state_t::set_to_top() { + m_cur_def = live_registers_t{nullptr}; + m_is_bottom = false; +} + +void registers_state_t::set_to_bottom() { + m_cur_def = live_registers_t{nullptr}; + m_is_bottom = true; +} + +bool registers_state_t::is_top() const { + if (m_is_bottom) return false; + if (m_offset_env == nullptr) return true; + for (auto &it : m_cur_def) { + if (it != nullptr) return false; + } + return true; +} + +bool registers_state_t::is_bottom() const { + return m_is_bottom; +} + +void registers_state_t::operator-=(register_t to_forget) { + if (is_bottom()) { + return; + } + m_cur_def[to_forget] = nullptr; +} + +registers_state_t registers_state_t::operator|(const registers_state_t& other) const { + if (is_bottom() || other.is_top()) { + return other; + } else if (other.is_bottom() || is_top()) { + return *this; + } + live_registers_t out_vars; + location_t loc = location_t(std::make_pair(label_t(-2, -2), 0)); + + for (size_t i = 0; i < m_cur_def.size(); i++) { + if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; + auto it1 = find(*(m_cur_def[i])); + auto it2 = other.find(*(other.m_cur_def[i])); + if (it1 && it2) { + dist_t dist1 = it1.value(), dist2 = it2.value(); + auto reg = reg_with_loc_t((register_t)i, loc); + if (dist1 == dist2) { + out_vars[i] = m_cur_def[i]; + } + } + } + return registers_state_t(std::move(out_vars), m_offset_env, false); +} + +void registers_state_t::adjust_bb_for_registers(location_t loc) { + location_t old_loc = location_t(std::make_pair(label_t(-2, -2), 0)); + for (size_t i = 0; i < m_cur_def.size(); i++) { + auto new_reg = reg_with_loc_t((register_t)i, loc); + auto it = find((register_t)i); + if (!it) continue; + m_cur_def[i] = std::make_shared(new_reg); + (*m_offset_env)[new_reg] = it.value(); + + auto old_reg = reg_with_loc_t((register_t)i, old_loc); + if (*m_cur_def[i] == old_reg) + m_offset_env->erase(old_reg); + } +} + +void stack_state_t::set_to_top() { + m_slot_dists.clear(); + m_is_bottom = false; +} + +void stack_state_t::set_to_bottom() { + m_slot_dists.clear(); + m_is_bottom = true; +} + +bool stack_state_t::is_top() const { + if (m_is_bottom) return false; + return m_slot_dists.empty(); +} + +bool stack_state_t::is_bottom() const { + return m_is_bottom; +} + +stack_state_t stack_state_t::top() { + return stack_state_t(false); +} + +std::optional stack_state_t::find(int key) const { + auto it = m_slot_dists.find(key); + if (it == m_slot_dists.end()) return {}; + return it->second; +} + +void stack_state_t::store(int key, dist_t d) { + m_slot_dists[key] = d; +} + +void stack_state_t::operator-=(int to_erase) { + if (is_bottom()) { + return; + } + m_slot_dists.erase(to_erase); +} + +stack_state_t stack_state_t::operator|(const stack_state_t& other) const { + if (is_bottom() || other.is_top()) { + return other; + } else if (other.is_bottom() || is_top()) { + return *this; + } + stack_slot_dists_t out_stack_dists; + for (auto const&kv: m_slot_dists) { + auto it = other.m_slot_dists.find(kv.first); + if (it != m_slot_dists.end() && kv.second == it->second) + out_stack_dists.insert(kv); + } + return stack_state_t(std::move(out_stack_dists), false); +} + +void extra_constraints_t::set_to_top() { + add_equality(forward_and_backward_eq_t()); + add_inequality(inequality_t()); +} + +void extra_constraints_t::set_to_bottom() { + m_is_bottom = true; +} + +bool extra_constraints_t::is_top() const { + if (m_is_bottom) return false; + return (m_eq.m_forw.m_slack == -1 && m_ineq.m_slack == -1); +} + +bool extra_constraints_t::is_bottom() const { + return m_is_bottom; +} + +void extra_constraints_t::add_equality(forward_and_backward_eq_t fabeq) { + m_eq = std::move(fabeq); +} + +void extra_constraints_t::add_inequality(inequality_t ineq) { + m_ineq = std::move(ineq); +} + +weight_t extra_constraints_t::get_limit() const { + return m_eq.m_forw.m_dist; +} + +void extra_constraints_t::normalize() { + weight_t dist_forw = m_eq.m_forw.m_dist - m_eq.m_backw.m_dist - 4099; + weight_t dist_backw = -4099; + slack_var_t s = m_eq.m_forw.m_slack; + dist_forw += m_ineq.m_value; + weight_t ineq_val = 0; + rop_t ineq_rel = m_ineq.m_rel; + + m_eq = forward_and_backward_eq_t(dist_t(dist_forw, s), dist_t(dist_backw)); + m_ineq = inequality_t(s, ineq_rel, ineq_val); +} + +extra_constraints_t extra_constraints_t::operator|(const extra_constraints_t& other) const { + //normalize(); + //other.normalize(); + + weight_t dist1 = m_eq.m_forw.m_dist; + weight_t dist2 = other.m_eq.m_forw.m_dist; + slack_var_t s = m_eq.m_forw.m_slack; + + dist_t f = dist_t(std::min(dist1, dist2), s); + dist_t b = dist_t(-4099); + + forward_and_backward_eq_t out_eq(f, b); + inequality_t out_ineq(s, m_ineq.m_rel, 0); + + return extra_constraints_t(std::move(out_eq), std::move(out_ineq), false); + // have to handle case for different slack vars +} + +ctx_offsets_t::ctx_offsets_t(const ebpf_context_descriptor_t* desc) { + if (desc->data != -1) { + m_dists[desc->data] = dist_t(0); + } + if (desc->end != -1) { + m_dists[desc->end] = dist_t(-4099); + } + if (desc->meta != -1) { + m_dists[desc->meta] = dist_t(-1); + } + m_size = desc->size; +} + +int ctx_offsets_t::get_size() const { + return m_size; +} + +std::optional ctx_offsets_t::find(int key) const { + auto it = m_dists.find(key); + if (it == m_dists.end()) return {}; + return it->second; +} + +offset_domain_t offset_domain_t::setup_entry() { + std::shared_ptr ctx = std::make_shared(global_program_info->type.context_descriptor); + std::shared_ptr all_types = std::make_shared(); + registers_state_t regs(all_types); + + offset_domain_t off_d(std::move(regs), stack_state_t::top(), ctx); + return off_d; +} + +offset_domain_t offset_domain_t::bottom() { + offset_domain_t off; + off.set_to_bottom(); + return off; +} + +void offset_domain_t::set_to_top() { + m_reg_state.set_to_top(); + m_stack_state.set_to_top(); + m_extra_constraints.set_to_top(); +} + +void offset_domain_t::set_to_bottom() { + m_is_bottom = true; +} + +bool offset_domain_t::is_bottom() const { + return m_is_bottom; +} + +bool offset_domain_t::is_top() const { + if (m_is_bottom) return false; + return (m_reg_state.is_top() && m_stack_state.is_top() && m_extra_constraints.is_top()); +} + +// inclusion +bool offset_domain_t::operator<=(const offset_domain_t& other) const { return true; } + +// join +void offset_domain_t::operator|=(const offset_domain_t& abs) { + offset_domain_t tmp{abs}; + operator|=(std::move(tmp)); +} + +void offset_domain_t::operator|=(offset_domain_t&& abs) { + if (is_bottom()) { + *this = abs; + return; + } + *this = *this | std::move(abs); +} + +offset_domain_t offset_domain_t::operator|(const offset_domain_t& other) const { + if (is_bottom() || other.is_top()) { + return other; + } + else if (other.is_bottom() || is_top()) { + return *this; + } + return offset_domain_t(m_reg_state | other.m_reg_state, m_stack_state | other.m_stack_state, m_extra_constraints | other.m_extra_constraints, m_ctx_dists, std::max(m_slack, other.m_slack)); +} + +offset_domain_t offset_domain_t::operator|(offset_domain_t&& other) const { + if (is_bottom() || other.is_top()) { + return std::move(other); + } + else if (other.is_bottom() || is_top()) { + return *this; + } + return offset_domain_t(m_reg_state | std::move(other.m_reg_state), m_stack_state | std::move(other.m_stack_state), m_extra_constraints | std::move(other.m_extra_constraints), m_ctx_dists, std::max(m_slack, other.m_slack)); +} + +// meet +offset_domain_t offset_domain_t::operator&(const offset_domain_t& other) const { + /* WARNING: The operation is not implemented yet.*/ + return other; +} + +// widening +offset_domain_t offset_domain_t::widen(const offset_domain_t& other, bool to_constants) { + /* WARNING: The operation is not implemented yet.*/ + return other; +} + +// narrowing +offset_domain_t offset_domain_t::narrow(const offset_domain_t& other) const { + /* WARNING: The operation is not implemented yet.*/ + return other; +} + +//forget +void offset_domain_t::operator-=(variable_t var) {} + +void offset_domain_t::write(std::ostream& os) const {} + +std::string offset_domain_t::domain_name() const { + return "offset_domain"; +} + +crab::bound_t offset_domain_t::get_loop_count_upper_bound() { + /* WARNING: The operation is not implemented yet.*/ + return crab::bound_t{crab::number_t{0}}; +} + +void offset_domain_t::initialize_loop_counter(const label_t& label) { + /* WARNING: The operation is not implemented yet.*/ +} + +string_invariant offset_domain_t::to_set() { return string_invariant{}; } + +void offset_domain_t::operator()(const Assume &b, location_t loc, int print) { + Condition cond = b.cond; + if (cond.op == Condition::Op::LE) { + if (std::holds_alternative(cond.right)) { + auto right_reg = std::get(cond.right).v; + auto dist_left = m_reg_state.find(cond.left.v); + auto dist_right = m_reg_state.find(right_reg); + if (!dist_left && !dist_right) { + return; + } + else if (!dist_left || !dist_right) { + // this should not happen, comparison between a packet pointer and either + // other region's pointers or numbers; possibly raise type error + //exit(1); + std::cout << "type_error: one of the pointers being compared isn't packet pointer\n"; + return; + } + dist_t left_reg_dist = dist_left.value(); + dist_t right_reg_dist = dist_right.value(); + slack_var_t s = m_slack++; + dist_t f = dist_t(left_reg_dist.m_dist, s); + dist_t b = dist_t(right_reg_dist.m_dist, slack_var_t{-1}); + m_extra_constraints.add_equality(forward_and_backward_eq_t(f, b)); + m_extra_constraints.add_inequality(inequality_t(s, rop_t::R_GE, 0)); + } + } + else {} //we do not need to deal with other cases +} + +bool is_packet_pointer(std::optional& type) { + if (!type) { // not a pointer + return false; + } + auto ptr_or_mapfd_type = type.value(); + if (std::holds_alternative(ptr_or_mapfd_type) + && std::get(ptr_or_mapfd_type).get_region() == crab::region_t::T_PACKET) { + return true; + } + return false; +} + +bool is_stack_pointer(std::optional& type) { + if (!type) { // not a pointer + return false; + } + auto ptr_or_mapfd_type = type.value(); + if (std::holds_alternative(ptr_or_mapfd_type) + && std::get(ptr_or_mapfd_type).get_region() == crab::region_t::T_STACK) { + return true; + } + return false; +} + +void offset_domain_t::do_bin(const Bin& bin, std::optional src_const_value, + std::optional src_type, std::optional dst_type, + location_t loc) { + if (is_bottom()) return; + + auto reg_with_loc = reg_with_loc_t(bin.dst.v, loc); + if (std::holds_alternative(bin.v)) { + Reg src = std::get(bin.v); + switch (bin.op) + { + // ra = rb; + case Bin::Op::MOV: { + if (!is_packet_pointer(src_type)) { + m_reg_state -= bin.dst.v; + return; + } + auto it = m_reg_state.find(src.v); + if (!it) { + std::cout << "type_error: src is a packet_pointer and no offset info found\n"; + //exit(1); + return; + } + m_reg_state.insert(bin.dst.v, reg_with_loc, it.value()); + //std::cout << "offset: " << (*m_reg_state.get(bin.dst.v)).m_dist << "\n"; + break; + } + // ra += rb + case Bin::Op::ADD: { + if (!is_packet_pointer(dst_type)) { + m_reg_state -= bin.dst.v; + return; + } + auto it = m_reg_state.find(bin.dst.v); + if (!it) { + std::cout << "type_error: dst is a packet_pointer and no offset info found\n"; + //exit(1); + return; + } + auto dst_dist = it.value(); + if (src_const_value) { + weight_t updated_dist; + if (dst_dist.m_dist >= 0) { + updated_dist = dst_dist.m_dist + src_const_value.value(); + } + else if (dst_dist.m_dist >= -4098) { + // TODO: special handling of meta pointer required + updated_dist = dst_dist.m_dist - src_const_value.value(); + } + else { + updated_dist = dst_dist.m_dist - src_const_value.value(); + } + m_reg_state.insert(bin.dst.v, reg_with_loc, dist_t(updated_dist)); + } + else { + m_reg_state -= bin.dst.v; + } + break; + } + + default: { + m_reg_state -= bin.dst.v; + break; + } + } + } + else { + int imm = static_cast(std::get(bin.v).v); + auto it = m_reg_state.find(bin.dst.v); + switch (bin.op) + { + case Bin::Op::ADD: { + if (!is_packet_pointer(dst_type)) { + m_reg_state -= bin.dst.v; + return; + } + if (!it) { + std::cout << "type_error: dst is a packet_pointer and no offset info found\n"; + //exit(1); + return; + } + auto dst_dist = it.value(); + + weight_t updated_dist; + if (dst_dist.m_dist >= 0) { + updated_dist = dst_dist.m_dist + imm; + } + else if (dst_dist.m_dist >= -4098) { + // TODO: special handling of meta pointer required + updated_dist = dst_dist.m_dist - imm; + } + else { + updated_dist = dst_dist.m_dist - imm; + } + m_reg_state.insert(bin.dst.v, reg_with_loc, dist_t(updated_dist)); + break; + } + + default: { + m_reg_state -= bin.dst.v; + break; + } + } + } +} + +void offset_domain_t::operator()(const Bin& bin, location_t loc, int print) { + // nothing to do here +} + +void offset_domain_t::operator()(const Undefined& u, location_t loc, int print) { + // nothing to do here +} + +void offset_domain_t::operator()(const Un& u, location_t loc, int print) { + // nothing to do here +} + +void offset_domain_t::operator()(const LoadMapFd& u, location_t loc, int print) { + m_reg_state -= u.dst.v; +} + +void offset_domain_t::operator()(const Call& u, location_t loc, int print) { + m_reg_state -= register_t{R0_RETURN_VALUE}; +} + +void offset_domain_t::operator()(const Exit& u, location_t loc, int print) {} + +void offset_domain_t::operator()(const Jmp& u, location_t loc, int print) { + // nothing to do here +} + +void offset_domain_t::operator()(const Packet& u, location_t loc, int print) { + m_reg_state -= register_t{R0_RETURN_VALUE}; +} + +void offset_domain_t::operator()(const ValidDivisor& u, location_t loc, int print) { + /* WARNING: This operation is not implemented yet. */ +} + +void offset_domain_t::operator()(const ValidAccess& u, location_t loc, int print) { + // nothing to do here +} + +void offset_domain_t::operator()(const Comparable& u, location_t loc, int print) { + // nothing to do here +} + +void offset_domain_t::operator()(const Addable& u, location_t loc, int print) { + // nothing to do here +} + +void offset_domain_t::operator()(const ValidStore& u, location_t loc, int print) { + // nothing to do here +} + +void offset_domain_t::operator()(const TypeConstraint& u, location_t loc, int print) { + // nothing to do here +} + +void offset_domain_t::operator()(const ValidSize& u, location_t loc, int print) { + /* WARNING: This operation is not implemented yet. */ +} + +void offset_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int print) { + /* WARNING: This operation is not implemented yet. */ +} + +void offset_domain_t::operator()(const ZeroCtxOffset&, location_t loc, int print) { + // nothing to do here +} + +void offset_domain_t::check_valid_access(const ValidAccess& s, + std::optional& reg_type) { + bool is_comparison_check = s.width == (Value)Imm{0}; + + if (std::holds_alternative(s.width)) return; + int w = std::get(s.width).v; + if (w == 0 || !reg_type) return; + + m_extra_constraints.normalize(); + if (std::holds_alternative(*reg_type)) { + auto it = m_reg_state.find(s.reg.v); + int limit = m_extra_constraints.get_limit(); + if (it) { + dist_t dist = it.value(); + // TODO: handle meta and end pointers separately + if (dist.m_dist >= PACKET_BEGIN && dist.m_dist+w <= limit) return; + } + } + std::cout << "valid access assert fail\n"; + //exit(1); +} + +void offset_domain_t::operator()(const Assert &u, location_t loc, int print) { + // nothing to do here +} + +void offset_domain_t::operator()(const basic_block_t& bb, int print) { + // nothing to do here +} + +void offset_domain_t::do_mem_store(const Mem& b, std::optional& basereg_type) { + std::optional reg_type = {}; + if (!std::holds_alternative(b.value)) { + // remove the offset from the stack or nothing + return; + } + Reg target_reg = std::get(b.value); + + int offset = b.access.offset; + if (is_stack_pointer(basereg_type)) { + auto basereg_with_off = std::get(*basereg_type); + auto basereg_off = basereg_with_off.get_offset().to_interval(); + auto basereg_off_singleton = basereg_off.singleton(); + if (!basereg_off_singleton) { + std::cout << "type_error: basereg offset is not a singleton\n"; + return; + } + auto store_at = (int)(*basereg_off_singleton + offset); + //if (is_packet_pointer(targetreg_type)) { + auto it = m_reg_state.find(target_reg.v); + if (!it) { + std::cout << "type_error: register is a packet_pointer and no offset info found\n"; + m_stack_state -= store_at; + return; + } + m_stack_state.store(store_at, it.value()); + //} + //else { + //m_stack_state -= store_at; + //} + } + else {} // in the rest cases, we do not store +} + +void offset_domain_t::do_load(const Mem& b, const Reg& target_reg, + std::optional basereg_type, location_t loc) { + if (!basereg_type) { + m_reg_state -= target_reg.v; + return; + } + auto basereg_ptr_type = basereg_type.value(); + int offset = b.access.offset; + + if (std::holds_alternative(basereg_ptr_type)) { + auto p_with_off = std::get(basereg_ptr_type); + auto offset_singleton = p_with_off.get_offset().to_interval().singleton(); + if (!offset_singleton) { + std::cout << "type_error: basereg offset is not a singleton\n"; + m_reg_state -= target_reg.v; + return; + } + int to_load = (int)(*offset_singleton + offset); + + if (p_with_off.get_region() == crab::region_t::T_CTX) { + auto it = m_ctx_dists->find(to_load); + if (!it) { + m_reg_state -= target_reg.v; + return; + } + dist_t d = it.value(); + auto reg = reg_with_loc_t(target_reg.v, loc); + m_reg_state.insert(target_reg.v, reg, dist_t(d)); + } + else if (p_with_off.get_region() == crab::region_t::T_STACK) { + auto it = m_stack_state.find(to_load); + + if (!it) { + m_reg_state -= target_reg.v; + return; + } + dist_t d = it.value(); + auto reg = reg_with_loc_t(target_reg.v, loc); + m_reg_state.insert(target_reg.v, reg, dist_t(d)); + } + } + else { // we are loading from packet or shared, or we have mapfd + m_reg_state -= target_reg.v; + } +} + +void offset_domain_t::operator()(const Mem &b, location_t loc, int print) { +} + +std::optional offset_domain_t::find_in_registers(const reg_with_loc_t reg) const { + return m_reg_state.find(reg); +} + +std::optional offset_domain_t::find_in_ctx(int key) const { + return m_ctx_dists->find(key); +} + +std::optional offset_domain_t::find_in_stack(int key) const { + return m_stack_state.find(key); +} + +std::optional offset_domain_t::find_offset_info(register_t reg) const { + return m_reg_state.find(reg); +} + +void offset_domain_t::adjust_bb_for_types(location_t loc) { + m_reg_state.adjust_bb_for_registers(loc); +} + +} // namespace crab diff --git a/src/crab/offset_domain.hpp b/src/crab/offset_domain.hpp new file mode 100644 index 000000000..16b8cf366 --- /dev/null +++ b/src/crab/offset_domain.hpp @@ -0,0 +1,235 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +#include +#include "crab/abstract_domain.hpp" +#include "crab/common.hpp" + +namespace crab { +constexpr int STACK_BEGIN = 0; +constexpr int CTX_BEGIN = 0; +constexpr int PACKET_BEGIN = 0; + +using constant_t = int; // define a domain for constants +//using symbol_t = register_t; // a register with unknown value +using weight_t = constant_t; // should be constants + symbols +using slack_var_t = int; + +enum class rop_t { + R_GT, + R_GE, + R_LT, + R_LE +}; + +struct dist_t { + slack_var_t m_slack; + weight_t m_dist; + + dist_t(weight_t d, slack_var_t s = -1) : m_slack(s), m_dist(d) {} + dist_t() : m_slack(-1), m_dist(0) {} + bool operator==(const dist_t& d) const; + void write(std::ostream&) const; + friend std::ostream& operator<<(std::ostream& o, const dist_t& d); +}; // if dist is +ve, represents `begin+dist+slack;`, if dist is -ve, represents `end+dist+1` + +struct inequality_t { + slack_var_t m_slack; + rop_t m_rel; + weight_t m_value; + + inequality_t(slack_var_t slack, rop_t rel, weight_t val) : m_slack(slack), m_rel(rel) + , m_value(val) {} + inequality_t() = default; +}; // represents `slack rel value;`, e.g., `s >= 0` + +struct forward_and_backward_eq_t { + dist_t m_forw; + dist_t m_backw; + + forward_and_backward_eq_t(dist_t forw, dist_t backw) : m_forw(forw), m_backw(backw) {} + forward_and_backward_eq_t() = default; +}; // represents constraint `p[0] = p[1];`, e.g., `begin+8+s = end` + +using live_registers_t = std::array, 11>; +using global_offset_env_t = std::unordered_map; + +class registers_state_t { + + live_registers_t m_cur_def; + std::shared_ptr m_offset_env; + bool m_is_bottom = false; + + public: + registers_state_t(bool is_bottom = false) : m_offset_env(nullptr), m_is_bottom(is_bottom) {} + registers_state_t(std::shared_ptr offset_env, bool is_bottom = false) : m_offset_env(offset_env), m_is_bottom(is_bottom) {} + explicit registers_state_t(live_registers_t&& vars, std::shared_ptr + offset_env, bool is_bottom = false) + : m_cur_def(std::move(vars)), m_offset_env(std::move(offset_env)), m_is_bottom(is_bottom) {} + + registers_state_t operator|(const registers_state_t&) const; + void operator-=(register_t); + void set_to_top(); + void set_to_bottom(); + bool is_bottom() const; + bool is_top() const; + void insert(register_t, const reg_with_loc_t&, const dist_t&); + std::optional find(reg_with_loc_t reg) const; + std::optional find(register_t key) const; + friend std::ostream& operator<<(std::ostream& o, const registers_state_t& p); + void adjust_bb_for_registers(location_t); +}; + +class stack_state_t { + using stack_slot_dists_t = std::unordered_map; // represents `sp[n] = dist;`, where n \belongs [0,511], e.g., `sp[508] = begin+16` + + stack_slot_dists_t m_slot_dists; + bool m_is_bottom = false; + + public: + stack_state_t(bool is_bottom = false) : m_is_bottom(is_bottom) {} + std::optional find(int) const; + void store(int, dist_t); + void operator-=(int); + void set_to_top(); + void set_to_bottom(); + bool is_bottom() const; + bool is_top() const; + static stack_state_t top(); + stack_state_t operator|(const stack_state_t&) const; + explicit stack_state_t(stack_slot_dists_t&& stack_dists, bool is_bottom = false) + : m_slot_dists(std::move(stack_dists)), m_is_bottom(is_bottom) {} +}; + +class extra_constraints_t { + + forward_and_backward_eq_t m_eq; + inequality_t m_ineq; + bool m_is_bottom = false; + + public: + extra_constraints_t(bool is_bottom = false) : m_is_bottom(is_bottom) {} + void set_to_top(); + void set_to_bottom(); + bool is_bottom() const; + bool is_top() const; + void add_equality(forward_and_backward_eq_t); + void add_inequality(inequality_t); + void normalize(); + weight_t get_limit() const; + extra_constraints_t operator|(const extra_constraints_t&) const; + explicit extra_constraints_t(forward_and_backward_eq_t&& fabeq, inequality_t ineq, bool is_bottom = false) : m_eq(fabeq), m_ineq(ineq), m_is_bottom(is_bottom) {} +}; + +class ctx_offsets_t { + using ctx_dists_t = std::unordered_map; // represents `cp[n] = dist;` + ctx_dists_t m_dists; + int m_size; + + public: + ctx_offsets_t(const ebpf_context_descriptor_t* desc); + std::optional find(int) const; + int get_size() const; +}; + + +class offset_domain_t final { + + bool m_is_bottom = false; + registers_state_t m_reg_state; + stack_state_t m_stack_state; + extra_constraints_t m_extra_constraints; + std::shared_ptr m_ctx_dists; + std::vector m_errors; + slack_var_t m_slack = 0; + + public: + offset_domain_t() = default; + offset_domain_t(offset_domain_t&& o) = default; + offset_domain_t(const offset_domain_t& o) = default; + offset_domain_t& operator=(offset_domain_t&& o) = default; + offset_domain_t& operator=(const offset_domain_t& o) = default; + explicit offset_domain_t(registers_state_t&& reg, stack_state_t&& stack, + extra_constraints_t extra, std::shared_ptr ctx, slack_var_t s = 0) + : m_reg_state(std::move(reg)), m_stack_state(std::move(stack)), + m_extra_constraints(std::move(extra)), m_ctx_dists(ctx), m_slack(s) {} + + explicit offset_domain_t(registers_state_t&& reg, stack_state_t&& stack, + std::shared_ptr ctx, slack_var_t s = 0) : m_reg_state(std::move(reg)), + m_stack_state(std::move(stack)), m_ctx_dists(ctx), m_slack(s) {} + + static offset_domain_t setup_entry(); + // bottom/top + static offset_domain_t bottom(); + void set_to_top(); + void set_to_bottom(); + bool is_bottom() const; + bool is_top() const; + // inclusion + bool operator<=(const offset_domain_t& other) const; + // join + void operator|=(const offset_domain_t& abs); + void operator|=(offset_domain_t&& abs); + offset_domain_t operator|(const offset_domain_t& other) const; + offset_domain_t operator|(offset_domain_t&& abs) const; + // meet + offset_domain_t operator&(const offset_domain_t& other) const; + // widening + offset_domain_t widen(const offset_domain_t& other, bool); + // narrowing + offset_domain_t narrow(const offset_domain_t& other) const; + //forget + void operator-=(variable_t var); + + //// abstract transformers + void operator()(const Undefined&, location_t loc = boost::none, int print = 0); + void operator()(const Bin&, location_t loc = boost::none, int print = 0); + void operator()(const Un&, location_t loc = boost::none, int print = 0); + void operator()(const LoadMapFd&, location_t loc = boost::none, int print = 0); + void operator()(const Atomic&, location_t loc = boost::none, int print = 0) {} + void operator()(const Call&, location_t loc = boost::none, int print = 0); + void operator()(const Callx&, location_t loc = boost::none, int print = 0); + void operator()(const Exit&, location_t loc = boost::none, int print = 0); + void operator()(const Jmp&, location_t loc = boost::none, int print = 0); + void operator()(const Mem&, location_t loc = boost::none, int print = 0); + void operator()(const Packet&, location_t loc = boost::none, int print = 0); + void operator()(const Assume&, location_t loc = boost::none, int print = 0); + void operator()(const Assert&, location_t loc = boost::none, int print = 0); + void operator()(const ValidAccess&, location_t loc = boost::none, int print = 0); + void operator()(const Comparable&, location_t loc = boost::none, int print = 0); + void operator()(const Addable&, location_t loc = boost::none, int print = 0); + void operator()(const ValidStore&, location_t loc = boost::none, int print = 0); + void operator()(const TypeConstraint&, location_t loc = boost::none, int print = 0); + void operator()(const ValidSize&, location_t loc = boost::none, int print = 0); + void operator()(const ValidMapKeyValue&, location_t loc = boost::none, int print = 0); + void operator()(const ZeroCtxOffset&, location_t loc = boost::none, int print = 0); + void operator()(const ValidDivisor&, location_t loc = boost::none, int print = 0); + void operator()(const FuncConstraint& s, location_t loc = boost::none, int print = 0) {}; + void operator()(const IncrementLoopCounter&, location_t loc = boost::none, int print = 0) {}; + void operator()(const basic_block_t& bb, int print = 0); + void write(std::ostream& os) const; + std::string domain_name() const; + crab::bound_t get_loop_count_upper_bound(); + void initialize_loop_counter(const label_t&); + string_invariant to_set(); + void set_require_check(check_require_func_t f) {} + + void do_load(const Mem&, const Reg&, std::optional, location_t loc); + void do_mem_store(const Mem&, std::optional&); + void do_bin(const Bin&, std::optional, std::optional, + std::optional, location_t); + void check_valid_access(const ValidAccess&, std::optional&); + + std::optional find_in_ctx(int) const; + std::optional find_in_stack(int) const; + std::optional find_in_registers(const reg_with_loc_t) const; + std::optional find_offset_info(register_t reg) const; + void adjust_bb_for_types(location_t); + [[nodiscard]] std::vector& get_errors() { return m_errors; } +}; // end offset_domain_t + +} // end namespace crab diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index acf5eb0ad..63f78834f 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -1,7 +1,7 @@ // Copyright (c) Prevail Verifier contributors. // SPDX-License-Identifier: MIT -#include "type_domain.hpp" +#include "crab/type_domain.hpp" namespace crab { @@ -11,7 +11,7 @@ bool type_domain_t::is_bottom() const { bool type_domain_t::is_top() const { if (m_is_bottom) return false; - return (m_region.is_top()); + return (m_region.is_top() && m_offset.is_top()); } type_domain_t type_domain_t::bottom() { @@ -26,6 +26,7 @@ void type_domain_t::set_to_bottom() { void type_domain_t::set_to_top() { m_region.set_to_top(); + m_offset.set_to_top(); } bool type_domain_t::operator<=(const type_domain_t& abs) const { @@ -59,7 +60,7 @@ type_domain_t type_domain_t::operator|(const type_domain_t& other) const { else if (other.is_bottom() || is_top()) { return *this; } - return type_domain_t(m_region | other.m_region); + return type_domain_t(m_region | other.m_region, m_offset | other.m_offset); } type_domain_t type_domain_t::operator|(type_domain_t&& other) const { @@ -69,7 +70,7 @@ type_domain_t type_domain_t::operator|(type_domain_t&& other) const { else if (other.is_bottom() || is_top()) { return *this; } - return type_domain_t(m_region | std::move(other.m_region)); + return type_domain_t(m_region | std::move(other.m_region), m_offset | std::move(m_offset)); } type_domain_t type_domain_t::operator&(const type_domain_t& abs) const { @@ -95,7 +96,9 @@ string_invariant type_domain_t::to_set() { return string_invariant{}; } -void type_domain_t::operator()(const Undefined& u, location_t loc, int print) {} +void type_domain_t::operator()(const Undefined& u, location_t loc, int print) { + // nothing to do here +} void type_domain_t::operator()(const Un& u, location_t loc, int print) { /* WARNING: The operation is not implemented yet.*/ @@ -103,6 +106,7 @@ void type_domain_t::operator()(const Un& u, location_t loc, int print) { void type_domain_t::operator()(const LoadMapFd& u, location_t loc, int print) { m_region(u, loc); + m_offset(u, loc); } void type_domain_t::operator()(const Atomic &u, location_t loc, int print) { @@ -134,18 +138,22 @@ void type_domain_t::operator()(const Call& u, location_t loc, int print) { } } m_region(u, loc); + m_offset(u, loc); } void type_domain_t::operator()(const Callx &u, location_t loc, int print) { // WARNING: Not implemented yet } -void type_domain_t::operator()(const Exit& u, location_t loc, int print) {} +void type_domain_t::operator()(const Exit& u, location_t loc, int print) { + // nothing to do here +} void type_domain_t::operator()(const Jmp& u, location_t loc, int print) {} void type_domain_t::operator()(const Packet& u, location_t loc, int print) { m_region(u, loc); + m_offset(u, loc); } void type_domain_t::operator()(const Assume& s, location_t loc, int print) { @@ -169,6 +177,7 @@ void type_domain_t::operator()(const Assume& s, location_t loc, int print) { } } else {} + m_offset(s, loc, print); } void type_domain_t::operator()(const ValidDivisor& s, location_t loc, int print) { @@ -177,6 +186,8 @@ void type_domain_t::operator()(const ValidDivisor& s, location_t loc, int print) void type_domain_t::operator()(const ValidAccess& s, location_t loc, int print) { m_region(s, loc); + auto reg_type = m_region.find_ptr_or_mapfd_type(s.reg.v); + m_offset.check_valid_access(s, reg_type); } void type_domain_t::operator()(const TypeConstraint& s, location_t loc, int print) { @@ -184,6 +195,7 @@ void type_domain_t::operator()(const TypeConstraint& s, location_t loc, int prin } void type_domain_t::operator()(const Assert& u, location_t loc, int print) { + if (is_bottom()) return; std::visit([this, loc, print](const auto& v) { std::apply(*this, std::make_tuple(v, loc, print)); }, u.cst); } @@ -192,7 +204,6 @@ void type_domain_t::operator()(const Comparable& u, location_t loc, int print) { auto maybe_ptr_or_mapfd1 = m_region.find_ptr_or_mapfd_type(u.r1.v); auto maybe_ptr_or_mapfd2 = m_region.find_ptr_or_mapfd_type(u.r2.v); if (maybe_ptr_or_mapfd1 && maybe_ptr_or_mapfd2) { - // an extra check just to make sure registers are not labelled both ptrs and numbers if (is_mapfd_type(maybe_ptr_or_mapfd1) && is_mapfd_type(maybe_ptr_or_mapfd2)) return; if (!is_shared_ptr(maybe_ptr_or_mapfd1) && same_region(*maybe_ptr_or_mapfd1, *maybe_ptr_or_mapfd2)) return; @@ -279,16 +290,18 @@ void type_domain_t::operator()(const ZeroCtxOffset& u, location_t loc, int print type_domain_t type_domain_t::setup_entry() { auto&& reg = crab::region_domain_t::setup_entry(); - type_domain_t typ(std::move(reg)); + auto&& off = offset_domain_t::setup_entry(); + type_domain_t typ(std::move(reg), std::move(off)); return typ; } void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { - + if (is_bottom()) return; auto dst_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(bin.dst.v); std::optional src_ptr_or_mapfd; std::optional src_interval; + if (std::holds_alternative(bin.v)) { Reg r = std::get(bin.v); src_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(r.v); @@ -313,17 +326,20 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { m_region -= bin.dst.v; return; } - m_region.do_bin(bin, src_interval, src_ptr_or_mapfd, dst_ptr_or_mapfd, loc); + m_offset.do_bin(bin, std::nullopt, src_ptr_or_mapfd, dst_ptr_or_mapfd, loc); } void type_domain_t::do_load(const Mem& b, const Reg& target_reg, bool unknown_ptr, - location_t loc, int print) { + std::optional basereg_opt, location_t loc, int print) { m_region.do_load(b, target_reg, unknown_ptr, loc); + m_offset.do_load(b, target_reg, basereg_opt, loc); } -void type_domain_t::do_mem_store(const Mem& b, location_t loc, int print) { +void type_domain_t::do_mem_store(const Mem& b, std::optional& basereg_opt, + location_t loc, int print) { m_region.do_mem_store(b, loc); + m_offset.do_mem_store(b, basereg_opt); } void type_domain_t::operator()(const Mem& b, location_t loc, int print) { @@ -336,11 +352,11 @@ void type_domain_t::operator()(const Mem& b, location_t loc, int print) { std::string("load/store using an unknown pointer, or number - r") + s); } if (!unknown_ptr && !b.is_load) { - do_mem_store(b, loc, print); + do_mem_store(b, ptr_or_mapfd_opt, loc, print); } else if (std::holds_alternative(b.value)) { auto targetreg = std::get(b.value); - if (b.is_load) do_load(b, targetreg, unknown_ptr, loc, print); + if (b.is_load) do_load(b, targetreg, unknown_ptr, ptr_or_mapfd_opt, loc, print); } } @@ -395,6 +411,7 @@ void type_domain_t::print_stack() const { void type_domain_t::adjust_bb_for_types(location_t loc) { m_region.adjust_bb_for_types(loc); + m_offset.adjust_bb_for_types(loc); } void type_domain_t::operator()(const basic_block_t& bb, int print) { @@ -416,6 +433,7 @@ void type_domain_t::operator()(const basic_block_t& bb, int print) { } operator+=(m_region.get_errors()); + operator+=(m_offset.get_errors()); } std::optional diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 864b14be5..89901e70b 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -5,12 +5,14 @@ #include "crab/abstract_domain.hpp" #include "crab/region_domain.hpp" +#include "crab/offset_domain.hpp" #include "crab/common.hpp" namespace crab { class type_domain_t final { crab::region_domain_t m_region; + crab::offset_domain_t m_offset; bool m_is_bottom = false; std::vector m_errors; @@ -19,8 +21,8 @@ class type_domain_t final { type_domain_t() = default; type_domain_t(type_domain_t&& o) = default; type_domain_t(const type_domain_t& o) = default; - explicit type_domain_t(crab::region_domain_t&& reg, bool is_bottom = false) : - m_region(reg), m_is_bottom(is_bottom) {} + explicit type_domain_t(region_domain_t&& reg, offset_domain_t&& off, bool is_bottom = false) : + m_region(reg), m_offset(off), m_is_bottom(is_bottom) {} type_domain_t& operator=(type_domain_t&& o) = default; type_domain_t& operator=(const type_domain_t& o) = default; // eBPF initialization: R1 points to ctx, R10 to stack, etc. @@ -86,14 +88,16 @@ class type_domain_t final { private: - void do_load(const Mem&, const Reg&, bool, location_t, int print = 0); - void do_mem_store(const Mem&, location_t, int print = 0); + void do_load(const Mem&, const Reg&, bool, std::optional, + location_t, int print = 0); + void do_mem_store(const Mem&, std::optional&, location_t, int print = 0); void report_type_error(std::string, location_t); void print_registers() const; void adjust_bb_for_types(location_t); void operator+=(std::vector& errs) { m_errors.insert(m_errors.end(), errs.begin(), errs.end()); } + void print_initial_registers() const; }; // end type_domain_t } // namespace crab diff --git a/src/crab_verifier.cpp b/src/crab_verifier.cpp index 962ced1a7..7d48e573e 100644 --- a/src/crab_verifier.cpp +++ b/src/crab_verifier.cpp @@ -16,6 +16,7 @@ #include "crab/ebpf_domain.hpp" #include "crab/type_domain.hpp" #include "crab/region_domain.hpp" +#include "crab/offset_domain.hpp" #include "crab/fwd_analyzer.hpp" #include "crab_utils/lazy_allocator.hpp" From 256d3d83e29b6f4640c5f607676aa74d9552b0d2 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Fri, 28 Jul 2023 15:20:33 -0400 Subject: [PATCH 116/373] Some fixes/additions Added support for intervals in offset domain instead of constants; Fixes in valid access assertion checks; Better handling in Bin operation: 1) Initial imprecise support for pointer subtraction, 2) Multiple sanity checks added (TODO: precise handling of subtraction for packet pointers); Better handling in Mem operations, especially in Load, when pointers do not have fixed offsets; Support packet access check in ValidMapKeyValue assertion; Signed-off-by: Ameer Hamza --- src/crab/offset_domain.cpp | 571 ++++++++++++++++++++++++++----------- src/crab/offset_domain.hpp | 109 +++++-- src/crab/type_domain.cpp | 27 +- 3 files changed, 508 insertions(+), 199 deletions(-) diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index 5b11590b0..27a95ca3a 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -9,15 +9,55 @@ bool dist_t::operator==(const dist_t& d) const { return (m_dist == d.m_dist && m_slack == d.m_slack); } +weight_t dist_t::offset_from_reference() const { + if (is_meta_pointer()) { + return (-m_dist+interval_t{number_t{PACKET_META}}); + } + if (is_backward_pointer()) { + return (m_dist-interval_t{number_t{PACKET_END}}); + } + return m_dist; +} + void dist_t::write(std::ostream& o) const { if (m_slack != -1) o << "s" << m_slack << "+"; - if (m_dist >= 0) - o << "begin+" << m_dist; - else if (m_dist >= -4098) - o << "meta"; - else - o << "end-" << (-1)*m_dist-4099; + if (is_forward_pointer()) o << "begin+"; + else if (is_meta_pointer()) o << "meta+"; + else if (is_backward_pointer()) o << "end+"; + auto offset = offset_from_reference(); + auto singleton_val = offset.singleton(); + if (singleton_val) o << singleton_val.value(); + else o << offset; +} + +bool dist_t::is_top() const { + if (m_is_bottom) return false; + return (m_slack == -1 && m_dist.is_top()); +} + +bool dist_t::is_bottom() const { + return m_is_bottom; +} + +void dist_t::set_to_top() { + m_slack = -1; + m_dist = interval_t::top(); + m_is_bottom = false; +} + +void dist_t::set_to_bottom() { + m_is_bottom = true; +} + +bool dist_t::is_meta_pointer() const { + return (m_dist.lb() > number_t{PACKET_END} && m_dist.ub() <= number_t{PACKET_META}); +} +bool dist_t::is_forward_pointer() const { + return (m_dist.lb() >= number_t{PACKET_BEGIN}); +} +bool dist_t::is_backward_pointer() const { + return (m_dist.ub() <= number_t{PACKET_END}); } std::ostream& operator<<(std::ostream& o, const dist_t& d) { @@ -25,7 +65,68 @@ std::ostream& operator<<(std::ostream& o, const dist_t& d) { return o; } -void registers_state_t::insert(register_t reg, const reg_with_loc_t& reg_with_loc, const dist_t& dist) { +bool inequality_t::is_top() const { + if (m_is_bottom) return false; + return (m_slack == -1 && m_value.is_top()); +} + +bool inequality_t::is_bottom() const { + return m_is_bottom; +} + +void inequality_t::set_to_top() { + m_value = interval_t::top(); + m_slack = -1; + m_is_bottom = false; +} + +void inequality_t::set_to_bottom() { + m_is_bottom = true; +} + + +std::ostream& operator<<(std::ostream& o, const inequality_t& ineq) { + ineq.write(o); + return o; +} + +void inequality_t::write(std::ostream& o) const { + o << m_slack << (m_rel == rop_t::R_GT ? ">" : + m_rel == rop_t::R_GE ? ">=" : + m_rel == rop_t::R_LT ? "<" : "<=") + << m_value; +} + +bool equality_t::is_top() const { + if (m_is_bottom) return false; + return (m_lhs.is_top() && m_rhs.is_top()); +} + +bool equality_t::is_bottom() const { + return m_is_bottom; +} + +void equality_t::set_to_top() { + m_lhs.set_to_top(); + m_rhs.set_to_top(); + m_is_bottom = false; +} + +void equality_t::set_to_bottom() { + m_is_bottom = true; +} + +std::ostream& operator<<(std::ostream& o, const equality_t& eq) { + eq.write(o); + return o; +} + +void equality_t::write(std::ostream& o) const { + o << m_lhs << " = " << m_rhs; +} + +void registers_state_t::insert(register_t reg, const reg_with_loc_t& reg_with_loc, + const dist_t& dist) { (*m_offset_env)[reg_with_loc] = dist; m_cur_def[reg] = std::make_shared(reg_with_loc); } @@ -88,9 +189,10 @@ registers_state_t registers_state_t::operator|(const registers_state_t& other) c if (it1 && it2) { dist_t dist1 = it1.value(), dist2 = it2.value(); auto reg = reg_with_loc_t((register_t)i, loc); - if (dist1 == dist2) { - out_vars[i] = m_cur_def[i]; - } + if (dist1.m_slack != dist2.m_slack) continue; + auto dist_joined = dist_t(std::move(dist1.m_dist | dist2.m_dist), dist1.m_slack); + out_vars[i] = std::make_shared(reg); + (*m_offset_env)[reg] = dist_joined; } } return registers_state_t(std::move(out_vars), m_offset_env, false); @@ -100,15 +202,25 @@ void registers_state_t::adjust_bb_for_registers(location_t loc) { location_t old_loc = location_t(std::make_pair(label_t(-2, -2), 0)); for (size_t i = 0; i < m_cur_def.size(); i++) { auto new_reg = reg_with_loc_t((register_t)i, loc); + auto old_reg = reg_with_loc_t((register_t)i, old_loc); + auto it = find((register_t)i); if (!it) continue; - m_cur_def[i] = std::make_shared(new_reg); - (*m_offset_env)[new_reg] = it.value(); - auto old_reg = reg_with_loc_t((register_t)i, old_loc); if (*m_cur_def[i] == old_reg) m_offset_env->erase(old_reg); + + m_cur_def[i] = std::make_shared(new_reg); + (*m_offset_env)[new_reg] = it.value(); + } +} + +void registers_state_t::print_all_register_types() const { + std::cout << "\toffset types: {\n"; + for (auto const& kv : *m_offset_env) { + std::cout << "\t\t" << kv.first << " : " << kv.second << "\n"; } + std::cout << "\t}\n"; } void stack_state_t::set_to_top() { @@ -166,77 +278,130 @@ stack_state_t stack_state_t::operator|(const stack_state_t& other) const { return stack_state_t(std::move(out_stack_dists), false); } -void extra_constraints_t::set_to_top() { - add_equality(forward_and_backward_eq_t()); - add_inequality(inequality_t()); -} - -void extra_constraints_t::set_to_bottom() { - m_is_bottom = true; -} - bool extra_constraints_t::is_top() const { if (m_is_bottom) return false; - return (m_eq.m_forw.m_slack == -1 && m_ineq.m_slack == -1); + return (m_meta_and_begin.is_top() && m_begin_and_end.is_top()); } bool extra_constraints_t::is_bottom() const { return m_is_bottom; } -void extra_constraints_t::add_equality(forward_and_backward_eq_t fabeq) { - m_eq = std::move(fabeq); +void extra_constraints_t::set_to_top() { + m_meta_and_begin.set_to_top(); + m_begin_and_end.set_to_top(); + m_is_bottom = false; } -void extra_constraints_t::add_inequality(inequality_t ineq) { - m_ineq = std::move(ineq); +void extra_constraints_t::set_to_bottom() { + m_is_bottom = true; } -weight_t extra_constraints_t::get_limit() const { - return m_eq.m_forw.m_dist; +void extra_constraints_t::add_meta_and_begin_constraint(equality_t&& eq, + inequality_t&& ineq) { + m_meta_and_begin = packet_constraint_t(std::move(eq), std::move(ineq), true); } +void extra_constraints_t::add_begin_and_end_constraint(equality_t&& eq, + inequality_t&& ineq) { + m_begin_and_end = packet_constraint_t(std::move(eq), std::move(ineq), false); +} +/* void extra_constraints_t::normalize() { - weight_t dist_forw = m_eq.m_forw.m_dist - m_eq.m_backw.m_dist - 4099; - weight_t dist_backw = -4099; - slack_var_t s = m_eq.m_forw.m_slack; - dist_forw += m_ineq.m_value; + weight_t dist_lhs = m_eq.m_lhs.m_dist - m_eq.m_rhs.m_dist - 4099; + weight_t dist_rhs = -4099; + slack_var_t s = m_eq.m_lhs.m_slack; + dist_lhs += m_ineq.m_value; weight_t ineq_val = 0; rop_t ineq_rel = m_ineq.m_rel; - m_eq = forward_and_backward_eq_t(dist_t(dist_forw, s), dist_t(dist_backw)); + m_eq = equality_t(dist_t(dist_lhs, s), dist_t(dist_rhs)); m_ineq = inequality_t(s, ineq_rel, ineq_val); } +*/ -extra_constraints_t extra_constraints_t::operator|(const extra_constraints_t& other) const { +packet_constraint_t packet_constraint_t::operator|(const packet_constraint_t& other) const { //normalize(); //other.normalize(); - weight_t dist1 = m_eq.m_forw.m_dist; - weight_t dist2 = other.m_eq.m_forw.m_dist; - slack_var_t s = m_eq.m_forw.m_slack; + weight_t dist1 = m_eq.m_lhs.m_dist; + weight_t dist2 = other.m_eq.m_lhs.m_dist; + slack_var_t s = m_eq.m_lhs.m_slack; - dist_t f = dist_t(std::min(dist1, dist2), s); - dist_t b = dist_t(-4099); + dist_t lhs = dist_t(dist1 | dist2, s); + dist_t rhs; + if (m_is_meta_constraint) rhs = dist_t(weight_t{number_t{PACKET_BEGIN}}); + else rhs = dist_t(weight_t{number_t{PACKET_END}}); - forward_and_backward_eq_t out_eq(f, b); - inequality_t out_ineq(s, m_ineq.m_rel, 0); - - return extra_constraints_t(std::move(out_eq), std::move(out_ineq), false); + equality_t out_eq(lhs, rhs); + inequality_t out_ineq(s, m_ineq.m_rel, weight_t{number_t{0}}); + return packet_constraint_t(std::move(out_eq), std::move(out_ineq), m_is_meta_constraint); // have to handle case for different slack vars } +std::ostream& operator<<(std::ostream& o, const packet_constraint_t& p) { + p.write(o); + return o; +} + +void packet_constraint_t::write(std::ostream& o) const { + o << m_eq << "\n"; + o << m_ineq << "\n"; +} + +void packet_constraint_t::set_to_top() { + m_eq.set_to_top(); + m_ineq.set_to_top(); + m_is_bottom = false; +} + +void packet_constraint_t::set_to_bottom() { + m_is_bottom = true; +} + +bool packet_constraint_t::is_top() const { + if (m_is_bottom) return false; + return (m_eq.is_top() && m_ineq.is_top()); +} + +bool packet_constraint_t::is_bottom() const { + return m_is_bottom; +} + +std::optional packet_constraint_t::get_limit() const { + // TODO: normalize constraint, if required + auto dist = m_eq.m_lhs.m_dist; + if (dist.is_top()) return {}; + return dist.ub(); +} + +extra_constraints_t extra_constraints_t::operator|(const extra_constraints_t& other) const { + auto meta_and_begin = m_meta_and_begin | other.m_meta_and_begin; + auto begin_and_end = m_begin_and_end | other.m_begin_and_end; + return extra_constraints_t(std::move(meta_and_begin), std::move(begin_and_end), false); +} + +std::optional extra_constraints_t::get_end_limit() const { + return m_begin_and_end.get_limit(); +} + +std::optional extra_constraints_t::get_meta_limit() const { + return m_meta_and_begin.get_limit(); +} + ctx_offsets_t::ctx_offsets_t(const ebpf_context_descriptor_t* desc) { - if (desc->data != -1) { - m_dists[desc->data] = dist_t(0); + if (desc->data >= 0) { + m_dists[desc->data] = dist_t(weight_t{number_t{PACKET_BEGIN}}); + } + if (desc->end >= 0) { + m_dists[desc->end] = dist_t(weight_t{number_t{PACKET_END}}); } - if (desc->end != -1) { - m_dists[desc->end] = dist_t(-4099); + if (desc->meta >= 0) { + m_dists[desc->meta] = dist_t(weight_t{number_t{PACKET_META}}); } - if (desc->meta != -1) { - m_dists[desc->meta] = dist_t(-1); + if (desc->size >= 0) { + m_size = desc->size; } - m_size = desc->size; } int ctx_offsets_t::get_size() const { @@ -366,9 +531,6 @@ void offset_domain_t::operator()(const Assume &b, location_t loc, int print) { auto dist_left = m_reg_state.find(cond.left.v); auto dist_right = m_reg_state.find(right_reg); if (!dist_left && !dist_right) { - return; - } - else if (!dist_left || !dist_right) { // this should not happen, comparison between a packet pointer and either // other region's pointers or numbers; possibly raise type error //exit(1); @@ -379,9 +541,15 @@ void offset_domain_t::operator()(const Assume &b, location_t loc, int print) { dist_t right_reg_dist = dist_right.value(); slack_var_t s = m_slack++; dist_t f = dist_t(left_reg_dist.m_dist, s); - dist_t b = dist_t(right_reg_dist.m_dist, slack_var_t{-1}); - m_extra_constraints.add_equality(forward_and_backward_eq_t(f, b)); - m_extra_constraints.add_inequality(inequality_t(s, rop_t::R_GE, 0)); + dist_t b = dist_t(right_reg_dist.m_dist); + auto eq = equality_t(f, b); + auto ineq = inequality_t(s, rop_t::R_GE, weight_t{number_t{0}}); + if (f.is_meta_pointer() && b.is_forward_pointer()) { + m_extra_constraints.add_meta_and_begin_constraint(std::move(eq), std::move(ineq)); + } + else if (f.is_forward_pointer() && b.is_backward_pointer()) { + m_extra_constraints.add_begin_and_end_constraint(std::move(eq), std::move(ineq)); + } } } else {} //we do not need to deal with other cases @@ -411,109 +579,132 @@ bool is_stack_pointer(std::optional& type) { return false; } -void offset_domain_t::do_bin(const Bin& bin, std::optional src_const_value, - std::optional src_type, std::optional dst_type, - location_t loc) { - if (is_bottom()) return; +void offset_domain_t::update_offset_info(const dist_t&& dist, const interval_t&& change, + const reg_with_loc_t& reg_with_loc, uint8_t reg, Bin::Op op) { + auto offset = dist.m_dist; + if (op == Bin::Op::ADD) { + if (dist.is_forward_pointer()) offset += change; + else if (dist.is_backward_pointer()) offset -= change; + else offset -= change; + } + else if (op == Bin::Op::SUB) { + // TODO: needs precise handling of subtraction + offset = interval_t::top(); + } + m_reg_state.insert(reg, reg_with_loc, dist_t(offset)); +} + +interval_t offset_domain_t::do_bin(const Bin &bin, + const std::optional& src_interval_opt, + const std::optional& dst_interval_opt, + std::optional& src_ptr_or_mapfd_opt, + std::optional& dst_ptr_or_mapfd_opt, location_t loc) { + using Op = Bin::Op; + // if both src and dst are numbers, nothing to do in offset domain + // if we are doing a move, where src is a number and dst is not set, nothing to do + if ((dst_interval_opt && src_interval_opt) + || (src_interval_opt && !dst_ptr_or_mapfd_opt && bin.op == Op::MOV)) + return interval_t::bottom(); + // offset domain only handles packet pointers + if (!is_packet_pointer(src_ptr_or_mapfd_opt) && !is_packet_pointer(dst_ptr_or_mapfd_opt)) + return interval_t::bottom(); + + interval_t src_interval = interval_t::bottom(), dst_interval = interval_t::bottom(); + if (src_interval_opt) src_interval = std::move(src_interval_opt.value()); + if (dst_interval_opt) dst_interval = std::move(dst_interval_opt.value()); + + Reg src; + if (std::holds_alternative(bin.v)) src = std::get(bin.v); auto reg_with_loc = reg_with_loc_t(bin.dst.v, loc); - if (std::holds_alternative(bin.v)) { - Reg src = std::get(bin.v); - switch (bin.op) - { - // ra = rb; - case Bin::Op::MOV: { - if (!is_packet_pointer(src_type)) { - m_reg_state -= bin.dst.v; - return; - } - auto it = m_reg_state.find(src.v); - if (!it) { - std::cout << "type_error: src is a packet_pointer and no offset info found\n"; - //exit(1); - return; - } - m_reg_state.insert(bin.dst.v, reg_with_loc, it.value()); - //std::cout << "offset: " << (*m_reg_state.get(bin.dst.v)).m_dist << "\n"; - break; + switch (bin.op) + { + // ra = rb; + case Op::MOV: { + if (!is_packet_pointer(src_ptr_or_mapfd_opt)) { + m_reg_state -= bin.dst.v; + return interval_t::bottom(); } - // ra += rb - case Bin::Op::ADD: { - if (!is_packet_pointer(dst_type)) { - m_reg_state -= bin.dst.v; - return; - } - auto it = m_reg_state.find(bin.dst.v); - if (!it) { + auto src_offset_opt = m_reg_state.find(src.v); + if (!src_offset_opt) { + std::cout << "type_error: src is a packet_pointer and no offset info found\n"; + return interval_t::bottom(); + } + m_reg_state.insert(bin.dst.v, reg_with_loc, src_offset_opt.value()); + break; + } + // ra += rb + case Op::ADD: { + dist_t dist_to_update; + interval_t interval_to_add = interval_t::bottom(); + if (is_packet_pointer(dst_ptr_or_mapfd_opt) + && is_packet_pointer(src_ptr_or_mapfd_opt)) { + m_reg_state -= bin.dst.v; + return interval_t::bottom(); + } + else if (is_packet_pointer(dst_ptr_or_mapfd_opt) && src_interval_opt) { + auto dst_offset_opt = m_reg_state.find(bin.dst.v); + if (!dst_offset_opt) { std::cout << "type_error: dst is a packet_pointer and no offset info found\n"; - //exit(1); - return; - } - auto dst_dist = it.value(); - if (src_const_value) { - weight_t updated_dist; - if (dst_dist.m_dist >= 0) { - updated_dist = dst_dist.m_dist + src_const_value.value(); - } - else if (dst_dist.m_dist >= -4098) { - // TODO: special handling of meta pointer required - updated_dist = dst_dist.m_dist - src_const_value.value(); - } - else { - updated_dist = dst_dist.m_dist - src_const_value.value(); - } - m_reg_state.insert(bin.dst.v, reg_with_loc, dist_t(updated_dist)); + m_reg_state -= bin.dst.v; + return interval_t::bottom(); } - else { + dist_to_update = std::move(dst_offset_opt.value()); + interval_to_add = std::move(src_interval_opt.value()); + } + else { + auto src_offset_opt = m_reg_state.find(src.v); + if (!src_offset_opt) { + std::cout << "type_error: src is a packet_pointer and no offset info found\n"; m_reg_state -= bin.dst.v; + return interval_t::bottom(); } - break; + dist_to_update = std::move(src_offset_opt.value()); + interval_to_add = std::move(dst_interval_opt.value()); } - - default: { + update_offset_info(std::move(dist_to_update), std::move(interval_to_add), + reg_with_loc, bin.dst.v, bin.op); + break; + } + // ra -= rb + case Op::SUB: { + dist_t dist_to_update; + interval_t interval_to_sub = interval_t::bottom(); + if (is_packet_pointer(dst_ptr_or_mapfd_opt) + && is_packet_pointer(src_ptr_or_mapfd_opt)) { m_reg_state -= bin.dst.v; - break; + return interval_t::top(); } - } - } - else { - int imm = static_cast(std::get(bin.v).v); - auto it = m_reg_state.find(bin.dst.v); - switch (bin.op) - { - case Bin::Op::ADD: { - if (!is_packet_pointer(dst_type)) { - m_reg_state -= bin.dst.v; - return; - } - if (!it) { + else if (is_packet_pointer(dst_ptr_or_mapfd_opt) && src_interval_opt) { + auto dst_offset_opt = m_reg_state.find(bin.dst.v); + if (!dst_offset_opt) { std::cout << "type_error: dst is a packet_pointer and no offset info found\n"; - //exit(1); - return; - } - auto dst_dist = it.value(); - - weight_t updated_dist; - if (dst_dist.m_dist >= 0) { - updated_dist = dst_dist.m_dist + imm; - } - else if (dst_dist.m_dist >= -4098) { - // TODO: special handling of meta pointer required - updated_dist = dst_dist.m_dist - imm; - } - else { - updated_dist = dst_dist.m_dist - imm; + m_reg_state -= bin.dst.v; + return interval_t::bottom(); } - m_reg_state.insert(bin.dst.v, reg_with_loc, dist_t(updated_dist)); - break; + dist_to_update = std::move(dst_offset_opt.value()); + interval_to_sub = std::move(src_interval_opt.value()); } - - default: { - m_reg_state -= bin.dst.v; - break; + else { + auto src_offset_opt = m_reg_state.find(src.v); + if (!src_offset_opt) { + std::cout << "type_error: src is a packet_pointer and no offset info found\n"; + m_reg_state -= bin.dst.v; + return interval_t::bottom(); + } + dist_to_update = std::move(src_offset_opt.value()); + interval_to_sub = std::move(dst_interval_opt.value()); } + update_offset_info(std::move(dist_to_update), std::move(interval_to_sub), + reg_with_loc, bin.dst.v, bin.op); + break; + } + default: { + m_reg_state -= bin.dst.v; + break; } } + return interval_t::bottom(); } void offset_domain_t::operator()(const Bin& bin, location_t loc, int print) { @@ -582,24 +773,64 @@ void offset_domain_t::operator()(const ZeroCtxOffset&, location_t loc, int print // nothing to do here } -void offset_domain_t::check_valid_access(const ValidAccess& s, - std::optional& reg_type) { - bool is_comparison_check = s.width == (Value)Imm{0}; +bool offset_domain_t::lower_bound_satisfied(const dist_t& dist, int offset) const { + auto meta_limit = m_extra_constraints.get_meta_limit(); + auto end_limit = m_extra_constraints.get_end_limit(); + + dist_t dist1 = dist; + if (dist.is_meta_pointer()) { + dist1 = dist_t(dist.offset_from_reference() + (meta_limit ? + weight_t{*meta_limit-number_t{PACKET_META}} : weight_t{number_t{0}})); + } + if (dist.is_backward_pointer()) { + dist1 = dist_t(dist.offset_from_reference() + + (end_limit ? weight_t{*end_limit} : weight_t{number_t{0}})); + } + + bound_t lb = meta_limit ? *meta_limit-number_t{PACKET_META} : bound_t{number_t{0}}; + return (dist1.m_dist.lb()+number_t{offset} >= lb); +} + +bool offset_domain_t::upper_bound_satisfied(const dist_t& dist, int offset, int width, + bool is_comparison_check) const { + auto meta_limit = m_extra_constraints.get_meta_limit(); + auto end_limit = m_extra_constraints.get_end_limit(); + dist_t dist1 = dist; + if (dist.is_meta_pointer()) { + dist1 = dist_t(dist.offset_from_reference() + (meta_limit ? + weight_t{*meta_limit-number_t{PACKET_META}} : weight_t{number_t{0}})); + } + if (dist.is_backward_pointer()) { + dist1 = dist_t(dist.offset_from_reference() + + (end_limit ? weight_t{*end_limit} : + weight_t{number_t{is_comparison_check ? MAX_PACKET_SIZE : 0}})); + } + + bound_t ub = is_comparison_check ? bound_t{MAX_PACKET_SIZE} + : (end_limit ? *end_limit : number_t{0}); + return (dist1.m_dist.ub()+number_t{offset+width} <= ub); +} + +bool offset_domain_t::check_packet_access(const Reg& r, int width, int offset, + bool is_comparison_check) const { + auto it = m_reg_state.find(r.v); + if (!it) return false; + dist_t dist = it.value(); + + return (lower_bound_satisfied(dist, offset) + && upper_bound_satisfied(dist, offset, width, is_comparison_check)); +} + +void offset_domain_t::check_valid_access(const ValidAccess& s, + std::optional& reg_type, + std::optional, std::optional) const { if (std::holds_alternative(s.width)) return; int w = std::get(s.width).v; if (w == 0 || !reg_type) return; - m_extra_constraints.normalize(); - if (std::holds_alternative(*reg_type)) { - auto it = m_reg_state.find(s.reg.v); - int limit = m_extra_constraints.get_limit(); - if (it) { - dist_t dist = it.value(); - // TODO: handle meta and end pointers separately - if (dist.m_dist >= PACKET_BEGIN && dist.m_dist+w <= limit) return; - } - } + bool is_comparison_check = s.width == (Value)Imm{0}; + if (check_packet_access(s.reg, w, s.offset, is_comparison_check)) return; std::cout << "valid access assert fail\n"; //exit(1); } @@ -663,10 +894,14 @@ void offset_domain_t::do_load(const Mem& b, const Reg& target_reg, m_reg_state -= target_reg.v; return; } - int to_load = (int)(*offset_singleton + offset); if (p_with_off.get_region() == crab::region_t::T_CTX) { - auto it = m_ctx_dists->find(to_load); + if (!offset_singleton) { + m_reg_state -= target_reg.v; + return; + } + auto load_at = (uint64_t)offset_singleton.value() + (uint64_t)offset; + auto it = m_ctx_dists->find(load_at); if (!it) { m_reg_state -= target_reg.v; return; @@ -676,7 +911,13 @@ void offset_domain_t::do_load(const Mem& b, const Reg& target_reg, m_reg_state.insert(target_reg.v, reg, dist_t(d)); } else if (p_with_off.get_region() == crab::region_t::T_STACK) { - auto it = m_stack_state.find(to_load); + if (!offset_singleton) { + m_reg_state -= target_reg.v; + return; + } + auto ptr_offset = offset_singleton.value(); + auto load_at = (uint64_t)(ptr_offset + offset); + auto it = m_stack_state.find(load_at); if (!it) { m_reg_state -= target_reg.v; @@ -686,16 +927,16 @@ void offset_domain_t::do_load(const Mem& b, const Reg& target_reg, auto reg = reg_with_loc_t(target_reg.v, loc); m_reg_state.insert(target_reg.v, reg, dist_t(d)); } + else { // shared + m_reg_state -= target_reg.v; + } } - else { // we are loading from packet or shared, or we have mapfd + else { // we are loading from packet, or we have mapfd m_reg_state -= target_reg.v; } } -void offset_domain_t::operator()(const Mem &b, location_t loc, int print) { -} - -std::optional offset_domain_t::find_in_registers(const reg_with_loc_t reg) const { +std::optional offset_domain_t::find_offset_at_loc(const reg_with_loc_t reg) const { return m_reg_state.find(reg); } diff --git a/src/crab/offset_domain.hpp b/src/crab/offset_domain.hpp index 16b8cf366..cdfb33992 100644 --- a/src/crab/offset_domain.hpp +++ b/src/crab/offset_domain.hpp @@ -13,10 +13,12 @@ namespace crab { constexpr int STACK_BEGIN = 0; constexpr int CTX_BEGIN = 0; constexpr int PACKET_BEGIN = 0; +constexpr int SHARED_BEGIN = 0; +constexpr int PACKET_END = -4100; +constexpr int PACKET_META = -1; +constexpr int MAX_PACKET_SIZE = 0xffff; -using constant_t = int; // define a domain for constants -//using symbol_t = register_t; // a register with unknown value -using weight_t = constant_t; // should be constants + symbols +using weight_t = interval_t; using slack_var_t = int; enum class rop_t { @@ -29,32 +31,76 @@ enum class rop_t { struct dist_t { slack_var_t m_slack; weight_t m_dist; + bool m_is_bottom = false; - dist_t(weight_t d, slack_var_t s = -1) : m_slack(s), m_dist(d) {} - dist_t() : m_slack(-1), m_dist(0) {} + dist_t(weight_t d, slack_var_t s = -1, bool bottom = false) + : m_slack(s), m_dist(d), m_is_bottom(bottom) {} + dist_t() : m_slack(-1), m_dist(weight_t::top()), m_is_bottom(false) {} bool operator==(const dist_t& d) const; void write(std::ostream&) const; + bool is_top() const; + bool is_bottom() const; + void set_to_top(); + void set_to_bottom(); friend std::ostream& operator<<(std::ostream& o, const dist_t& d); -}; // if dist is +ve, represents `begin+dist+slack;`, if dist is -ve, represents `end+dist+1` + bool is_meta_pointer() const; + bool is_forward_pointer() const; + bool is_backward_pointer() const; + weight_t offset_from_reference() const; +}; struct inequality_t { slack_var_t m_slack; rop_t m_rel; weight_t m_value; + bool m_is_bottom = false; inequality_t(slack_var_t slack, rop_t rel, weight_t val) : m_slack(slack), m_rel(rel) , m_value(val) {} - inequality_t() = default; + inequality_t() : m_slack(-1), m_value(weight_t::top()) {} + bool is_top() const; + bool is_bottom() const; + void set_to_top(); + void set_to_bottom(); + void write(std::ostream&) const; + friend std::ostream& operator<<(std::ostream&, const inequality_t&); }; // represents `slack rel value;`, e.g., `s >= 0` -struct forward_and_backward_eq_t { - dist_t m_forw; - dist_t m_backw; +struct equality_t { + dist_t m_lhs; + dist_t m_rhs; + bool m_is_bottom = false; - forward_and_backward_eq_t(dist_t forw, dist_t backw) : m_forw(forw), m_backw(backw) {} - forward_and_backward_eq_t() = default; + equality_t(dist_t lhs, dist_t rhs) : m_lhs(lhs), m_rhs(rhs) {} + equality_t() = default; + bool is_top() const; + bool is_bottom() const; + void set_to_top(); + void set_to_bottom(); + void write(std::ostream&) const; + friend std::ostream& operator<<(std::ostream&, const equality_t&); }; // represents constraint `p[0] = p[1];`, e.g., `begin+8+s = end` +struct packet_constraint_t { + equality_t m_eq; + inequality_t m_ineq; + bool m_is_meta_constraint; + bool m_is_bottom = false; + + bool is_bottom() const; + bool is_top() const; + void set_to_bottom(); + void set_to_top(); + std::optional get_limit() const; + packet_constraint_t() = default; + packet_constraint_t(equality_t&& eq, inequality_t&& ineq, bool is_meta_constraint, + bool is_bottom = false) : m_eq(eq), m_ineq(ineq), + m_is_meta_constraint(is_meta_constraint), m_is_bottom(is_bottom) {} + packet_constraint_t operator|(const packet_constraint_t&) const; + void write(std::ostream&) const; + friend std::ostream& operator<<(std::ostream&, const packet_constraint_t&); +}; + using live_registers_t = std::array, 11>; using global_offset_env_t = std::unordered_map; @@ -82,6 +128,7 @@ class registers_state_t { std::optional find(register_t key) const; friend std::ostream& operator<<(std::ostream& o, const registers_state_t& p); void adjust_bb_for_registers(location_t); + void print_all_register_types() const; }; class stack_state_t { @@ -107,22 +154,26 @@ class stack_state_t { class extra_constraints_t { - forward_and_backward_eq_t m_eq; - inequality_t m_ineq; + packet_constraint_t m_meta_and_begin; + packet_constraint_t m_begin_and_end; bool m_is_bottom = false; public: extra_constraints_t(bool is_bottom = false) : m_is_bottom(is_bottom) {} - void set_to_top(); - void set_to_bottom(); bool is_bottom() const; bool is_top() const; - void add_equality(forward_and_backward_eq_t); - void add_inequality(inequality_t); + void set_to_top(); + void set_to_bottom(); void normalize(); - weight_t get_limit() const; + std::optional get_end_limit() const; + std::optional get_meta_limit() const; + void add_meta_and_begin_constraint(equality_t&&, inequality_t&&); + void add_begin_and_end_constraint(equality_t&&, inequality_t&&); extra_constraints_t operator|(const extra_constraints_t&) const; - explicit extra_constraints_t(forward_and_backward_eq_t&& fabeq, inequality_t ineq, bool is_bottom = false) : m_eq(fabeq), m_ineq(ineq), m_is_bottom(is_bottom) {} + explicit extra_constraints_t(packet_constraint_t&& meta_and_begin, + packet_constraint_t&& begin_and_end, bool is_bottom = false) + : m_meta_and_begin(meta_and_begin), m_begin_and_end(begin_and_end), + m_is_bottom(is_bottom) {} }; class ctx_offsets_t { @@ -184,6 +235,7 @@ class offset_domain_t final { offset_domain_t narrow(const offset_domain_t& other) const; //forget void operator-=(variable_t var); + void operator-=(register_t reg) { m_reg_state -= reg; } //// abstract transformers void operator()(const Undefined&, location_t loc = boost::none, int print = 0); @@ -220,14 +272,23 @@ class offset_domain_t final { void do_load(const Mem&, const Reg&, std::optional, location_t loc); void do_mem_store(const Mem&, std::optional&); - void do_bin(const Bin&, std::optional, std::optional, - std::optional, location_t); - void check_valid_access(const ValidAccess&, std::optional&); + interval_t do_bin(const Bin&, const std::optional&, + const std::optional&, + std::optional&, + std::optional&, location_t); + bool upper_bound_satisfied(const dist_t&, int, int, bool) const; + bool lower_bound_satisfied(const dist_t&, int) const; + bool check_packet_access(const Reg&, int, int, bool) const; + void check_valid_access(const ValidAccess&, std::optional&, + std::optional, std::optional) const; std::optional find_in_ctx(int) const; std::optional find_in_stack(int) const; - std::optional find_in_registers(const reg_with_loc_t) const; + std::optional find_offset_at_loc(const reg_with_loc_t) const; std::optional find_offset_info(register_t reg) const; + void update_offset_info(const dist_t&&, const interval_t&&, + const reg_with_loc_t&, uint8_t, Bin::Op); + dist_t update_offset(const dist_t&, const weight_t&, const interval_t&, Bin::Op); void adjust_bb_for_types(location_t); [[nodiscard]] std::vector& get_errors() { return m_errors; } }; // end offset_domain_t diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 63f78834f..22519b75d 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -70,7 +70,7 @@ type_domain_t type_domain_t::operator|(type_domain_t&& other) const { else if (other.is_bottom() || is_top()) { return *this; } - return type_domain_t(m_region | std::move(other.m_region), m_offset | std::move(m_offset)); + return type_domain_t(m_region | std::move(other.m_region), m_offset | std::move(other.m_offset)); } type_domain_t type_domain_t::operator&(const type_domain_t& abs) const { @@ -176,7 +176,6 @@ void type_domain_t::operator()(const Assume& s, location_t loc, int print) { m_region.set_registers_to_top(); } } - else {} m_offset(s, loc, print); } @@ -187,7 +186,8 @@ void type_domain_t::operator()(const ValidDivisor& s, location_t loc, int print) void type_domain_t::operator()(const ValidAccess& s, location_t loc, int print) { m_region(s, loc); auto reg_type = m_region.find_ptr_or_mapfd_type(s.reg.v); - m_offset.check_valid_access(s, reg_type); + std::optional width_interval = {}; + m_offset.check_valid_access(s, reg_type, std::nullopt, width_interval); } void type_domain_t::operator()(const TypeConstraint& s, location_t loc, int print) { @@ -253,7 +253,9 @@ void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int pr auto maybe_ptr_or_mapfd_basereg = m_region.find_ptr_or_mapfd_type(u.access_reg.v); auto maybe_mapfd = m_region.find_ptr_or_mapfd_type(u.map_fd_reg.v); if (maybe_ptr_or_mapfd_basereg && maybe_mapfd) { + auto mapfd = maybe_mapfd.value(); if (is_mapfd_type(maybe_mapfd)) { + // TODO: define width auto ptr_or_mapfd_basereg = maybe_ptr_or_mapfd_basereg.value(); if (std::holds_alternative(ptr_or_mapfd_basereg)) { auto ptr_with_off = std::get(ptr_or_mapfd_basereg); @@ -274,10 +276,11 @@ void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int pr } } else if (std::holds_alternative(ptr_or_mapfd_basereg)) { - // We do not check packet ptr accesses yet - return; + if (m_offset.check_packet_access(u.access_reg, width, 0, true)) return; + } + else { + m_errors.push_back("Only stack or packet can be used as a parameter"); } - m_errors.push_back("Only stack or packet can be used as a parameter"); } } //std::cout << "type error: valid map key value assertion failed\n"; @@ -321,13 +324,17 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { // for all operations except mov, add, sub, the src and dst should be numbers if ((src_ptr_or_mapfd || dst_ptr_or_mapfd) && (bin.op != Op::MOV && bin.op != Op::ADD && bin.op != Op::SUB)) { - //std::cout << "type error: operation on pointers not allowed\n"; - m_errors.push_back("operation on pointers not allowed"); + std::cout << "type error: operation on pointers not allowed\n"; m_region -= bin.dst.v; + m_offset -= bin.dst.v; return; } - m_region.do_bin(bin, src_interval, src_ptr_or_mapfd, dst_ptr_or_mapfd, loc); - m_offset.do_bin(bin, std::nullopt, src_ptr_or_mapfd, dst_ptr_or_mapfd, loc); + + interval_t subtracted_reg = + m_region.do_bin(bin, src_interval, src_ptr_or_mapfd, dst_ptr_or_mapfd, loc); + interval_t subtracted_off = + m_offset.do_bin(bin, src_interval, interval_t::top(), src_ptr_or_mapfd, dst_ptr_or_mapfd, loc); + auto subtracted = subtracted_reg.is_bottom() ? subtracted_off : subtracted_reg; } void type_domain_t::do_load(const Mem& b, const Reg& target_reg, bool unknown_ptr, From 706bf396db39b0a47c0ba28d4157a9e04b388667 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Wed, 9 Aug 2023 16:47:13 -0400 Subject: [PATCH 117/373] Error reporting similar to how zoneCrab reports errors Signed-off-by: Ameer Hamza --- src/crab/offset_domain.cpp | 33 +++++++++++++++++++++------------ src/crab/offset_domain.hpp | 2 +- src/crab/type_domain.cpp | 3 ++- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index 27a95ca3a..34a21aa95 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -533,8 +533,8 @@ void offset_domain_t::operator()(const Assume &b, location_t loc, int print) { if (!dist_left && !dist_right) { // this should not happen, comparison between a packet pointer and either // other region's pointers or numbers; possibly raise type error - //exit(1); - std::cout << "type_error: one of the pointers being compared isn't packet pointer\n"; + m_errors.push_back("one of the pointers being compared isn't packet pointer"); + //std::cout << "type_error: one of the pointers being compared isn't packet pointer\n"; return; } dist_t left_reg_dist = dist_left.value(); @@ -627,7 +627,8 @@ interval_t offset_domain_t::do_bin(const Bin &bin, } auto src_offset_opt = m_reg_state.find(src.v); if (!src_offset_opt) { - std::cout << "type_error: src is a packet_pointer and no offset info found\n"; + m_errors.push_back("src is a packet_pointer and no offset info found"); + //std::cout << "type_error: src is a packet_pointer and no offset info found\n"; return interval_t::bottom(); } m_reg_state.insert(bin.dst.v, reg_with_loc, src_offset_opt.value()); @@ -645,7 +646,8 @@ interval_t offset_domain_t::do_bin(const Bin &bin, else if (is_packet_pointer(dst_ptr_or_mapfd_opt) && src_interval_opt) { auto dst_offset_opt = m_reg_state.find(bin.dst.v); if (!dst_offset_opt) { - std::cout << "type_error: dst is a packet_pointer and no offset info found\n"; + m_errors.push_back("dst is a packet_pointer and no offset info found"); + //std::cout << "type_error: dst is a packet_pointer and no offset info found\n"; m_reg_state -= bin.dst.v; return interval_t::bottom(); } @@ -655,7 +657,8 @@ interval_t offset_domain_t::do_bin(const Bin &bin, else { auto src_offset_opt = m_reg_state.find(src.v); if (!src_offset_opt) { - std::cout << "type_error: src is a packet_pointer and no offset info found\n"; + m_errors.push_back("src is a packet_pointer and no offset info found"); + //std::cout << "type_error: src is a packet_pointer and no offset info found\n"; m_reg_state -= bin.dst.v; return interval_t::bottom(); } @@ -678,7 +681,8 @@ interval_t offset_domain_t::do_bin(const Bin &bin, else if (is_packet_pointer(dst_ptr_or_mapfd_opt) && src_interval_opt) { auto dst_offset_opt = m_reg_state.find(bin.dst.v); if (!dst_offset_opt) { - std::cout << "type_error: dst is a packet_pointer and no offset info found\n"; + m_errors.push_back("dst is a packet_pointer and no offset info found"); + //std::cout << "type_error: dst is a packet_pointer and no offset info found\n"; m_reg_state -= bin.dst.v; return interval_t::bottom(); } @@ -688,7 +692,8 @@ interval_t offset_domain_t::do_bin(const Bin &bin, else { auto src_offset_opt = m_reg_state.find(src.v); if (!src_offset_opt) { - std::cout << "type_error: src is a packet_pointer and no offset info found\n"; + m_errors.push_back("src is a packet_pointer and no offset info found"); + //std::cout << "type_error: src is a packet_pointer and no offset info found\n"; m_reg_state -= bin.dst.v; return interval_t::bottom(); } @@ -824,14 +829,15 @@ bool offset_domain_t::check_packet_access(const Reg& r, int width, int offset, void offset_domain_t::check_valid_access(const ValidAccess& s, std::optional& reg_type, - std::optional, std::optional) const { + std::optional, std::optional) { if (std::holds_alternative(s.width)) return; int w = std::get(s.width).v; if (w == 0 || !reg_type) return; bool is_comparison_check = s.width == (Value)Imm{0}; if (check_packet_access(s.reg, w, s.offset, is_comparison_check)) return; - std::cout << "valid access assert fail\n"; + m_errors.push_back("valid access check failed"); + //std::cout << "type_error: valid access assert fail\n"; //exit(1); } @@ -857,14 +863,16 @@ void offset_domain_t::do_mem_store(const Mem& b, std::optional& auto basereg_off = basereg_with_off.get_offset().to_interval(); auto basereg_off_singleton = basereg_off.singleton(); if (!basereg_off_singleton) { - std::cout << "type_error: basereg offset is not a singleton\n"; + m_errors.push_back("basereg offset is not a singleton"); + //std::cout << "type_error: basereg offset is not a singleton\n"; return; } auto store_at = (int)(*basereg_off_singleton + offset); //if (is_packet_pointer(targetreg_type)) { auto it = m_reg_state.find(target_reg.v); if (!it) { - std::cout << "type_error: register is a packet_pointer and no offset info found\n"; + m_errors.push_back("register is a packet_pointer and no offset info found"); + //std::cout << "type_error: register is a packet_pointer and no offset info found\n"; m_stack_state -= store_at; return; } @@ -890,7 +898,8 @@ void offset_domain_t::do_load(const Mem& b, const Reg& target_reg, auto p_with_off = std::get(basereg_ptr_type); auto offset_singleton = p_with_off.get_offset().to_interval().singleton(); if (!offset_singleton) { - std::cout << "type_error: basereg offset is not a singleton\n"; + m_errors.push_back("basereg offset is not a singleton"); + //std::cout << "type_error: basereg offset is not a singleton\n"; m_reg_state -= target_reg.v; return; } diff --git a/src/crab/offset_domain.hpp b/src/crab/offset_domain.hpp index cdfb33992..902fc48f2 100644 --- a/src/crab/offset_domain.hpp +++ b/src/crab/offset_domain.hpp @@ -280,7 +280,7 @@ class offset_domain_t final { bool lower_bound_satisfied(const dist_t&, int) const; bool check_packet_access(const Reg&, int, int, bool) const; void check_valid_access(const ValidAccess&, std::optional&, - std::optional, std::optional) const; + std::optional, std::optional); std::optional find_in_ctx(int) const; std::optional find_in_stack(int) const; diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 22519b75d..af6c1b8ed 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -324,7 +324,8 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { // for all operations except mov, add, sub, the src and dst should be numbers if ((src_ptr_or_mapfd || dst_ptr_or_mapfd) && (bin.op != Op::MOV && bin.op != Op::ADD && bin.op != Op::SUB)) { - std::cout << "type error: operation on pointers not allowed\n"; + m_errors.push_back("operation on pointers not allowed"); + //std::cout << "type error: operation on pointers not allowed\n"; m_region -= bin.dst.v; m_offset -= bin.dst.v; return; From ca74001b966d900eaf9d86fa5d7bf2df5545346a Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Thu, 10 Aug 2023 12:07:16 -0400 Subject: [PATCH 118/373] Better handling of stack in Offset domain, fixing some error handling Support for widths in the stack, similar to Region domain; Changed the stack to a map instead of an unordered_map, and support for forgetting overlapping cells; Multiple fixes in transformers, related to access of Offset domain -- the execution was going into Offset domain even when packet pointers were not involved, hence added checks Signed-off-by: Ameer Hamza --- src/crab/offset_domain.cpp | 109 +++++++++++++++++++++++-------------- src/crab/offset_domain.hpp | 16 ++++-- src/crab/type_domain.cpp | 30 ++++++---- src/crab/type_domain.hpp | 3 +- 4 files changed, 100 insertions(+), 58 deletions(-) diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index 34a21aa95..2b97e3358 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -143,6 +143,27 @@ std::optional registers_state_t::find(register_t key) const { return find(reg); } +std::vector stack_state_t::find_overlapping_cells(uint64_t start, int width) const { + std::vector overlapping_cells; + auto it = m_slot_dists.begin(); + while (it != m_slot_dists.end() && it->first < start) { + it++; + } + if (it != m_slot_dists.begin()) { + it--; + auto key = it->first; + auto width_key = it->second.second; + if (key < start && key+width_key > start) overlapping_cells.push_back(key); + } + + for (; it != m_slot_dists.end(); it++) { + auto key = it->first; + if (key >= start && key < start+width) overlapping_cells.push_back(key); + if (key >= start+width) break; + } + return overlapping_cells; +} + void registers_state_t::set_to_top() { m_cur_def = live_registers_t{nullptr}; m_is_bottom = false; @@ -246,23 +267,29 @@ stack_state_t stack_state_t::top() { return stack_state_t(false); } -std::optional stack_state_t::find(int key) const { +std::optional stack_state_t::find(uint64_t key) const { auto it = m_slot_dists.find(key); if (it == m_slot_dists.end()) return {}; return it->second; } -void stack_state_t::store(int key, dist_t d) { - m_slot_dists[key] = d; +void stack_state_t::store(uint64_t key, dist_t d, int width) { + m_slot_dists[key] = std::make_pair(d, width); } -void stack_state_t::operator-=(int to_erase) { +void stack_state_t::operator-=(uint64_t to_erase) { if (is_bottom()) { return; } m_slot_dists.erase(to_erase); } +void stack_state_t::operator-=(const std::vector& keys) { + for (auto &key : keys) { + *this -= key; + } +} + stack_state_t stack_state_t::operator|(const stack_state_t& other) const { if (is_bottom() || other.is_top()) { return other; @@ -270,10 +297,20 @@ stack_state_t stack_state_t::operator|(const stack_state_t& other) const { return *this; } stack_slot_dists_t out_stack_dists; + // We do not join dist cells because different dist values different types of offsets for (auto const&kv: m_slot_dists) { - auto it = other.m_slot_dists.find(kv.first); - if (it != m_slot_dists.end() && kv.second == it->second) - out_stack_dists.insert(kv); + auto maybe_dist_cells = other.find(kv.first); + if (maybe_dist_cells) { + auto dist_cells1 = kv.second; + auto dist_cells2 = *maybe_dist_cells; + auto dist1 = dist_cells1.first; + auto dist2 = dist_cells2.first; + int width1 = dist_cells1.second; + int width2 = dist_cells2.second; + if (dist1 == dist2 && width1 == width2) { + out_stack_dists.insert(kv); + } + } } return stack_state_t(std::move(out_stack_dists), false); } @@ -530,7 +567,7 @@ void offset_domain_t::operator()(const Assume &b, location_t loc, int print) { auto right_reg = std::get(cond.right).v; auto dist_left = m_reg_state.find(cond.left.v); auto dist_right = m_reg_state.find(right_reg); - if (!dist_left && !dist_right) { + if (!dist_left || !dist_right) { // this should not happen, comparison between a packet pointer and either // other region's pointers or numbers; possibly raise type error m_errors.push_back("one of the pointers being compared isn't packet pointer"); @@ -849,40 +886,32 @@ void offset_domain_t::operator()(const basic_block_t& bb, int print) { // nothing to do here } -void offset_domain_t::do_mem_store(const Mem& b, std::optional& basereg_type) { - std::optional reg_type = {}; - if (!std::holds_alternative(b.value)) { - // remove the offset from the stack or nothing - return; +void offset_domain_t::do_mem_store(const Mem& b, std::optional maybe_targetreg_type, std::optional& maybe_basereg_type) { + bool target_is_reg = std::holds_alternative(b.value); + if (target_is_reg) { } - Reg target_reg = std::get(b.value); int offset = b.access.offset; - if (is_stack_pointer(basereg_type)) { - auto basereg_with_off = std::get(*basereg_type); - auto basereg_off = basereg_with_off.get_offset().to_interval(); - auto basereg_off_singleton = basereg_off.singleton(); - if (!basereg_off_singleton) { - m_errors.push_back("basereg offset is not a singleton"); - //std::cout << "type_error: basereg offset is not a singleton\n"; - return; - } - auto store_at = (int)(*basereg_off_singleton + offset); - //if (is_packet_pointer(targetreg_type)) { - auto it = m_reg_state.find(target_reg.v); - if (!it) { - m_errors.push_back("register is a packet_pointer and no offset info found"); - //std::cout << "type_error: register is a packet_pointer and no offset info found\n"; - m_stack_state -= store_at; + int width = b.access.width; + + if (is_stack_pointer(maybe_basereg_type)) { + auto basereg_with_off = std::get(*maybe_basereg_type); + auto basereg_off_singleton = basereg_with_off.get_offset().to_interval().singleton(); + if (!basereg_off_singleton) return; + auto store_at = (uint64_t)(*basereg_off_singleton + offset); + auto overlapping_cells = m_stack_state.find_overlapping_cells(store_at, width); + m_stack_state -= overlapping_cells; + + if (!is_packet_pointer(maybe_targetreg_type)) return; + auto target_reg = std::get(b.value); + auto offset_info = m_reg_state.find(target_reg.v); + if (!offset_info) { + m_errors.push_back("register is a packet_pointer and no offset info found"); + //std::cout << "type_error: register is a packet_pointer and no offset info found\n"; return; - } - m_stack_state.store(store_at, it.value()); - //} - //else { - //m_stack_state -= store_at; - //} + } + m_stack_state.store(store_at, *offset_info, width); } - else {} // in the rest cases, we do not store } void offset_domain_t::do_load(const Mem& b, const Reg& target_reg, @@ -932,9 +961,9 @@ void offset_domain_t::do_load(const Mem& b, const Reg& target_reg, m_reg_state -= target_reg.v; return; } - dist_t d = it.value(); + dist_t d = it->first; auto reg = reg_with_loc_t(target_reg.v, loc); - m_reg_state.insert(target_reg.v, reg, dist_t(d)); + m_reg_state.insert(target_reg.v, reg, d); } else { // shared m_reg_state -= target_reg.v; @@ -953,7 +982,7 @@ std::optional offset_domain_t::find_in_ctx(int key) const { return m_ctx_dists->find(key); } -std::optional offset_domain_t::find_in_stack(int key) const { +std::optional offset_domain_t::find_in_stack(int key) const { return m_stack_state.find(key); } diff --git a/src/crab/offset_domain.hpp b/src/crab/offset_domain.hpp index 902fc48f2..22267368d 100644 --- a/src/crab/offset_domain.hpp +++ b/src/crab/offset_domain.hpp @@ -131,17 +131,20 @@ class registers_state_t { void print_all_register_types() const; }; +using dist_cells_t = std::pair; +using stack_slot_dists_t = std::map; // represents `sp[n] = dist;`, where n \belongs [0,511], e.g., `sp[508] = begin+16` + class stack_state_t { - using stack_slot_dists_t = std::unordered_map; // represents `sp[n] = dist;`, where n \belongs [0,511], e.g., `sp[508] = begin+16` stack_slot_dists_t m_slot_dists; bool m_is_bottom = false; public: stack_state_t(bool is_bottom = false) : m_is_bottom(is_bottom) {} - std::optional find(int) const; - void store(int, dist_t); - void operator-=(int); + std::optional find(uint64_t) const; + void store(uint64_t, dist_t, int); + void operator-=(uint64_t); + void operator-=(const std::vector&); void set_to_top(); void set_to_bottom(); bool is_bottom() const; @@ -150,6 +153,7 @@ class stack_state_t { stack_state_t operator|(const stack_state_t&) const; explicit stack_state_t(stack_slot_dists_t&& stack_dists, bool is_bottom = false) : m_slot_dists(std::move(stack_dists)), m_is_bottom(is_bottom) {} + std::vector find_overlapping_cells(uint64_t, int) const; }; class extra_constraints_t { @@ -271,7 +275,7 @@ class offset_domain_t final { void set_require_check(check_require_func_t f) {} void do_load(const Mem&, const Reg&, std::optional, location_t loc); - void do_mem_store(const Mem&, std::optional&); + void do_mem_store(const Mem&, std::optional, std::optional&); interval_t do_bin(const Bin&, const std::optional&, const std::optional&, std::optional&, @@ -283,7 +287,7 @@ class offset_domain_t final { std::optional, std::optional); std::optional find_in_ctx(int) const; - std::optional find_in_stack(int) const; + std::optional find_in_stack(int) const; std::optional find_offset_at_loc(const reg_with_loc_t) const; std::optional find_offset_info(register_t reg) const; void update_offset_info(const dist_t&&, const interval_t&&, diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index af6c1b8ed..1de48d0a2 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -164,6 +164,9 @@ void type_domain_t::operator()(const Assume& s, location_t loc, int print) { const auto& right_reg = std::get(right); const auto& maybe_right_type = m_region.find_ptr_or_mapfd_type(right_reg.v); if (maybe_left_type && maybe_right_type) { + if (is_packet_ptr(maybe_left_type) && is_packet_ptr(maybe_right_type)) { + m_offset(s, loc, print); + } // both pointers } else if (!maybe_left_type && !maybe_right_type) { @@ -176,7 +179,6 @@ void type_domain_t::operator()(const Assume& s, location_t loc, int print) { m_region.set_registers_to_top(); } } - m_offset(s, loc, print); } void type_domain_t::operator()(const ValidDivisor& s, location_t loc, int print) { @@ -184,10 +186,14 @@ void type_domain_t::operator()(const ValidDivisor& s, location_t loc, int print) } void type_domain_t::operator()(const ValidAccess& s, location_t loc, int print) { - m_region(s, loc); auto reg_type = m_region.find_ptr_or_mapfd_type(s.reg.v); - std::optional width_interval = {}; - m_offset.check_valid_access(s, reg_type, std::nullopt, width_interval); + if (reg_type) { + m_region(s, loc); + std::optional width_interval = {}; + if (is_packet_ptr(reg_type)) { + m_offset.check_valid_access(s, reg_type, std::nullopt, width_interval); + } + } } void type_domain_t::operator()(const TypeConstraint& s, location_t loc, int print) { @@ -344,10 +350,10 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg, bool unknown_pt m_offset.do_load(b, target_reg, basereg_opt, loc); } -void type_domain_t::do_mem_store(const Mem& b, std::optional& basereg_opt, - location_t loc, int print) { +void type_domain_t::do_mem_store(const Mem& b, std::optional target_opt, + std::optional& basereg_opt, location_t loc, int print) { m_region.do_mem_store(b, loc); - m_offset.do_mem_store(b, basereg_opt); + m_offset.do_mem_store(b, target_opt, basereg_opt); } void type_domain_t::operator()(const Mem& b, location_t loc, int print) { @@ -359,12 +365,14 @@ void type_domain_t::operator()(const Mem& b, location_t loc, int print) { m_errors.push_back( std::string("load/store using an unknown pointer, or number - r") + s); } - if (!unknown_ptr && !b.is_load) { - do_mem_store(b, ptr_or_mapfd_opt, loc, print); - } - else if (std::holds_alternative(b.value)) { + if (std::holds_alternative(b.value)) { auto targetreg = std::get(b.value); + auto targetreg_type = m_region.find_ptr_or_mapfd_type(targetreg.v); if (b.is_load) do_load(b, targetreg, unknown_ptr, ptr_or_mapfd_opt, loc, print); + else if (!unknown_ptr) do_mem_store(b, targetreg_type, ptr_or_mapfd_opt, loc, print); + } + else if (!unknown_ptr && !b.is_load) { + do_mem_store(b, std::nullopt, ptr_or_mapfd_opt, loc, print); } } diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 89901e70b..c7b944804 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -90,7 +90,8 @@ class type_domain_t final { void do_load(const Mem&, const Reg&, bool, std::optional, location_t, int print = 0); - void do_mem_store(const Mem&, std::optional&, location_t, int print = 0); + void do_mem_store(const Mem&, std::optional, std::optional&, + location_t, int print = 0); void report_type_error(std::string, location_t); void print_registers() const; void adjust_bb_for_types(location_t); From ba13e3ac6968e4e3a0ea14e0add81546a9415a8b Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Thu, 10 Aug 2023 16:31:07 -0400 Subject: [PATCH 119/373] Adding Offset information into printing, and removing some redundant code Signed-off-by: Ameer Hamza --- src/crab/offset_domain.cpp | 57 ++++++++++---------------------------- src/crab/type_domain.cpp | 36 ++++++++++++++++-------- src/crab/type_domain.hpp | 1 + src/crab/type_ostream.cpp | 26 +++++++++-------- src/crab/type_ostream.hpp | 11 ++++---- 5 files changed, 60 insertions(+), 71 deletions(-) diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index 2b97e3358..a5cf1ebca 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -236,14 +236,6 @@ void registers_state_t::adjust_bb_for_registers(location_t loc) { } } -void registers_state_t::print_all_register_types() const { - std::cout << "\toffset types: {\n"; - for (auto const& kv : *m_offset_env) { - std::cout << "\t\t" << kv.first << " : " << kv.second << "\n"; - } - std::cout << "\t}\n"; -} - void stack_state_t::set_to_top() { m_slot_dists.clear(); m_is_bottom = false; @@ -592,30 +584,6 @@ void offset_domain_t::operator()(const Assume &b, location_t loc, int print) { else {} //we do not need to deal with other cases } -bool is_packet_pointer(std::optional& type) { - if (!type) { // not a pointer - return false; - } - auto ptr_or_mapfd_type = type.value(); - if (std::holds_alternative(ptr_or_mapfd_type) - && std::get(ptr_or_mapfd_type).get_region() == crab::region_t::T_PACKET) { - return true; - } - return false; -} - -bool is_stack_pointer(std::optional& type) { - if (!type) { // not a pointer - return false; - } - auto ptr_or_mapfd_type = type.value(); - if (std::holds_alternative(ptr_or_mapfd_type) - && std::get(ptr_or_mapfd_type).get_region() == crab::region_t::T_STACK) { - return true; - } - return false; -} - void offset_domain_t::update_offset_info(const dist_t&& dist, const interval_t&& change, const reg_with_loc_t& reg_with_loc, uint8_t reg, Bin::Op op) { auto offset = dist.m_dist; @@ -636,6 +604,7 @@ interval_t offset_domain_t::do_bin(const Bin &bin, const std::optional& dst_interval_opt, std::optional& src_ptr_or_mapfd_opt, std::optional& dst_ptr_or_mapfd_opt, location_t loc) { + using Op = Bin::Op; // if both src and dst are numbers, nothing to do in offset domain // if we are doing a move, where src is a number and dst is not set, nothing to do @@ -643,7 +612,7 @@ interval_t offset_domain_t::do_bin(const Bin &bin, || (src_interval_opt && !dst_ptr_or_mapfd_opt && bin.op == Op::MOV)) return interval_t::bottom(); // offset domain only handles packet pointers - if (!is_packet_pointer(src_ptr_or_mapfd_opt) && !is_packet_pointer(dst_ptr_or_mapfd_opt)) + if (!is_packet_ptr(src_ptr_or_mapfd_opt) && !is_packet_ptr(dst_ptr_or_mapfd_opt)) return interval_t::bottom(); interval_t src_interval = interval_t::bottom(), dst_interval = interval_t::bottom(); @@ -658,7 +627,7 @@ interval_t offset_domain_t::do_bin(const Bin &bin, { // ra = rb; case Op::MOV: { - if (!is_packet_pointer(src_ptr_or_mapfd_opt)) { + if (!is_packet_ptr(src_ptr_or_mapfd_opt)) { m_reg_state -= bin.dst.v; return interval_t::bottom(); } @@ -675,12 +644,12 @@ interval_t offset_domain_t::do_bin(const Bin &bin, case Op::ADD: { dist_t dist_to_update; interval_t interval_to_add = interval_t::bottom(); - if (is_packet_pointer(dst_ptr_or_mapfd_opt) - && is_packet_pointer(src_ptr_or_mapfd_opt)) { + if (is_packet_ptr(dst_ptr_or_mapfd_opt) + && is_packet_ptr(src_ptr_or_mapfd_opt)) { m_reg_state -= bin.dst.v; return interval_t::bottom(); } - else if (is_packet_pointer(dst_ptr_or_mapfd_opt) && src_interval_opt) { + else if (is_packet_ptr(dst_ptr_or_mapfd_opt) && src_interval_opt) { auto dst_offset_opt = m_reg_state.find(bin.dst.v); if (!dst_offset_opt) { m_errors.push_back("dst is a packet_pointer and no offset info found"); @@ -710,12 +679,12 @@ interval_t offset_domain_t::do_bin(const Bin &bin, case Op::SUB: { dist_t dist_to_update; interval_t interval_to_sub = interval_t::bottom(); - if (is_packet_pointer(dst_ptr_or_mapfd_opt) - && is_packet_pointer(src_ptr_or_mapfd_opt)) { + if (is_packet_ptr(dst_ptr_or_mapfd_opt) + && is_packet_ptr(src_ptr_or_mapfd_opt)) { m_reg_state -= bin.dst.v; return interval_t::top(); } - else if (is_packet_pointer(dst_ptr_or_mapfd_opt) && src_interval_opt) { + else if (is_packet_ptr(dst_ptr_or_mapfd_opt) && src_interval_opt) { auto dst_offset_opt = m_reg_state.find(bin.dst.v); if (!dst_offset_opt) { m_errors.push_back("dst is a packet_pointer and no offset info found"); @@ -894,7 +863,7 @@ void offset_domain_t::do_mem_store(const Mem& b, std::optional m int offset = b.access.offset; int width = b.access.width; - if (is_stack_pointer(maybe_basereg_type)) { + if (is_stack_ptr(maybe_basereg_type)) { auto basereg_with_off = std::get(*maybe_basereg_type); auto basereg_off_singleton = basereg_with_off.get_offset().to_interval().singleton(); if (!basereg_off_singleton) return; @@ -902,7 +871,7 @@ void offset_domain_t::do_mem_store(const Mem& b, std::optional m auto overlapping_cells = m_stack_state.find_overlapping_cells(store_at, width); m_stack_state -= overlapping_cells; - if (!is_packet_pointer(maybe_targetreg_type)) return; + if (!is_packet_ptr(maybe_targetreg_type)) return; auto target_reg = std::get(b.value); auto offset_info = m_reg_state.find(target_reg.v); if (!offset_info) { @@ -974,6 +943,10 @@ void offset_domain_t::do_load(const Mem& b, const Reg& target_reg, } } +void offset_domain_t::operator()(const Mem& b, location_t loc, int print) { + // nothing to do here +} + std::optional offset_domain_t::find_offset_at_loc(const reg_with_loc_t reg) const { return m_reg_state.find(reg); } diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 1de48d0a2..5ae9c018c 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -376,18 +376,15 @@ void type_domain_t::operator()(const Mem& b, location_t loc, int print) { } } -// the method does not work well as it requires info about the label of basic block we are in -// this info is not available when we are only printing any state -// but it is available when we are processing a basic block for all its instructions:w -// void type_domain_t::print_registers() const { std::cout << " register types: {\n"; for (size_t i = 0; i < NUM_REGISTERS; i++) { register_t reg = (register_t)i; auto maybe_ptr_or_mapfd_type = m_region.find_ptr_or_mapfd_type(reg); + auto maybe_offset_info = m_offset.find_offset_info(reg); if (maybe_ptr_or_mapfd_type) { std::cout << " "; - print_register(std::cout, Reg{(uint8_t)reg}, maybe_ptr_or_mapfd_type); + print_register(std::cout, Reg{(uint8_t)reg}, maybe_ptr_or_mapfd_type, maybe_offset_info); std::cout << "\n"; } } @@ -398,10 +395,11 @@ void type_domain_t::print_ctx() const { std::vector ctx_keys = m_region.get_ctx_keys(); std::cout << " ctx: {\n"; for (auto const& k : ctx_keys) { - auto ptr = m_region.find_in_ctx(k); - if (ptr) { + auto maybe_ptr = m_region.find_in_ctx(k); + auto maybe_dist = m_offset.find_in_ctx(k); + if (maybe_ptr) { std::cout << " " << k << ": "; - print_ptr_type(std::cout, ptr_or_mapfd_t{ptr.value()}); + print_ptr_type(std::cout, *maybe_ptr, maybe_dist); std::cout << ",\n"; } } @@ -413,18 +411,25 @@ void type_domain_t::print_stack() const { std::cout << " stack: {\n"; for (auto const& k : stack_keys_region) { auto maybe_ptr_or_mapfd_cells = m_region.find_in_stack(k); + auto maybe_dist_cells = m_offset.find_in_stack(k); if (maybe_ptr_or_mapfd_cells) { - auto ptr_or_mapfd_cells = maybe_ptr_or_mapfd_cells.value(); + auto ptr_or_mapfd_cells = *maybe_ptr_or_mapfd_cells; int width = ptr_or_mapfd_cells.second; auto ptr_or_mapfd = ptr_or_mapfd_cells.first; + std::optional maybe_dist = maybe_dist_cells ? + std::optional{maybe_dist_cells->first} : std::nullopt; std::cout << " [" << k << "-" << k+width-1 << "] : "; - print_ptr_or_mapfd_type(std::cout, ptr_or_mapfd); + print_ptr_or_mapfd_type(std::cout, ptr_or_mapfd, maybe_dist); std::cout << ",\n"; } } std::cout << " }\n"; } +// the method does not work well as it requires info about the label of basic block we are in +// this info is not available when we are only printing any state +// but it is available when we are processing a basic block for all its instructions:w +// void type_domain_t::adjust_bb_for_types(location_t loc) { m_region.adjust_bb_for_types(loc); m_offset.adjust_bb_for_types(loc); @@ -457,6 +462,11 @@ type_domain_t::find_ptr_or_mapfd_at_loc(const crab::reg_with_loc_t& loc) const { return m_region.find_ptr_or_mapfd_at_loc(loc); } +std::optional +type_domain_t::find_offset_at_loc(const crab::reg_with_loc_t& loc) const { + return m_offset.find_offset_at_loc(loc); +} + std::ostream& operator<<(std::ostream& o, const type_domain_t& typ) { typ.write(o); return o; @@ -493,7 +503,8 @@ void print_annotated(std::ostream& o, const crab::type_domain_t& typ, auto b = std::get(statement); auto reg_with_loc = crab::reg_with_loc_t(b.dst.v, loc); auto region = typ.find_ptr_or_mapfd_at_loc(reg_with_loc); - print_annotated(o, b, region); + auto offset = typ.find_offset_at_loc(reg_with_loc); + print_annotated(o, b, region, offset); } else if (std::holds_alternative(statement)) { auto u = std::get(statement); @@ -501,7 +512,8 @@ void print_annotated(std::ostream& o, const crab::type_domain_t& typ, auto target_reg = std::get(u.value); auto target_reg_loc = crab::reg_with_loc_t(target_reg.v, loc); auto region = typ.find_ptr_or_mapfd_at_loc(target_reg_loc); - print_annotated(o, u, region); + auto offset = typ.find_offset_at_loc(target_reg_loc); + print_annotated(o, u, region, offset); } else o << " " << u << "\n"; } diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index c7b944804..42efb4493 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -85,6 +85,7 @@ class type_domain_t final { void print_ctx() const; void print_stack() const; std::optional find_ptr_or_mapfd_at_loc(const crab::reg_with_loc_t&) const; + std::optional find_offset_at_loc(const crab::reg_with_loc_t&) const; private: diff --git a/src/crab/type_ostream.cpp b/src/crab/type_ostream.cpp index 041b4ca93..20e21adea 100644 --- a/src/crab/type_ostream.cpp +++ b/src/crab/type_ostream.cpp @@ -3,28 +3,28 @@ #include "crab/type_ostream.hpp" -void print_ptr_type(std::ostream& o, const crab::ptr_or_mapfd_t& ptr) { +void print_ptr_type(std::ostream& o, const crab::ptr_or_mapfd_t& ptr, std::optional d) { if (std::holds_alternative(ptr)) { o << std::get(ptr); } else if (std::holds_alternative(ptr)) { - o << std::get(ptr); + o << std::get(ptr) << "<" << (d ? *d : crab::dist_t{}) << ">"; } } -void print_ptr_or_mapfd_type(std::ostream& o, const crab::ptr_or_mapfd_t& ptr_or_mapfd) { +void print_ptr_or_mapfd_type(std::ostream& o, const crab::ptr_or_mapfd_t& ptr_or_mapfd, std::optional d) { if (std::holds_alternative(ptr_or_mapfd)) { o << std::get(ptr_or_mapfd); } else { - print_ptr_type(o, ptr_or_mapfd); + print_ptr_type(o, ptr_or_mapfd, d); } } -void print_register(std::ostream& o, const Reg& r, std::optional& p) { +void print_register(std::ostream& o, const Reg& r, std::optional& p, std::optional d) { o << r << " : "; if (p) { - print_ptr_or_mapfd_type(o, p.value()); + print_ptr_or_mapfd_type(o, *p, d); } } @@ -32,25 +32,27 @@ inline std::string size_(int w) { return std::string("u") + std::to_string(w * 8 void print_annotated(std::ostream& o, const Call& call, std::optional& p) { o << " "; - print_register(o, Reg{(uint8_t)R0_RETURN_VALUE}, p); + print_register(o, Reg{(uint8_t)R0_RETURN_VALUE}, p, std::nullopt); o << " = " << call.name << ":" << call.func << "(...)\n"; } -void print_annotated(std::ostream& o, const Bin& b, std::optional& p) { +void print_annotated(std::ostream& o, const Bin& b, std::optional& p, + std::optional& d) { o << " "; - print_register(o, b.dst, p); + print_register(o, b.dst, p, d); o << " " << b.op << "= " << b.v << "\n"; } void print_annotated(std::ostream& o, const LoadMapFd& u, std::optional& p) { o << " "; - print_register(o, u.dst, p); + print_register(o, u.dst, p, std::nullopt); o << " = map_fd " << u.mapfd << "\n"; } -void print_annotated(std::ostream& o, const Mem& b, std::optional& p) { +void print_annotated(std::ostream& o, const Mem& b, std::optional& p, + std::optional& d) { o << " "; - print_register(o, std::get(b.value), p); + print_register(o, std::get(b.value), p, d); o << " = "; std::string sign = b.access.offset < 0 ? " - " : " + "; int offset = std::abs(b.access.offset); diff --git a/src/crab/type_ostream.hpp b/src/crab/type_ostream.hpp index 11e7e2303..b1427e504 100644 --- a/src/crab/type_ostream.hpp +++ b/src/crab/type_ostream.hpp @@ -7,11 +7,12 @@ #include "asm_syntax.hpp" #include "asm_ostream.hpp" #include "crab/common.hpp" +#include "crab/offset_domain.hpp" -void print_ptr_or_mapfd_type(std::ostream&, const crab::ptr_or_mapfd_t&); -void print_ptr_type(std::ostream&, const crab::ptr_or_mapfd_t& ptr); -void print_register(std::ostream& o, const Reg& r, std::optional& p); +void print_ptr_or_mapfd_type(std::ostream&, const crab::ptr_or_mapfd_t&, std::optional); +void print_ptr_type(std::ostream&, const crab::ptr_or_mapfd_t& ptr, std::optional); +void print_register(std::ostream& o, const Reg& r, std::optional& p, std::optional); void print_annotated(std::ostream& o, const Call& call, std::optional& p); -void print_annotated(std::ostream& o, const Bin& b, std::optional& p); +void print_annotated(std::ostream& o, const Bin& b, std::optional& p, std::optional&); void print_annotated(std::ostream& o, const LoadMapFd& u, std::optional& p); -void print_annotated(std::ostream& o, const Mem& b, std::optional& p); +void print_annotated(std::ostream& o, const Mem& b, std::optional& p, std::optional&); From 2e39d00c6b3fe1bdea92cbb168262b1ce3a9b0bb Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 15 Aug 2023 18:59:57 -0400 Subject: [PATCH 120/373] Multiple fixes and refactoring Adding extra checks as interval domain has not been implemented yet; Fixing shared region checks in Load operation; Fixing error reporting, as it was previously wrongly copied to multiple basic blocks; Multiple refactorings; Signed-off-by: Ameer Hamza --- src/crab/offset_domain.cpp | 84 +++++++++++++++++--------------------- src/crab/offset_domain.hpp | 1 + src/crab/type_domain.cpp | 10 ++--- src/crab/type_ostream.cpp | 2 +- src/crab/type_ostream.hpp | 2 +- 5 files changed, 45 insertions(+), 54 deletions(-) diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index a5cf1ebca..bbf335f59 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -660,7 +660,8 @@ interval_t offset_domain_t::do_bin(const Bin &bin, dist_to_update = std::move(dst_offset_opt.value()); interval_to_add = std::move(src_interval_opt.value()); } - else { + // Condition might not be necessary once interval domain is added + else if (is_packet_ptr(src_ptr_or_mapfd_opt) && dst_interval_opt) { auto src_offset_opt = m_reg_state.find(src.v); if (!src_offset_opt) { m_errors.push_back("src is a packet_pointer and no offset info found"); @@ -671,6 +672,11 @@ interval_t offset_domain_t::do_bin(const Bin &bin, dist_to_update = std::move(src_offset_opt.value()); interval_to_add = std::move(dst_interval_opt.value()); } + else if (is_packet_ptr(dst_ptr_or_mapfd_opt)) { + // this case is only needed till interval domain is added + m_reg_state.insert(bin.dst.v, reg_with_loc, dist_t()); + break; + } update_offset_info(std::move(dist_to_update), std::move(interval_to_add), reg_with_loc, bin.dst.v, bin.op); break; @@ -885,61 +891,45 @@ void offset_domain_t::do_mem_store(const Mem& b, std::optional m void offset_domain_t::do_load(const Mem& b, const Reg& target_reg, std::optional basereg_type, location_t loc) { - if (!basereg_type) { + + bool is_stack_p = is_stack_ptr(basereg_type); + bool is_ctx_p = is_ctx_ptr(basereg_type); + + if (!is_stack_p && !is_ctx_p) { m_reg_state -= target_reg.v; return; } - auto basereg_ptr_type = basereg_type.value(); + int offset = b.access.offset; - - if (std::holds_alternative(basereg_ptr_type)) { - auto p_with_off = std::get(basereg_ptr_type); - auto offset_singleton = p_with_off.get_offset().to_interval().singleton(); - if (!offset_singleton) { - m_errors.push_back("basereg offset is not a singleton"); - //std::cout << "type_error: basereg offset is not a singleton\n"; + auto type_with_off = std::get(*basereg_type); + auto p_offset = type_with_off.get_offset(); + auto offset_singleton = p_offset.to_interval().singleton(); + if (!offset_singleton) { + m_reg_state -= target_reg.v; + return; + } + auto ptr_offset = *offset_singleton; + auto load_at = (uint64_t)(ptr_offset + offset); + + if (is_stack_p) { + auto it = m_stack_state.find(load_at); + if (!it) { m_reg_state -= target_reg.v; return; } - - if (p_with_off.get_region() == crab::region_t::T_CTX) { - if (!offset_singleton) { - m_reg_state -= target_reg.v; - return; - } - auto load_at = (uint64_t)offset_singleton.value() + (uint64_t)offset; - auto it = m_ctx_dists->find(load_at); - if (!it) { - m_reg_state -= target_reg.v; - return; - } - dist_t d = it.value(); - auto reg = reg_with_loc_t(target_reg.v, loc); - m_reg_state.insert(target_reg.v, reg, dist_t(d)); - } - else if (p_with_off.get_region() == crab::region_t::T_STACK) { - if (!offset_singleton) { - m_reg_state -= target_reg.v; - return; - } - auto ptr_offset = offset_singleton.value(); - auto load_at = (uint64_t)(ptr_offset + offset); - auto it = m_stack_state.find(load_at); - - if (!it) { - m_reg_state -= target_reg.v; - return; - } - dist_t d = it->first; - auto reg = reg_with_loc_t(target_reg.v, loc); - m_reg_state.insert(target_reg.v, reg, d); - } - else { // shared + dist_t d = it->first; + auto reg = reg_with_loc_t(target_reg.v, loc); + m_reg_state.insert(target_reg.v, reg, d); + } + else if (is_ctx_p) { + auto it = m_ctx_dists->find(load_at); + if (!it) { m_reg_state -= target_reg.v; + return; } - } - else { // we are loading from packet, or we have mapfd - m_reg_state -= target_reg.v; + dist_t d = it.value(); + auto reg = reg_with_loc_t(target_reg.v, loc); + m_reg_state.insert(target_reg.v, reg, dist_t(d)); } } diff --git a/src/crab/offset_domain.hpp b/src/crab/offset_domain.hpp index 22267368d..ada6aa1a8 100644 --- a/src/crab/offset_domain.hpp +++ b/src/crab/offset_domain.hpp @@ -295,6 +295,7 @@ class offset_domain_t final { dist_t update_offset(const dist_t&, const weight_t&, const interval_t&, Bin::Op); void adjust_bb_for_types(location_t); [[nodiscard]] std::vector& get_errors() { return m_errors; } + void reset_errors() { m_errors.clear(); } }; // end offset_domain_t } // end namespace crab diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 5ae9c018c..caa623f33 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -340,7 +340,7 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { interval_t subtracted_reg = m_region.do_bin(bin, src_interval, src_ptr_or_mapfd, dst_ptr_or_mapfd, loc); interval_t subtracted_off = - m_offset.do_bin(bin, src_interval, interval_t::top(), src_ptr_or_mapfd, dst_ptr_or_mapfd, loc); + m_offset.do_bin(bin, src_interval, std::nullopt, src_ptr_or_mapfd, dst_ptr_or_mapfd, loc); auto subtracted = subtracted_reg.is_bottom() ? subtracted_off : subtracted_reg; } @@ -426,10 +426,6 @@ void type_domain_t::print_stack() const { std::cout << " }\n"; } -// the method does not work well as it requires info about the label of basic block we are in -// this info is not available when we are only printing any state -// but it is available when we are processing a basic block for all its instructions:w -// void type_domain_t::adjust_bb_for_types(location_t loc) { m_region.adjust_bb_for_types(loc); m_offset.adjust_bb_for_types(loc); @@ -441,6 +437,10 @@ void type_domain_t::operator()(const basic_block_t& bb, int print) { print_annotated(std::cout, *this, bb, print); return; } + // A temporary fix to avoid printing errors for multiple basic blocks + m_errors.clear(); + m_region.reset_errors(); + m_offset.reset_errors(); auto label = bb.label(); uint32_t curr_pos = 0; diff --git a/src/crab/type_ostream.cpp b/src/crab/type_ostream.cpp index 20e21adea..28eb6e139 100644 --- a/src/crab/type_ostream.cpp +++ b/src/crab/type_ostream.cpp @@ -21,7 +21,7 @@ void print_ptr_or_mapfd_type(std::ostream& o, const crab::ptr_or_mapfd_t& ptr_or } } -void print_register(std::ostream& o, const Reg& r, std::optional& p, std::optional d) { +void print_register(std::ostream& o, const Reg& r, const std::optional& p, std::optional d) { o << r << " : "; if (p) { print_ptr_or_mapfd_type(o, *p, d); diff --git a/src/crab/type_ostream.hpp b/src/crab/type_ostream.hpp index b1427e504..5e8e10251 100644 --- a/src/crab/type_ostream.hpp +++ b/src/crab/type_ostream.hpp @@ -11,7 +11,7 @@ void print_ptr_or_mapfd_type(std::ostream&, const crab::ptr_or_mapfd_t&, std::optional); void print_ptr_type(std::ostream&, const crab::ptr_or_mapfd_t& ptr, std::optional); -void print_register(std::ostream& o, const Reg& r, std::optional& p, std::optional); +void print_register(std::ostream& o, const Reg& r, const std::optional& p, std::optional); void print_annotated(std::ostream& o, const Call& call, std::optional& p); void print_annotated(std::ostream& o, const Bin& b, std::optional& p, std::optional&); void print_annotated(std::ostream& o, const LoadMapFd& u, std::optional& p); From 2c6dc7c1dc4a262d390f550003a5a80c7c458d96 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 15 Aug 2023 23:47:20 -0400 Subject: [PATCH 121/373] Refactoring of redundant code Signed-off-by: Ameer Hamza --- src/crab/offset_domain.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index bbf335f59..ade57da3b 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -862,10 +862,6 @@ void offset_domain_t::operator()(const basic_block_t& bb, int print) { } void offset_domain_t::do_mem_store(const Mem& b, std::optional maybe_targetreg_type, std::optional& maybe_basereg_type) { - bool target_is_reg = std::holds_alternative(b.value); - if (target_is_reg) { - } - int offset = b.access.offset; int width = b.access.width; From 141049287a8059181cbc533286d94b5491cce47c Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Sat, 19 Aug 2023 03:52:37 -0400 Subject: [PATCH 122/373] Initial implementation for interval propagation domain Implemented transformers including Bin, Call, Mem, etc; Implemented Assert and Assume checks; Implemented correct representation of stack storing both intervals and their widths; Implemented join operations; Proper printing of intervals through the type domain; Added error reporting; Intervals are actually stored as mock_interval_t, instead of interval_t, since the default constructor of interval_t is, by default, private. Hence it creates issues when storing; --- src/crab/abstract_domain.cpp | 5 + src/crab/interval_prop_domain.cpp | 789 ++++++++++++++++++++++++++++++ src/crab/interval_prop_domain.hpp | 164 +++++++ src/crab/type_domain.cpp | 128 +++-- src/crab/type_domain.hpp | 12 +- src/crab/type_ostream.cpp | 10 + src/crab/type_ostream.hpp | 1 + src/crab_verifier.cpp | 1 + 8 files changed, 1063 insertions(+), 47 deletions(-) create mode 100644 src/crab/interval_prop_domain.cpp create mode 100644 src/crab/interval_prop_domain.hpp diff --git a/src/crab/abstract_domain.cpp b/src/crab/abstract_domain.cpp index 392a130f0..6ab87d730 100644 --- a/src/crab/abstract_domain.cpp +++ b/src/crab/abstract_domain.cpp @@ -1,7 +1,11 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT + #include "abstract_domain.hpp" #include "ebpf_domain.hpp" #include "type_domain.hpp" #include "region_domain.hpp" +#include "interval_prop_domain.hpp" #include "offset_domain.hpp" template @@ -296,3 +300,4 @@ template abstract_domain_t::abstract_domain_t(crab::ebpf_domain_t); template abstract_domain_t::abstract_domain_t(crab::type_domain_t); template abstract_domain_t::abstract_domain_t(crab::region_domain_t); template abstract_domain_t::abstract_domain_t(crab::offset_domain_t); +template abstract_domain_t::abstract_domain_t(crab::interval_prop_domain_t); diff --git a/src/crab/interval_prop_domain.cpp b/src/crab/interval_prop_domain.cpp new file mode 100644 index 000000000..60c482e98 --- /dev/null +++ b/src/crab/interval_prop_domain.cpp @@ -0,0 +1,789 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT + +#include "crab/interval_prop_domain.hpp" + +namespace crab { + +bool registers_cp_state_t::is_bottom() const { + return m_is_bottom; +} + +bool registers_cp_state_t::is_top() const { + if (m_is_bottom) return false; + if (m_interval_env == nullptr) return true; + for (auto it : m_cur_def) { + if (it != nullptr) return false; + } + return true; +} + +void registers_cp_state_t::set_to_top() { + m_cur_def = live_registers_t{nullptr}; + m_is_bottom = false; +} + +void registers_cp_state_t::set_to_bottom() { + m_is_bottom = true; +} + +void registers_cp_state_t::insert(register_t reg, const reg_with_loc_t& reg_with_loc, + interval_t interval) { + (*m_interval_env)[reg_with_loc] = mock_interval_t(interval); + m_cur_def[reg] = std::make_shared(reg_with_loc); +} + +std::optional registers_cp_state_t::find(reg_with_loc_t reg) const { + auto it = m_interval_env->find(reg); + if (it == m_interval_env->end()) return {}; + return it->second; +} + +std::optional registers_cp_state_t::find(register_t key) const { + if (m_cur_def[key] == nullptr) return {}; + const reg_with_loc_t& reg = *(m_cur_def[key]); + return find(reg); +} + +registers_cp_state_t registers_cp_state_t::operator|(const registers_cp_state_t& other) const { + if (is_bottom() || other.is_top()) { + return other; + } else if (other.is_bottom() || is_top()) { + return *this; + } + live_registers_t intervals_joined; + location_t loc = location_t(std::make_pair(label_t(-2, -2), 0)); + for (size_t i = 0; i < m_cur_def.size(); i++) { + if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; + auto it1 = find(*(m_cur_def[i])); + auto it2 = other.find(*(other.m_cur_def[i])); + if (it1 && it2) { + auto interval1 = it1->to_interval(), interval2 = it2->to_interval(); + auto reg = reg_with_loc_t((register_t)i, loc); + intervals_joined[i] = std::make_shared(reg); + (*m_interval_env)[reg] = mock_interval_t(interval1 | interval2); + } + } + return registers_cp_state_t(std::move(intervals_joined), m_interval_env); +} + +void registers_cp_state_t::adjust_bb_for_registers(location_t loc) { + location_t old_loc = location_t(std::make_pair(label_t(-2, -2), 0)); + for (size_t i = 0; i < m_cur_def.size(); i++) { + auto new_reg = reg_with_loc_t((register_t)i, loc); + auto old_reg = reg_with_loc_t((register_t)i, old_loc); + + auto it = find((register_t)i); + if (!it) continue; + + if (*m_cur_def[i] == old_reg) + m_interval_env->erase(old_reg); + + m_cur_def[i] = std::make_shared(new_reg); + (*m_interval_env)[new_reg] = mock_interval_t(it.value()); + + } +} + +void registers_cp_state_t::operator-=(register_t var) { + if (is_bottom()) { + return; + } + m_cur_def[var] = nullptr; +} + +void registers_cp_state_t::print_all_register_types() const { + std::cout << "\tinterval types: {\n"; + for (auto const& kv : *m_interval_env) { + std::cout << "\t\t" << kv.first << " : " << kv.second.to_interval() << "\n"; + } + std::cout << "\t}\n"; +} + +bool stack_cp_state_t::is_bottom() const { + return m_is_bottom; +} + +bool stack_cp_state_t::is_top() const { + if (m_is_bottom) return false; + return m_interval_values.empty(); +} + +void stack_cp_state_t::set_to_top() { + m_interval_values.clear(); + m_is_bottom = false; +} + +void stack_cp_state_t::set_to_bottom() { + m_is_bottom = true; +} + +stack_cp_state_t stack_cp_state_t::top() { + return stack_cp_state_t(false); +} + +std::optional stack_cp_state_t::find(uint64_t key) const { + auto it = m_interval_values.find(key); + if (it == m_interval_values.end()) return {}; + return it->second; +} + +void stack_cp_state_t::store(uint64_t key, mock_interval_t val, int width) { + m_interval_values[key] = std::make_pair(val, width); +} + +void stack_cp_state_t::operator-=(uint64_t key) { + auto it = find(key); + if (it) + m_interval_values.erase(key); +} + +bool stack_cp_state_t::all_numeric(uint64_t start_loc, int width) const { + auto overlapping_cells = find_overlapping_cells(start_loc, width); + if (overlapping_cells.empty()) return false; + for (std::size_t i = 0; i < overlapping_cells.size()-1; i++) { + int width_i = find(overlapping_cells[i]).value().second; + if (overlapping_cells[i]+width_i != overlapping_cells[i+1]) return false; + } + return true; +} + +void stack_cp_state_t::remove_overlap(const std::vector& keys, uint64_t start, int width) { + for (auto& key : keys) { + auto type = find(key); + auto width_key = type.value().second; + if (key < start) { + int new_width = start-key; + store(key, interval_t::top(), new_width); + } + if (key+width_key > start+width) { + int new_width = key+width_key-(start+width); + store(start+width, interval_t::top(), new_width); + } + if (key >= start) *this -= key; + } +} + +std::vector stack_cp_state_t::find_overlapping_cells(uint64_t start, int width) const { + std::vector overlapping_cells; + auto it = m_interval_values.begin(); + while (it != m_interval_values.end() && it->first < start) { + it++; + } + if (it != m_interval_values.begin()) { + it--; + auto key = it->first; + auto width_key = it->second.second; + if (key < start && key+width_key > start) overlapping_cells.push_back(key); + } + + for (; it != m_interval_values.end(); it++) { + auto key = it->first; + if (key >= start && key < start+width) overlapping_cells.push_back(key); + if (key >= start+width) break; + } + return overlapping_cells; +} + +void join_stack(const stack_cp_state_t& stack1, uint64_t key1, int& loc1, + const stack_cp_state_t& stack2, uint64_t key2, int& loc2, + interval_values_stack_t& interval_values_joined) { + auto type1 = stack1.find(key1); auto type2 = stack2.find(key2); + auto& cells1 = type1.value(); auto& cells2 = type2.value(); + int width1 = cells1.second; int width2 = cells2.second; + auto interval1 = cells1.first.to_interval(); + auto interval2 = cells2.first.to_interval(); + auto top_interval_mock = mock_interval_t(interval_t::top()); + if (key1 == key2) { + if (width1 == width2) { + interval_values_joined[key1] = std::make_pair( + mock_interval_t(interval1 | interval2), width1); + loc1++; loc2++; + } + else if (width1 < width2) { + interval_values_joined[key1] = std::make_pair(top_interval_mock, width1); + loc1++; + } + else { + interval_values_joined[key1] = std::make_pair(top_interval_mock, width2); + loc2++; + } + } + else if (key1 > key2) { + if (key2+width2 > key1+width1) { + interval_values_joined[key1] = std::make_pair(top_interval_mock, width1); + loc1++; + } + else if (key2+width2 > key1) { + interval_values_joined[key1] = std::make_pair(top_interval_mock, key2+width2-key1); + loc2++; + } + else loc2++; + } + else { + join_stack(stack2, key2, loc2, stack1, key1, loc1, interval_values_joined); + } +} + +stack_cp_state_t stack_cp_state_t::operator|(const stack_cp_state_t& other) const { + if (is_bottom() || other.is_top()) { + return other; + } else if (other.is_bottom() || is_top()) { + return *this; + } + interval_values_stack_t interval_values_joined; + auto stack1_keys = get_keys(); + auto stack2_keys = other.get_keys(); + int i = 0, j = 0; + while (i < static_cast(stack1_keys.size()) && j < static_cast(stack2_keys.size())) { + int key1 = stack1_keys[i], key2 = stack2_keys[j]; + join_stack(*this, key1, i, other, key2, j, interval_values_joined); + } + return stack_cp_state_t(std::move(interval_values_joined)); +} + +size_t stack_cp_state_t::size() const { + return m_interval_values.size(); +} + +std::vector stack_cp_state_t::get_keys() const { + std::vector keys; + keys.reserve(size()); + + for (auto const&kv : m_interval_values) { + keys.push_back(kv.first); + } + return keys; +} + +bool interval_prop_domain_t::is_bottom() const { + if (m_is_bottom) return true; + return (m_registers_interval_values.is_bottom() || m_stack_slots_interval_values.is_bottom()); +} + +bool interval_prop_domain_t::is_top() const { + if (m_is_bottom) return false; + return (m_registers_interval_values.is_top() && m_stack_slots_interval_values.is_top()); +} + +interval_prop_domain_t interval_prop_domain_t::bottom() { + interval_prop_domain_t cp; + cp.set_to_bottom(); + return cp; +} + +void interval_prop_domain_t::set_to_bottom() { + m_is_bottom = true; +} + +void interval_prop_domain_t::set_to_top() { + m_registers_interval_values.set_to_top(); + m_stack_slots_interval_values.set_to_top(); +} + +std::optional interval_prop_domain_t::find_interval_value(register_t reg) const { + return m_registers_interval_values.find(reg); +} + +std::optional interval_prop_domain_t::find_interval_at_loc( + const reg_with_loc_t reg) const { + return m_registers_interval_values.find(reg); +} + +bool interval_prop_domain_t::operator<=(const interval_prop_domain_t& abs) const { + /* WARNING: The operation is not implemented yet.*/ + return true; +} + +void interval_prop_domain_t::operator|=(const interval_prop_domain_t& abs) { + interval_prop_domain_t tmp{abs}; + operator|=(std::move(tmp)); +} + +void interval_prop_domain_t::operator|=(interval_prop_domain_t&& abs) { + if (is_bottom()) { + *this = abs; + return; + } + *this = *this | std::move(abs); +} + +interval_prop_domain_t interval_prop_domain_t::operator|(const interval_prop_domain_t& other) const { + if (is_bottom() || other.is_top()) { + return other; + } + else if (other.is_bottom() || is_top()) { + return *this; + } + return interval_prop_domain_t(m_registers_interval_values | other.m_registers_interval_values, + m_stack_slots_interval_values | other.m_stack_slots_interval_values); +} + +interval_prop_domain_t interval_prop_domain_t::operator|(interval_prop_domain_t&& other) const { + if (is_bottom() || other.is_top()) { + return std::move(other); + } + else if (other.is_bottom() || is_top()) { + return *this; + } + return interval_prop_domain_t(m_registers_interval_values | std::move(other.m_registers_interval_values), + m_stack_slots_interval_values | std::move(other.m_stack_slots_interval_values)); +} + +interval_prop_domain_t interval_prop_domain_t::operator&(const interval_prop_domain_t& abs) const { + /* WARNING: The operation is not implemented yet.*/ + return abs; +} + +interval_prop_domain_t interval_prop_domain_t::widen(const interval_prop_domain_t& abs) const { + /* WARNING: The operation is not implemented yet.*/ + return abs; +} + +interval_prop_domain_t interval_prop_domain_t::narrow(const interval_prop_domain_t& other) const { + /* WARNING: The operation is not implemented yet.*/ + return other; +} + +void interval_prop_domain_t::write(std::ostream& os) const {} + +std::string interval_prop_domain_t::domain_name() const { + return "interval_prop_domain"; +} + +crab::bound_t interval_prop_domain_t::get_instruction_count_upper_bound() { + /* WARNING: The operation is not implemented yet.*/ + return crab::bound_t{crab::number_t{0}}; +} + +string_invariant interval_prop_domain_t::to_set() { + return string_invariant{}; +} + +interval_prop_domain_t interval_prop_domain_t::setup_entry() { + std::shared_ptr all_intervals = std::make_shared(); + registers_cp_state_t registers(all_intervals); + + interval_prop_domain_t cp(std::move(registers), stack_cp_state_t::top()); + return cp; +} + +void interval_prop_domain_t::operator()(const Un& u, location_t loc, int print) { + auto reg_with_loc = reg_with_loc_t(u.dst.v, loc); + m_registers_interval_values.insert(u.dst.v, reg_with_loc, interval_t::top()); +} + +void interval_prop_domain_t::operator()(const LoadMapFd &u, location_t loc, int print) { + m_registers_interval_values -= u.dst.v; +} + +void interval_prop_domain_t::operator()(const ValidSize& s, location_t loc, int print) { + auto reg_v = m_registers_interval_values.find(s.reg.v); + if (reg_v) { + auto reg_value = reg_v.value(); + if ((s.can_be_zero && reg_value.lb() >= bound_t{number_t{0}}) + || (!s.can_be_zero && reg_value.lb() > bound_t{number_t{0}})) { + return; + } + } + //std::cout << "type error: Valid Size assertion fail\n"; + m_errors.push_back("Valid Size assertion fail"); +} + +void interval_prop_domain_t::do_call(const Call& u, const interval_values_stack_t& store_in_stack, + location_t loc) { + + auto r0 = register_t{R0_RETURN_VALUE}; + for (const auto& kv : store_in_stack) { + auto key = kv.first; + auto width = kv.second.second; + auto overlapping_cells + = m_stack_slots_interval_values.find_overlapping_cells(key, width); + m_stack_slots_interval_values.remove_overlap(overlapping_cells, key, width); + m_stack_slots_interval_values.store(kv.first, kv.second.first.to_interval(), + kv.second.second); + } + if (u.is_map_lookup) { + m_registers_interval_values -= r0; + } + else { + auto r0_with_loc = reg_with_loc_t(r0, loc); + m_registers_interval_values.insert(r0, r0_with_loc, interval_t::top()); + } +} + +void interval_prop_domain_t::operator()(const Packet &u, location_t loc, int print) { + auto r0 = register_t{R0_RETURN_VALUE}; + auto r0_with_loc = reg_with_loc_t(r0, loc); + m_registers_interval_values.insert(r0, r0_with_loc, interval_t::top()); +} + +void interval_prop_domain_t::operator()(const Assume &s, location_t loc, int print) { + // TODO: generalize for intervals + using Op = Condition::Op; + Condition cond = s.cond; + int64_t imm = 0; + bool is_imm = false; + auto reg_with_loc = reg_with_loc_t(cond.left.v, loc); + auto left_reg_optional = m_registers_interval_values.find(cond.left.v); + if (!left_reg_optional) { + return; + } + auto left_value = left_reg_optional.value(); + bound_t right_value = bound_t(-1); + if (std::holds_alternative(cond.right)) { + auto reg = std::get(cond.right).v; + auto right_reg_optional = m_registers_interval_values.find(reg); + if (!right_reg_optional) { + //std::cout << "type error: assumption for an unknown register\n"; + m_errors.push_back("assumption for an unknown register"); + return; + } + auto right_reg = right_reg_optional->to_interval(); + auto right_reg_singleton = right_reg.singleton(); + if (!right_reg_singleton) { + //std::cout << "type error: assumption for a non-singleton register\n"; + m_errors.push_back("assumption for a non-singleton register"); + return; + } + right_value = right_reg_singleton.value(); + } + else { + imm = std::get(cond.right).v; + is_imm = true; + right_value = bound_t(static_cast(imm)); + } + bool is_right_within_left_interval = left_value.lb() <= right_value + && right_value <= left_value.ub(); + switch (cond.op) { + case Op::EQ: { + if (is_right_within_left_interval) + m_registers_interval_values.insert(cond.left.v, reg_with_loc, interval_t(right_value)); + return; + } + case Op::NE: { + if (right_value <= left_value.lb() || right_value >= left_value.ub()) { + if (right_value == left_value.lb()) + m_registers_interval_values.insert(cond.left.v, reg_with_loc, + interval_t(left_value.lb() + number_t{1}, left_value.ub())); + else if (right_value == left_value.ub()) + m_registers_interval_values.insert(cond.left.v, reg_with_loc, + interval_t(left_value.lb(), left_value.ub() - number_t{1})); + return; + } + break; + } + case Op::SGE: + case Op::GE: + { + if (is_right_within_left_interval || right_value < left_value.lb()) { + if (is_right_within_left_interval) { + if (cond.op == Op::GE && is_imm) { + auto value = bound_t(static_cast(imm)); + m_registers_interval_values.insert(cond.left.v, reg_with_loc, + interval_t(value, left_value.ub())); + } + else { + m_registers_interval_values.insert(cond.left.v, reg_with_loc, + interval_t(right_value, left_value.ub())); + } + } + return; + } + break; + } + case Op::SLE: + case Op::LE: + { + if (is_right_within_left_interval || right_value > left_value.ub()) { + if (is_right_within_left_interval) { + if (cond.op == Op::LE && is_imm && left_value.lb() < number_t{0}) { + m_registers_interval_values.insert(cond.left.v, reg_with_loc, + interval_t(number_t{0}, right_value)); + } + else { + m_registers_interval_values.insert(cond.left.v, reg_with_loc, + interval_t(left_value.lb(), right_value)); + } + } + return; + } + break; + } + case Op::SGT: + case Op::GT: { + auto right_value_plus_1 = right_value + bound_t(1); + bool is_right_plus_1_within_left_interval = left_value.lb() <= right_value_plus_1 + && right_value_plus_1 <= left_value.ub(); + if (is_right_plus_1_within_left_interval || right_value_plus_1 < left_value.lb()) { + if (is_right_plus_1_within_left_interval) { + if (cond.op == Op::GT && is_imm) { + auto value = bound_t(static_cast(imm)); + m_registers_interval_values.insert(cond.left.v, reg_with_loc, + interval_t(value + number_t{1}, left_value.ub())); + } + else { + m_registers_interval_values.insert(cond.left.v, reg_with_loc, + interval_t(right_value_plus_1, left_value.ub())); + } + } + return; + } + break; + } + case Op::SLT: + case Op::LT: { + auto right_value_minus_1 = right_value - number_t{1}; + bool is_right_minus_1_within_left_interval = left_value.lb() <= right_value_minus_1 + && right_value_minus_1 <= left_value.ub(); + if (is_right_minus_1_within_left_interval || right_value_minus_1 > left_value.ub()) { + if (is_right_minus_1_within_left_interval) { + if (cond.op == Op::LT && is_imm) { + auto value = bound_t(static_cast(imm)); + m_registers_interval_values.insert(cond.left.v, reg_with_loc, + interval_t(left_value.lb(), value - number_t{1})); + } + else { + m_registers_interval_values.insert(cond.left.v, reg_with_loc, + interval_t(left_value.lb(), right_value_minus_1)); + } + } + return; + } + break; + } + default: return; + m_registers_interval_values.insert(cond.left.v, reg_with_loc, interval_t::bottom()); + } +} + +void interval_prop_domain_t::do_bin(const Bin& bin, + const std::optional& src_interval_opt, + const std::optional& dst_interval_opt, + const std::optional& src_ptr_or_mapfd_opt, + const std::optional& dst_ptr_or_mapfd_opt, + const interval_t& subtract_interval, location_t loc) { + using Op = Bin::Op; + // if op is not MOV, + // we skip handling in this domain is when dst is pointer and src is numerical value + if (bin.op != Op::MOV && dst_ptr_or_mapfd_opt && src_interval_opt) return; + // if op is MOV, + // we skip handling in this domain is when both dst and src are pointers + // when dst is not known and src is pointer + if (bin.op == Op::MOV && + ((dst_ptr_or_mapfd_opt && src_ptr_or_mapfd_opt) + || (!dst_interval_opt && src_ptr_or_mapfd_opt))) + return; + + uint64_t imm = std::holds_alternative(bin.v) ? std::get(bin.v).v : 0; + interval_t src_interval = interval_t::bottom(), dst_interval = interval_t::bottom(); + if (src_interval_opt) src_interval = std::move(src_interval_opt.value()); + if (dst_interval_opt) dst_interval = std::move(dst_interval_opt.value()); + + auto reg_with_loc = reg_with_loc_t(bin.dst.v, loc); + switch (bin.op) + { + // ra = b + case Op::MOV: { + if (src_interval_opt) + dst_interval = src_interval; + else if (dst_interval_opt) { + m_registers_interval_values -= bin.dst.v; + return; + } + break; + } + // ra += b + case Op::ADD: { + if (dst_ptr_or_mapfd_opt && src_ptr_or_mapfd_opt) return; + if (dst_interval_opt && src_interval_opt) + dst_interval += src_interval; + else if (dst_interval_opt && src_ptr_or_mapfd_opt) { + m_registers_interval_values -= bin.dst.v; + return; + } + break; + } + // ra -= b + case Op::SUB: { + if (dst_ptr_or_mapfd_opt && src_ptr_or_mapfd_opt) + dst_interval = subtract_interval; + else if (dst_interval_opt && src_interval_opt) + dst_interval -= src_interval; + else if (dst_interval_opt && src_ptr_or_mapfd_opt) { + m_registers_interval_values -= bin.dst.v; + return; + } + break; + } + // ra *= b + case Op::MUL: { + dst_interval *= src_interval; + break; + } + // ra /= b + case Op::UDIV: { + dst_interval /= src_interval; + break; + } + // ra %= b + case Op::UMOD: { + dst_interval = dst_interval.SRem(src_interval); + break; + } + // ra |= b + case Op::OR: { + dst_interval = dst_interval.Or(src_interval); + break; + } + // ra &= b + case Op::AND: { + dst_interval = dst_interval.And(src_interval); + if ((int32_t)imm > 0) + dst_interval = interval_t(number_t(0), number_t(static_cast(imm))); + break; + } + // ra <<= b + case Op::LSH: { + dst_interval = dst_interval.Shl(src_interval); + break; + } + // ra >>= b + case Op::RSH: { + dst_interval = dst_interval.LShr(src_interval); + break; + } + // ra >>>= b + case Op::ARSH: { + dst_interval = dst_interval.AShr(src_interval); + break; + } + // ra ^= b + case Op::XOR: { + dst_interval = dst_interval.Xor(src_interval); + break; + } + default: { + dst_interval = interval_t::top(); + break; + } + } + m_registers_interval_values.insert(bin.dst.v, reg_with_loc, dst_interval); +} + + +void interval_prop_domain_t::do_load(const Mem& b, const Reg& target_reg, + std::optional basereg_type, std::optional targetreg_type, + location_t loc) { + if (!basereg_type) { + m_registers_interval_values -= target_reg.v; + return; + } + + auto basereg_ptr_or_mapfd_type = basereg_type.value(); + int offset = b.access.offset; + int width = b.access.width; + + auto reg_with_loc = reg_with_loc_t(target_reg.v, loc); + if (std::holds_alternative(basereg_ptr_or_mapfd_type)) { + auto p_with_off = std::get(basereg_ptr_or_mapfd_type); + if (p_with_off.get_region() == crab::region_t::T_STACK) { + auto ptr_offset = p_with_off.get_offset(); + auto load_at_interval = ptr_offset.to_interval() + interval_t{number_t(static_cast(offset))}; + auto load_at_singleton = load_at_interval.singleton(); + if (load_at_singleton) { + auto load_at = load_at_singleton.value(); + auto loaded = m_stack_slots_interval_values.find((uint64_t)load_at); + if (loaded) { + auto loaded_cells = loaded.value(); + m_registers_interval_values.insert(target_reg.v, reg_with_loc, + loaded_cells.first.to_interval()); + return; + } + } + auto load_at_lb_opt = load_at_interval.lb().number(); + auto load_at_ub_opt = load_at_interval.ub().number(); + if (!load_at_lb_opt || !load_at_ub_opt) { + m_registers_interval_values -= target_reg.v; + //std::cout << "type error: missing offset information\n"; + m_errors.push_back("missing offset information"); + return; + } + auto load_at_lb = load_at_lb_opt.value(); + auto load_at_ub = load_at_ub_opt.value(); + auto start = (uint64_t)load_at_lb; + auto width_to_check = (int)(load_at_ub+number_t(width)-load_at_lb); + + if (m_stack_slots_interval_values.all_numeric(start, width_to_check)) { + m_registers_interval_values.insert(target_reg.v, reg_with_loc, + interval_t::top()); + } + else { + m_registers_interval_values -= target_reg.v; + } + return; + } + } + // we check targetreg_type because in case we already loaded a pointer from ctx, + // we then do not store a number + if (!targetreg_type) { // we are loading from ctx, packet or shared + m_registers_interval_values.insert(target_reg.v, reg_with_loc, interval_t::top()); + } + else { + m_registers_interval_values -= target_reg.v; + } +} + +void interval_prop_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, + std::optional basereg_type) { + int offset = b.access.offset; + int width = b.access.width; + + if (!basereg_type) { + return; + } + auto basereg_ptr_or_mapfd_type = basereg_type.value(); + auto targetreg_type = m_registers_interval_values.find(target_reg.v); + if (std::holds_alternative(basereg_ptr_or_mapfd_type)) { + auto basereg_ptr_with_off_type = std::get(basereg_ptr_or_mapfd_type); + auto offset_singleton = basereg_ptr_with_off_type.get_offset().to_interval().singleton(); + if (!offset_singleton) { + //std::cout << "type error: doing a store with unknown offset\n"; + m_errors.push_back("doing a store with unknown offset"); + return; + } + auto store_at = (uint64_t)offset_singleton.value() + (uint64_t)offset; + if (basereg_ptr_with_off_type.get_region() == crab::region_t::T_STACK) { + auto overlapping_cells + = m_stack_slots_interval_values.find_overlapping_cells(store_at, width); + m_stack_slots_interval_values.remove_overlap(overlapping_cells, store_at, width); + + // if targetreg_type is empty, we are storing a pointer + if (!targetreg_type) return; + + auto type_to_store = targetreg_type.value(); + m_stack_slots_interval_values.store(store_at, type_to_store, width); + } + } + else {} +} + +void interval_prop_domain_t::set_require_check(check_require_func_t f) {} + +std::optional interval_prop_domain_t::find_in_stack(uint64_t key) const { + return m_stack_slots_interval_values.find(key); +} + +void interval_prop_domain_t::adjust_bb_for_types(location_t loc) { + m_registers_interval_values.adjust_bb_for_registers(loc); +} + +std::vector interval_prop_domain_t::get_stack_keys() const { + return m_stack_slots_interval_values.get_keys(); +} + +bool interval_prop_domain_t::all_numeric_in_stack(uint64_t start_loc, int width) const { + return m_stack_slots_interval_values.all_numeric(start_loc, width); +} + +} // namespace crab diff --git a/src/crab/interval_prop_domain.hpp b/src/crab/interval_prop_domain.hpp new file mode 100644 index 000000000..fe527714e --- /dev/null +++ b/src/crab/interval_prop_domain.hpp @@ -0,0 +1,164 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include + +#include "crab/abstract_domain.hpp" +#include "crab/region_domain.hpp" +#include "crab/cfg.hpp" +#include "linear_constraint.hpp" +#include "string_constraints.hpp" +#include "crab/common.hpp" + +namespace crab { + +using live_registers_t = std::array, 11>; +using global_interval_env_t = std::unordered_map; + +class registers_cp_state_t { + + live_registers_t m_cur_def; + std::shared_ptr m_interval_env; + bool m_is_bottom = false; + + public: + bool is_bottom() const; + bool is_top() const; + void set_to_bottom(); + void set_to_top(); + std::optional find(reg_with_loc_t reg) const; + std::optional find(register_t key) const; + void insert(register_t, const reg_with_loc_t&, interval_t); + void operator-=(register_t); + registers_cp_state_t operator|(const registers_cp_state_t& other) const; + registers_cp_state_t(bool is_bottom = false) : m_interval_env(nullptr), + m_is_bottom(is_bottom) {} + explicit registers_cp_state_t(live_registers_t&& vars, + std::shared_ptr interval_env, bool is_bottom = false) + : m_cur_def(std::move(vars)), m_interval_env(interval_env), m_is_bottom(is_bottom) {} + explicit registers_cp_state_t(std::shared_ptr interval_env, + bool is_bottom = false) + : m_interval_env(interval_env), m_is_bottom(is_bottom) {} + void adjust_bb_for_registers(location_t); + void print_all_register_types() const; +}; + +using interval_cells_t = std::pair; // intervals with width +using interval_values_stack_t = std::map; + +class stack_cp_state_t { + + interval_values_stack_t m_interval_values; + bool m_is_bottom = false; + + public: + bool is_bottom() const; + bool is_top() const; + void set_to_bottom(); + void set_to_top(); + static stack_cp_state_t top(); + std::optional find(uint64_t) const; + void store(uint64_t, mock_interval_t, int); + void operator-=(uint64_t); + bool all_numeric(uint64_t, int) const; + stack_cp_state_t operator|(const stack_cp_state_t& other) const; + stack_cp_state_t(bool is_bottom = false) : m_is_bottom(is_bottom) {} + explicit stack_cp_state_t(interval_values_stack_t&& interval_values, bool is_bottom = false) + : m_interval_values(std::move(interval_values)), m_is_bottom(is_bottom) {} + std::vector get_keys() const; + size_t size() const; + std::vector find_overlapping_cells(uint64_t, int) const; + void remove_overlap(const std::vector&, uint64_t, int); +}; + +class interval_prop_domain_t final { + registers_cp_state_t m_registers_interval_values; + stack_cp_state_t m_stack_slots_interval_values; + std::vector m_errors; + bool m_is_bottom = false; + + public: + + interval_prop_domain_t() = default; + interval_prop_domain_t(interval_prop_domain_t&& o) = default; + interval_prop_domain_t(const interval_prop_domain_t& o) = default; + explicit interval_prop_domain_t(registers_cp_state_t&& consts_regs, + stack_cp_state_t&& interval_stack_slots, bool is_bottom = false) : + m_registers_interval_values(std::move(consts_regs)), m_stack_slots_interval_values(std::move(interval_stack_slots)), + m_is_bottom(is_bottom) {} + interval_prop_domain_t& operator=(interval_prop_domain_t&& o) = default; + interval_prop_domain_t& operator=(const interval_prop_domain_t& o) = default; + // eBPF initialization: R1 points to ctx, R10 to stack, etc. + static interval_prop_domain_t setup_entry(); + // bottom/top + static interval_prop_domain_t bottom(); + void set_to_top(); + void set_to_bottom(); + bool is_bottom() const; + bool is_top() const; + // inclusion + bool operator<=(const interval_prop_domain_t& other) const; + // join + void operator|=(const interval_prop_domain_t& abs); + void operator|=(interval_prop_domain_t&& abs); + interval_prop_domain_t operator|(const interval_prop_domain_t& other) const; + interval_prop_domain_t operator|(interval_prop_domain_t&& abs) const; + // meet + interval_prop_domain_t operator&(const interval_prop_domain_t& other) const; + // widening + interval_prop_domain_t widen(const interval_prop_domain_t& other) const; + // narrowing + interval_prop_domain_t narrow(const interval_prop_domain_t& other) const; + //forget + void operator-=(variable_t var); + void operator-=(register_t reg) { m_registers_interval_values -= reg; } + + //// abstract transformers + void operator()(const Undefined&, location_t loc = boost::none, int print = 0) {} + void operator()(const Bin&, location_t loc = boost::none, int print = 0) {} + void operator()(const Un&, location_t loc = boost::none, int print = 0); + void operator()(const LoadMapFd&, location_t loc = boost::none, int print = 0); + void operator()(const Call&, location_t loc = boost::none, int print = 0) {} + void operator()(const Exit&, location_t loc = boost::none, int print = 0) {} + void operator()(const Jmp&, location_t loc = boost::none, int print = 0) {} + void operator()(const Mem&, location_t loc = boost::none, int print = 0) {} + void operator()(const Packet&, location_t loc = boost::none, int print = 0); + void operator()(const LockAdd&, location_t loc = boost::none, int print = 0) {} + void operator()(const Assume&, location_t loc = boost::none, int print = 0); + void operator()(const Assert&, location_t loc = boost::none, int print = 0) {} + void operator()(const ValidAccess&, location_t loc = boost::none, int print = 0) {} + void operator()(const Comparable&, location_t loc = boost::none, int print = 0) {} + void operator()(const Addable&, location_t loc = boost::none, int print = 0) {} + void operator()(const ValidStore&, location_t loc = boost::none, int print = 0) {} + void operator()(const TypeConstraint&, location_t loc = boost::none, int print = 0) {} + void operator()(const ValidSize&, location_t loc = boost::none, int print = 0); + void operator()(const ValidMapKeyValue&, location_t loc = boost::none, int print = 0) {} + void operator()(const ZeroCtxOffset&, location_t loc = boost::none, int print = 0) {} + void operator()(const basic_block_t& bb, bool check_termination, int print = 0) {} + void write(std::ostream& os) const; + std::string domain_name() const; + crab::bound_t get_instruction_count_upper_bound(); + string_invariant to_set(); + void set_require_check(check_require_func_t f); + + void do_load(const Mem&, const Reg&, std::optional, + std::optional, location_t); + void do_mem_store(const Mem&, const Reg&, std::optional); + void do_call(const Call&, const interval_values_stack_t&, location_t); + void do_bin(const Bin&, const std::optional&, const std::optional&, + const std::optional&, const std::optional&, + const interval_t&, location_t); + std::optional find_interval_value(register_t) const; + std::optional find_interval_at_loc(const reg_with_loc_t reg) const; + std::optional find_in_stack(uint64_t) const; + void adjust_bb_for_types(location_t); + std::vector get_stack_keys() const; + bool all_numeric_in_stack(uint64_t, int) const; + [[nodiscard]] std::vector& get_errors() { return m_errors; } + void reset_errors() { m_errors.clear(); } +}; // end interval_prop_domain_t + +} // namespace crab diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index caa623f33..61b3c44f1 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -11,7 +11,7 @@ bool type_domain_t::is_bottom() const { bool type_domain_t::is_top() const { if (m_is_bottom) return false; - return (m_region.is_top() && m_offset.is_top()); + return (m_region.is_top() && m_offset.is_top() && m_interval.is_top()); } type_domain_t type_domain_t::bottom() { @@ -27,6 +27,7 @@ void type_domain_t::set_to_bottom() { void type_domain_t::set_to_top() { m_region.set_to_top(); m_offset.set_to_top(); + m_interval.set_to_top(); } bool type_domain_t::operator<=(const type_domain_t& abs) const { @@ -60,7 +61,8 @@ type_domain_t type_domain_t::operator|(const type_domain_t& other) const { else if (other.is_bottom() || is_top()) { return *this; } - return type_domain_t(m_region | other.m_region, m_offset | other.m_offset); + return type_domain_t(m_region | other.m_region, m_offset | other.m_offset, + m_interval | other.m_interval); } type_domain_t type_domain_t::operator|(type_domain_t&& other) const { @@ -70,7 +72,8 @@ type_domain_t type_domain_t::operator|(type_domain_t&& other) const { else if (other.is_bottom() || is_top()) { return *this; } - return type_domain_t(m_region | std::move(other.m_region), m_offset | std::move(other.m_offset)); + return type_domain_t(m_region | std::move(other.m_region), + m_offset | std::move(other.m_offset), m_interval | std::move(other.m_interval)); } type_domain_t type_domain_t::operator&(const type_domain_t& abs) const { @@ -107,6 +110,7 @@ void type_domain_t::operator()(const Un& u, location_t loc, int print) { void type_domain_t::operator()(const LoadMapFd& u, location_t loc, int print) { m_region(u, loc); m_offset(u, loc); + m_interval(u, loc); } void type_domain_t::operator()(const Atomic &u, location_t loc, int print) { @@ -118,11 +122,15 @@ void type_domain_t::operator()(const IncrementLoopCounter &u, location_t loc, in void type_domain_t::operator()(const Call& u, location_t loc, int print) { + using interval_values_stack_t = std::map; + interval_values_stack_t stack_values; for (ArgPair param : u.pairs) { if (param.kind == ArgPair::Kind::PTR_TO_WRITABLE_MEM) { auto maybe_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(param.mem.v); - if (!maybe_ptr_or_mapfd) continue; + auto maybe_width_interval = m_interval.find_interval_value(param.size.v); + if (!maybe_ptr_or_mapfd || !maybe_width_interval) continue; auto ptr_or_mapfd = maybe_ptr_or_mapfd.value(); + auto width_interval = maybe_width_interval->to_interval(); if (std::holds_alternative(ptr_or_mapfd)) { auto ptr_with_off = std::get(ptr_or_mapfd); if (ptr_with_off.get_region() == region_t::T_STACK) { @@ -133,12 +141,18 @@ void type_domain_t::operator()(const Call& u, location_t loc, int print) { continue; } // TODO: forget the stack at [offset, offset+width] + auto offset = (uint64_t)offset_singleton.value(); + if (auto single_width = width_interval.singleton(); single_width) { + int width = (int)single_width.value(); + stack_values[offset] = std::make_pair(interval_t::top(), width); + } } } } } m_region(u, loc); m_offset(u, loc); + m_interval.do_call(u, stack_values, loc); } void type_domain_t::operator()(const Callx &u, location_t loc, int print) { @@ -154,6 +168,7 @@ void type_domain_t::operator()(const Jmp& u, location_t loc, int print) {} void type_domain_t::operator()(const Packet& u, location_t loc, int print) { m_region(u, loc); m_offset(u, loc); + m_interval(u, loc); } void type_domain_t::operator()(const Assume& s, location_t loc, int print) { @@ -171,6 +186,7 @@ void type_domain_t::operator()(const Assume& s, location_t loc, int print) { } else if (!maybe_left_type && !maybe_right_type) { // both numbers + m_interval(s, loc); } else { // We should only reach here if `--assume-assert` is off @@ -182,16 +198,28 @@ void type_domain_t::operator()(const Assume& s, location_t loc, int print) { } void type_domain_t::operator()(const ValidDivisor& s, location_t loc, int print) { - m_region(s, loc); + /* WARNING: The operation is not implemented yet.*/ } void type_domain_t::operator()(const ValidAccess& s, location_t loc, int print) { auto reg_type = m_region.find_ptr_or_mapfd_type(s.reg.v); + auto mock_interval_type = m_interval.find_interval_value(s.reg.v); + auto interval_type = + mock_interval_type ? mock_interval_type->to_interval() : std::optional{}; if (reg_type) { m_region(s, loc); - std::optional width_interval = {}; + std::optional width_mock_interval; + if (std::holds_alternative(s.width)) { + width_mock_interval = m_interval.find_interval_value(std::get(s.width).v); + } + else { + auto imm = std::get(s.width); + width_mock_interval = mock_interval_t{interval_t(number_t{imm.v}, number_t{imm.v})}; + } + auto width_interval = + width_mock_interval ? width_mock_interval->to_interval() : std::optional{}; if (is_packet_ptr(reg_type)) { - m_offset.check_valid_access(s, reg_type, std::nullopt, width_interval); + m_offset.check_valid_access(s, reg_type, interval_type, width_interval); } } } @@ -201,7 +229,6 @@ void type_domain_t::operator()(const TypeConstraint& s, location_t loc, int prin } void type_domain_t::operator()(const Assert& u, location_t loc, int print) { - if (is_bottom()) return; std::visit([this, loc, print](const auto& v) { std::apply(*this, std::make_tuple(v, loc, print)); }, u.cst); } @@ -209,12 +236,15 @@ void type_domain_t::operator()(const Comparable& u, location_t loc, int print) { auto maybe_ptr_or_mapfd1 = m_region.find_ptr_or_mapfd_type(u.r1.v); auto maybe_ptr_or_mapfd2 = m_region.find_ptr_or_mapfd_type(u.r2.v); + auto maybe_num_type1 = m_interval.find_interval_value(u.r1.v); + auto maybe_num_type2 = m_interval.find_interval_value(u.r2.v); if (maybe_ptr_or_mapfd1 && maybe_ptr_or_mapfd2) { if (is_mapfd_type(maybe_ptr_or_mapfd1) && is_mapfd_type(maybe_ptr_or_mapfd2)) return; if (!is_shared_ptr(maybe_ptr_or_mapfd1) && same_region(*maybe_ptr_or_mapfd1, *maybe_ptr_or_mapfd2)) return; } else if (!maybe_ptr_or_mapfd2) { + // TODO: interval check here // two numbers can be compared // if r1 is a pointer, r2 must be a number return; @@ -232,7 +262,7 @@ void type_domain_t::operator()(const ValidStore& u, location_t loc, int print) { } void type_domain_t::operator()(const ValidSize& u, location_t loc, int print) { - /* WARNING: The operation is not implemented yet.*/ + m_interval(u, loc); } void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int print) { @@ -273,6 +303,8 @@ void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int pr return; } auto offset_to_check = (uint64_t)offset_singleton.value(); + auto it = m_interval.all_numeric_in_stack(offset_to_check, width); + if (it) return; auto it2 = m_region.find_in_stack(offset_to_check); if (it2) { //std::cout << "type error: map update with a non-numerical value\n"; @@ -300,13 +332,16 @@ void type_domain_t::operator()(const ZeroCtxOffset& u, location_t loc, int print type_domain_t type_domain_t::setup_entry() { auto&& reg = crab::region_domain_t::setup_entry(); auto&& off = offset_domain_t::setup_entry(); - type_domain_t typ(std::move(reg), std::move(off)); + auto&& interval = interval_prop_domain_t::setup_entry(); + type_domain_t typ(std::move(reg), std::move(off), std::move(interval)); return typ; } void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { if (is_bottom()) return; auto dst_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(bin.dst.v); + auto dst_interval = m_interval.find_interval_value(bin.dst.v); + assert(!dst_ptr_or_mapfd || !dst_interval); std::optional src_ptr_or_mapfd; std::optional src_interval; @@ -314,6 +349,10 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { if (std::holds_alternative(bin.v)) { Reg r = std::get(bin.v); src_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(r.v); + auto src_mock_interval = m_interval.find_interval_value(r.v); + if (src_mock_interval) { + src_interval = src_mock_interval->to_interval(); + } } else { int64_t imm; @@ -323,8 +362,9 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { else { imm = static_cast(std::get(bin.v).v); } - src_interval = interval_t{crab::number_t{imm}}; + src_interval = interval_t{number_t{imm}}; } + //assert(!src_ptr_or_mapfd || !src_interval); using Op = Bin::Op; // for all operations except mov, add, sub, the src and dst should be numbers @@ -334,25 +374,30 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { //std::cout << "type error: operation on pointers not allowed\n"; m_region -= bin.dst.v; m_offset -= bin.dst.v; + m_interval -= bin.dst.v; return; } interval_t subtracted_reg = m_region.do_bin(bin, src_interval, src_ptr_or_mapfd, dst_ptr_or_mapfd, loc); interval_t subtracted_off = - m_offset.do_bin(bin, src_interval, std::nullopt, src_ptr_or_mapfd, dst_ptr_or_mapfd, loc); + m_offset.do_bin(bin, src_interval, interval_t::top(), src_ptr_or_mapfd, dst_ptr_or_mapfd, loc); auto subtracted = subtracted_reg.is_bottom() ? subtracted_off : subtracted_reg; + m_interval.do_bin(bin, src_interval, interval_t::top(), src_ptr_or_mapfd, dst_ptr_or_mapfd, + subtracted, loc); } void type_domain_t::do_load(const Mem& b, const Reg& target_reg, bool unknown_ptr, std::optional basereg_opt, location_t loc, int print) { m_region.do_load(b, target_reg, unknown_ptr, loc); m_offset.do_load(b, target_reg, basereg_opt, loc); + m_interval.do_load(b, target_reg, basereg_opt, std::nullopt, loc); } void type_domain_t::do_mem_store(const Mem& b, std::optional target_opt, std::optional& basereg_opt, location_t loc, int print) { m_region.do_mem_store(b, loc); + m_interval.do_mem_store(b, get(b.value), basereg_opt); m_offset.do_mem_store(b, target_opt, basereg_opt); } @@ -376,59 +421,56 @@ void type_domain_t::operator()(const Mem& b, location_t loc, int print) { } } -void type_domain_t::print_registers() const { - std::cout << " register types: {\n"; - for (size_t i = 0; i < NUM_REGISTERS; i++) { - register_t reg = (register_t)i; - auto maybe_ptr_or_mapfd_type = m_region.find_ptr_or_mapfd_type(reg); - auto maybe_offset_info = m_offset.find_offset_info(reg); - if (maybe_ptr_or_mapfd_type) { - std::cout << " "; - print_register(std::cout, Reg{(uint8_t)reg}, maybe_ptr_or_mapfd_type, maybe_offset_info); - std::cout << "\n"; - } - } - std::cout << " }\n"; -} - void type_domain_t::print_ctx() const { std::vector ctx_keys = m_region.get_ctx_keys(); - std::cout << " ctx: {\n"; + std::cout << "\tctx: {\n"; for (auto const& k : ctx_keys) { - auto maybe_ptr = m_region.find_in_ctx(k); - auto maybe_dist = m_offset.find_in_ctx(k); - if (maybe_ptr) { - std::cout << " " << k << ": "; - print_ptr_type(std::cout, *maybe_ptr, maybe_dist); + auto ptr = m_region.find_in_ctx(k); + auto dist = m_offset.find_in_ctx(k); + if (ptr) { + std::cout << "\t\t" << k << ": "; + print_ptr_type(std::cout, *ptr, dist); std::cout << ",\n"; } } - std::cout << " }\n"; + std::cout << "\t}\n"; } void type_domain_t::print_stack() const { std::vector stack_keys_region = m_region.get_stack_keys(); - std::cout << " stack: {\n"; + std::vector stack_keys_interval = m_interval.get_stack_keys(); + std::cout << "\tstack: {\n"; for (auto const& k : stack_keys_region) { auto maybe_ptr_or_mapfd_cells = m_region.find_in_stack(k); - auto maybe_dist_cells = m_offset.find_in_stack(k); + auto dist = m_offset.find_in_stack(k); if (maybe_ptr_or_mapfd_cells) { - auto ptr_or_mapfd_cells = *maybe_ptr_or_mapfd_cells; + auto ptr_or_mapfd_cells = maybe_ptr_or_mapfd_cells.value(); int width = ptr_or_mapfd_cells.second; auto ptr_or_mapfd = ptr_or_mapfd_cells.first; - std::optional maybe_dist = maybe_dist_cells ? - std::optional{maybe_dist_cells->first} : std::nullopt; - std::cout << " [" << k << "-" << k+width-1 << "] : "; - print_ptr_or_mapfd_type(std::cout, ptr_or_mapfd, maybe_dist); + std::cout << "\t\t[" << k << "-" << k+width-1 << "] : "; + if (dist) + print_ptr_or_mapfd_type(std::cout, ptr_or_mapfd, dist->first); + else + print_ptr_or_mapfd_type(std::cout, ptr_or_mapfd, std::nullopt); + std::cout << ",\n"; + } + } + for (auto const& k : stack_keys_interval) { + auto maybe_interval_cells = m_interval.find_in_stack(k); + if (maybe_interval_cells) { + auto interval_cells = maybe_interval_cells.value(); + std::cout << "\t\t" << "[" << k << "-" << k+interval_cells.second-1 << "] : "; + print_number(std::cout, interval_cells.first.to_interval()); std::cout << ",\n"; } } - std::cout << " }\n"; + std::cout << "\t}\n"; } void type_domain_t::adjust_bb_for_types(location_t loc) { m_region.adjust_bb_for_types(loc); m_offset.adjust_bb_for_types(loc); + m_interval.adjust_bb_for_types(loc); } void type_domain_t::operator()(const basic_block_t& bb, int print) { @@ -441,6 +483,7 @@ void type_domain_t::operator()(const basic_block_t& bb, int print) { m_errors.clear(); m_region.reset_errors(); m_offset.reset_errors(); + m_interval.reset_errors(); auto label = bb.label(); uint32_t curr_pos = 0; @@ -455,6 +498,7 @@ void type_domain_t::operator()(const basic_block_t& bb, int print) { operator+=(m_region.get_errors()); operator+=(m_offset.get_errors()); + operator+=(m_interval.get_errors()); } std::optional diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 42efb4493..754903b0d 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -5,14 +5,16 @@ #include "crab/abstract_domain.hpp" #include "crab/region_domain.hpp" +#include "crab/interval_prop_domain.hpp" #include "crab/offset_domain.hpp" #include "crab/common.hpp" namespace crab { class type_domain_t final { - crab::region_domain_t m_region; - crab::offset_domain_t m_offset; + region_domain_t m_region; + offset_domain_t m_offset; + interval_prop_domain_t m_interval; bool m_is_bottom = false; std::vector m_errors; @@ -21,8 +23,9 @@ class type_domain_t final { type_domain_t() = default; type_domain_t(type_domain_t&& o) = default; type_domain_t(const type_domain_t& o) = default; - explicit type_domain_t(region_domain_t&& reg, offset_domain_t&& off, bool is_bottom = false) : - m_region(reg), m_offset(off), m_is_bottom(is_bottom) {} + explicit type_domain_t(region_domain_t&& reg, offset_domain_t&& off, interval_prop_domain_t&& + interval, bool is_bottom = false) : + m_region(reg), m_offset(off), m_interval(interval), m_is_bottom(is_bottom) {} type_domain_t& operator=(type_domain_t&& o) = default; type_domain_t& operator=(const type_domain_t& o) = default; // eBPF initialization: R1 points to ctx, R10 to stack, etc. @@ -99,7 +102,6 @@ class type_domain_t final { void operator+=(std::vector& errs) { m_errors.insert(m_errors.end(), errs.begin(), errs.end()); } - void print_initial_registers() const; }; // end type_domain_t } // namespace crab diff --git a/src/crab/type_ostream.cpp b/src/crab/type_ostream.cpp index 28eb6e139..34f8de6b7 100644 --- a/src/crab/type_ostream.cpp +++ b/src/crab/type_ostream.cpp @@ -12,6 +12,13 @@ void print_ptr_type(std::ostream& o, const crab::ptr_or_mapfd_t& ptr, std::optio } } +void print_number(std::ostream& o, crab::interval_t n) { + o << "number"; + if (!n.is_top()) { + o << "<" << n << ">"; + } +} + void print_ptr_or_mapfd_type(std::ostream& o, const crab::ptr_or_mapfd_t& ptr_or_mapfd, std::optional d) { if (std::holds_alternative(ptr_or_mapfd)) { o << std::get(ptr_or_mapfd); @@ -26,6 +33,9 @@ void print_register(std::ostream& o, const Reg& r, const std::optional); +void print_number(std::ostream&, crab::interval_t); void print_ptr_type(std::ostream&, const crab::ptr_or_mapfd_t& ptr, std::optional); void print_register(std::ostream& o, const Reg& r, const std::optional& p, std::optional); void print_annotated(std::ostream& o, const Call& call, std::optional& p); diff --git a/src/crab_verifier.cpp b/src/crab_verifier.cpp index 7d48e573e..bbab52b47 100644 --- a/src/crab_verifier.cpp +++ b/src/crab_verifier.cpp @@ -15,6 +15,7 @@ #include "crab/abstract_domain.hpp" #include "crab/ebpf_domain.hpp" #include "crab/type_domain.hpp" +#include "crab/interval_prop_domain.hpp" #include "crab/region_domain.hpp" #include "crab/offset_domain.hpp" #include "crab/fwd_analyzer.hpp" From e1d68153d44753c64c2067496040c2c114484760 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Sat, 9 Sep 2023 14:40:09 -0400 Subject: [PATCH 123/373] Better handling of Assume for interval prop domain Generalize the handling for intervals as well; Have main implementation structure similar to ZoneCrab implementation, to minimize any corner cases or incorrect analysis Signed-off-by: Ameer Hamza --- src/crab/interval_prop_domain.cpp | 330 +++++++++++++++++++----------- src/crab/interval_prop_domain.hpp | 9 + src/crab/type_domain.cpp | 13 +- 3 files changed, 228 insertions(+), 124 deletions(-) diff --git a/src/crab/interval_prop_domain.cpp b/src/crab/interval_prop_domain.cpp index 60c482e98..5ccc549c1 100644 --- a/src/crab/interval_prop_domain.cpp +++ b/src/crab/interval_prop_domain.cpp @@ -418,145 +418,237 @@ void interval_prop_domain_t::operator()(const Packet &u, location_t loc, int pri m_registers_interval_values.insert(r0, r0_with_loc, interval_t::top()); } -void interval_prop_domain_t::operator()(const Assume &s, location_t loc, int print) { - // TODO: generalize for intervals - using Op = Condition::Op; - Condition cond = s.cond; - int64_t imm = 0; - bool is_imm = false; - auto reg_with_loc = reg_with_loc_t(cond.left.v, loc); - auto left_reg_optional = m_registers_interval_values.find(cond.left.v); - if (!left_reg_optional) { - return; - } - auto left_value = left_reg_optional.value(); - bound_t right_value = bound_t(-1); - if (std::holds_alternative(cond.right)) { - auto reg = std::get(cond.right).v; - auto right_reg_optional = m_registers_interval_values.find(reg); - if (!right_reg_optional) { - //std::cout << "type error: assumption for an unknown register\n"; - m_errors.push_back("assumption for an unknown register"); - return; +void interval_prop_domain_t::assume_lt(bool strict, interval_t&& left_interval, + interval_t&& right_interval, register_t left, Value right, location_t loc) { + auto reg_with_loc_left = reg_with_loc_t(left, loc); + auto rlb = right_interval.lb(); + auto rub = right_interval.ub(); + auto llb = left_interval.lb(); + auto lub = left_interval.ub(); + + auto ub = strict ? (right_interval.lb() - number_t{1}) : right_interval.lb(); + if (lub >= rlb && llb < rlb) { + auto interval_to_insert = interval_t(llb, strict ? rlb - number_t{1} : rlb); + m_registers_interval_values.insert(left, reg_with_loc_left, interval_to_insert); + } + else if (right_interval <= left_interval && strict ? llb < rlb : llb <= rlb) { + auto interval_to_insert = interval_t(llb, strict ? rlb - number_t{1} : rlb); + m_registers_interval_values.insert(left, reg_with_loc_left, interval_to_insert); + } + else if (left_interval <= right_interval && strict ? lub < rub : lub <= rub && + std::holds_alternative(right)) { + auto right_reg = std::get(right).v; + auto reg_with_loc_right = reg_with_loc_t(right_reg, loc); + auto interval_to_insert = interval_t(strict ? lub + number_t{1} : lub, rub); + m_registers_interval_values.insert(right_reg, reg_with_loc_right, interval_to_insert); + } + else if (lub > rub && strict ? llb < rub : llb <= rub) { + auto interval_to_insert_left = interval_t(llb, strict ? rub - number_t{1} : rub); + m_registers_interval_values.insert(left, reg_with_loc_left, interval_to_insert_left); + // this is only one way to resolve this scenario, i.e. set right to singleton value (rub) + // and set left to the rest of the interval < (or <=) of right + // a more sound analysis is needed + if (std::holds_alternative(right)) { + auto right_reg = std::get(right).v; + auto reg_with_loc_right = reg_with_loc_t(right_reg, loc); + m_registers_interval_values.insert(right_reg, reg_with_loc_right, interval_t(rub)); } - auto right_reg = right_reg_optional->to_interval(); - auto right_reg_singleton = right_reg.singleton(); - if (!right_reg_singleton) { - //std::cout << "type error: assumption for a non-singleton register\n"; - m_errors.push_back("assumption for a non-singleton register"); - return; + } + else { + // TODO: verify if any legitimate case can fall into here + m_registers_interval_values.set_to_bottom(); + } +} + +void interval_prop_domain_t::assume_gt(bool strict, interval_t&& left_interval, + interval_t&& right_interval, register_t left, Value right, location_t loc) { + auto reg_with_loc_left = reg_with_loc_t(left, loc); + auto rlb = right_interval.lb(); + auto rub = right_interval.ub(); + auto llb = left_interval.lb(); + auto lub = left_interval.ub(); + + if (llb <= rub && lub > rub) { + auto interval_to_insert = interval_t(strict ? rub + number_t{1} : rub, lub); + m_registers_interval_values.insert(left, reg_with_loc_left, interval_to_insert); + } + else if (right_interval <= left_interval && strict ? lub > rub : lub >= rub) { + auto interval_to_insert = interval_t(strict ? rub + number_t{1} : rub, lub); + m_registers_interval_values.insert(left, reg_with_loc_left, interval_to_insert); + } + else if (left_interval <= right_interval && strict ? llb > rlb : llb >= rlb && + std::holds_alternative(right)) { + auto right_reg = std::get(right).v; + auto reg_with_loc_right = reg_with_loc_t(right_reg, loc); + auto interval_to_insert = interval_t(rlb, strict ? llb - number_t{1} : llb); + m_registers_interval_values.insert(right_reg, reg_with_loc_right, interval_to_insert); + } + else if (llb < rlb && strict ? lub > rlb : lub >= rlb) { + auto interval_to_insert_left = interval_t(strict ? rlb + number_t{1} : rlb, lub); + m_registers_interval_values.insert(left, reg_with_loc_left, interval_to_insert_left); + // this is only one way to resolve this scenario, i.e. set right to singleton value (rlb) + // and set left to the rest of the interval > (or >=) of right + // a more sound analysis is needed + if (std::holds_alternative(right)) { + auto right_reg = std::get(right).v; + auto reg_with_loc_right = reg_with_loc_t(right_reg, loc); + m_registers_interval_values.insert(right_reg, reg_with_loc_right, interval_t(rlb)); } - right_value = right_reg_singleton.value(); } else { - imm = std::get(cond.right).v; - is_imm = true; - right_value = bound_t(static_cast(imm)); - } - bool is_right_within_left_interval = left_value.lb() <= right_value - && right_value <= left_value.ub(); - switch (cond.op) { - case Op::EQ: { - if (is_right_within_left_interval) - m_registers_interval_values.insert(cond.left.v, reg_with_loc, interval_t(right_value)); - return; + // TODO: verify if any legitimate case can fall into here + m_registers_interval_values.set_to_bottom(); + } +} + +void interval_prop_domain_t::assume_gt_and_lt(bool is64, bool strict, bool is_lt, + interval_t&& left_interval, interval_t&& right_interval, register_t left, + Value right, location_t loc) { + + auto llb = left_interval.lb(); + auto lub = left_interval.ub(); + auto rlb = right_interval.lb(); + auto rub = right_interval.ub(); + if (!is_lt && (strict ? (lub <= rlb) : (lub < rlb))) { + // Left unsigned interval is lower than right unsigned interval. + m_registers_interval_values.set_to_bottom(); + } else if (is_lt && (strict ? (llb >= rub) : (llb > rub))) { + // Left unsigned interval is higher than right unsigned interval. + m_registers_interval_values.set_to_bottom(); + } + if (is_lt && (strict ? (lub < rlb) : (lub <= rlb))) { + // Left unsigned interval is lower than right unsigned interval. + // TODO: verify if setting to top is the correct equivalent of returning linear cst true + m_registers_interval_values.set_to_top(); + } else if (!is_lt && (strict ? (llb > rub) : (llb >= rub))) { + // Left unsigned interval is higher than right unsigned interval. + m_registers_interval_values.set_to_top(); + } + + if (is_lt) + assume_lt(strict, std::move(left_interval), std::move(right_interval), left, right, loc); + else + assume_gt(strict, std::move(left_interval), std::move(right_interval), left, right, loc); +} + +void interval_prop_domain_t::assume_unsigned_cst_interval(Condition::Op op, bool is64, + interval_t&& left_interval, interval_t&& right_interval, register_t left, Value right, + location_t loc) { + + for (interval_t* interval : {&left_interval, &right_interval}) { + if (!(*interval <= interval_t::unsigned_int(true))) { + *interval = interval->truncate_to_uint(true); } - case Op::NE: { - if (right_value <= left_value.lb() || right_value >= left_value.ub()) { - if (right_value == left_value.lb()) - m_registers_interval_values.insert(cond.left.v, reg_with_loc, - interval_t(left_value.lb() + number_t{1}, left_value.ub())); - else if (right_value == left_value.ub()) - m_registers_interval_values.insert(cond.left.v, reg_with_loc, - interval_t(left_value.lb(), left_value.ub() - number_t{1})); + } + + // Handle uvalue != right. + if (op == Condition::Op::NE) { + if (auto rn = right_interval.singleton()) { + if (rn == left_interval.truncate_to_uint(is64).lb().number()) { + // "NE lower bound" is equivalent to "GT lower bound". + op = Condition::Op::GT; + right_interval = interval_t{left_interval.lb()}; + } else if (rn == left_interval.ub().number()) { + // "NE upper bound" is equivalent to "LT upper bound". + op = Condition::Op::LT; + right_interval = interval_t{left_interval.ub()}; + } else { return; } - break; + } else { + return; } - case Op::SGE: - case Op::GE: - { - if (is_right_within_left_interval || right_value < left_value.lb()) { - if (is_right_within_left_interval) { - if (cond.op == Op::GE && is_imm) { - auto value = bound_t(static_cast(imm)); - m_registers_interval_values.insert(cond.left.v, reg_with_loc, - interval_t(value, left_value.ub())); - } - else { - m_registers_interval_values.insert(cond.left.v, reg_with_loc, - interval_t(right_value, left_value.ub())); - } - } - return; - } - break; + } + + const bool is_lt = op == Condition::Op::LT || op == Condition::Op::LE; + bool strict = op == Condition::Op::LT || op == Condition::Op::GT; + + assume_gt_and_lt(is64, strict, is_lt, std::move(left_interval), std::move(right_interval), + left, right, loc); +} + +void interval_prop_domain_t::assume_signed_cst_interval(Condition::Op op, bool is64, + interval_t&& left_interval, interval_t&& right_interval, register_t left, Value right, + location_t loc) { + + for (interval_t* interval : {&left_interval, &right_interval}) { + if (!(*interval <= interval_t::signed_int(is64))) { + *interval = interval->truncate_to_sint(is64); + } + } + + if (op == Condition::Op::EQ) { + auto eq_interval = right_interval & left_interval; + m_registers_interval_values.insert(left, reg_with_loc_t(left, loc), eq_interval); + if (std::holds_alternative(right)) { + auto right_reg = std::get(right).v; + m_registers_interval_values.insert(right_reg, + reg_with_loc_t(right_reg, loc), eq_interval); } + return; + } + + const bool is_lt = op == Condition::Op::SLT || op == Condition::Op::SLE; + bool strict = op == Condition::Op::SLT || op == Condition::Op::SGT; + + assume_gt_and_lt(is64, strict, is_lt, std::move(left_interval), std::move(right_interval), + left, right, loc); +} + +void interval_prop_domain_t::assume_cst(Condition::Op op, bool is64, register_t left, + Value right, location_t loc) { + using Op = Condition::Op; + auto left_mock_interval = m_registers_interval_values.find(left); + if (!left_mock_interval) return; + auto left_interval = left_mock_interval->to_interval(); + interval_t right_interval = interval_t::bottom(); + int64_t imm; + bool is_right_reg = std::holds_alternative(right); + if (is_right_reg) { + auto right_mock_interval = m_registers_interval_values.find(std::get(right).v); + if (!right_mock_interval) return; + right_interval = right_mock_interval->to_interval(); + } + else { + imm = static_cast(std::get(right).v); + } + if (left_interval.is_bottom() || (is_right_reg && right_interval.is_bottom())) { + m_registers_interval_values.set_to_bottom(); + return; + } + switch (op) { + case Op::EQ: + case Op::SGE: case Op::SLE: - case Op::LE: - { - if (is_right_within_left_interval || right_value > left_value.ub()) { - if (is_right_within_left_interval) { - if (cond.op == Op::LE && is_imm && left_value.lb() < number_t{0}) { - m_registers_interval_values.insert(cond.left.v, reg_with_loc, - interval_t(number_t{0}, right_value)); - } - else { - m_registers_interval_values.insert(cond.left.v, reg_with_loc, - interval_t(left_value.lb(), right_value)); - } - } - return; - } + case Op::SGT: + case Op::SLT: { + assume_signed_cst_interval(op, is64, std::move(left_interval), + is_right_reg ? std::move(right_interval) : interval_t(number_t{imm}), + left, right, loc); break; } - case Op::SGT: - case Op::GT: { - auto right_value_plus_1 = right_value + bound_t(1); - bool is_right_plus_1_within_left_interval = left_value.lb() <= right_value_plus_1 - && right_value_plus_1 <= left_value.ub(); - if (is_right_plus_1_within_left_interval || right_value_plus_1 < left_value.lb()) { - if (is_right_plus_1_within_left_interval) { - if (cond.op == Op::GT && is_imm) { - auto value = bound_t(static_cast(imm)); - m_registers_interval_values.insert(cond.left.v, reg_with_loc, - interval_t(value + number_t{1}, left_value.ub())); - } - else { - m_registers_interval_values.insert(cond.left.v, reg_with_loc, - interval_t(right_value_plus_1, left_value.ub())); - } - } - return; - } + case Op::SET: + case Op::NSET: { + // TODO: implement SET and NSET break; } - case Op::SLT: + case Op::NE: + case Op::GE: + case Op::LE: + case Op::GT: case Op::LT: { - auto right_value_minus_1 = right_value - number_t{1}; - bool is_right_minus_1_within_left_interval = left_value.lb() <= right_value_minus_1 - && right_value_minus_1 <= left_value.ub(); - if (is_right_minus_1_within_left_interval || right_value_minus_1 > left_value.ub()) { - if (is_right_minus_1_within_left_interval) { - if (cond.op == Op::LT && is_imm) { - auto value = bound_t(static_cast(imm)); - m_registers_interval_values.insert(cond.left.v, reg_with_loc, - interval_t(left_value.lb(), value - number_t{1})); - } - else { - m_registers_interval_values.insert(cond.left.v, reg_with_loc, - interval_t(left_value.lb(), right_value_minus_1)); - } - } - return; - } + assume_unsigned_cst_interval(op, is64, std::move(left_interval), + is_right_reg ? std::move(right_interval) : interval_t(number_t{(uint64_t)imm}), + left, right, loc); break; } - default: return; - m_registers_interval_values.insert(cond.left.v, reg_with_loc, interval_t::bottom()); } } +void interval_prop_domain_t::operator()(const Assume& s, location_t loc, int print) { + // nothing to do here +} + void interval_prop_domain_t::do_bin(const Bin& bin, const std::optional& src_interval_opt, const std::optional& dst_interval_opt, diff --git a/src/crab/interval_prop_domain.hpp b/src/crab/interval_prop_domain.hpp index fe527714e..6e87513b7 100644 --- a/src/crab/interval_prop_domain.hpp +++ b/src/crab/interval_prop_domain.hpp @@ -151,6 +151,15 @@ class interval_prop_domain_t final { void do_bin(const Bin&, const std::optional&, const std::optional&, const std::optional&, const std::optional&, const interval_t&, location_t); + void assume_unsigned_cst_interval(Condition::Op, bool, interval_t&&, interval_t&&, register_t, + Value, location_t); + void assume_signed_cst_interval(Condition::Op, bool, interval_t&&, interval_t&&, register_t, + Value, location_t); + void assume_cst(Condition::Op, bool, register_t, Value, location_t); + void assume_lt(bool, interval_t&&, interval_t&&, register_t, Value, location_t); + void assume_gt(bool, interval_t&&, interval_t&&, register_t, Value, location_t); + void assume_gt_and_lt(bool, bool, bool, interval_t&&, interval_t&&, register_t, + Value, location_t); std::optional find_interval_value(register_t) const; std::optional find_interval_at_loc(const reg_with_loc_t reg) const; std::optional find_in_stack(uint64_t) const; diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 61b3c44f1..26582c276 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -173,20 +173,20 @@ void type_domain_t::operator()(const Packet& u, location_t loc, int print) { void type_domain_t::operator()(const Assume& s, location_t loc, int print) { Condition cond = s.cond; - auto right = cond.right; const auto& maybe_left_type = m_region.find_ptr_or_mapfd_type(cond.left.v); - if (std::holds_alternative(right)) { - const auto& right_reg = std::get(right); + if (std::holds_alternative(cond.right)) { + const auto& right_reg = std::get(cond.right); const auto& maybe_right_type = m_region.find_ptr_or_mapfd_type(right_reg.v); if (maybe_left_type && maybe_right_type) { + // both pointers if (is_packet_ptr(maybe_left_type) && is_packet_ptr(maybe_right_type)) { m_offset(s, loc, print); } - // both pointers } else if (!maybe_left_type && !maybe_right_type) { // both numbers - m_interval(s, loc); + auto left_interval = m_interval.find_interval_value(cond.left.v); + m_interval.assume_cst(cond.op, cond.is64, cond.left.v, cond.right, loc); } else { // We should only reach here if `--assume-assert` is off @@ -195,6 +195,9 @@ void type_domain_t::operator()(const Assume& s, location_t loc, int print) { m_region.set_registers_to_top(); } } + else { + m_interval.assume_cst(cond.op, cond.is64, cond.left.v, cond.right, loc); + } } void type_domain_t::operator()(const ValidDivisor& s, location_t loc, int print) { From f8a6ede0c797ba19d419547f463cde9ad5bff618 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 11 Sep 2023 13:36:30 -0400 Subject: [PATCH 124/373] Fixes in printing intervals in the interval propagation domain Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 14 +++++++++++--- src/crab/type_domain.hpp | 1 + src/crab/type_ostream.cpp | 28 +++++++++++++++++----------- src/crab/type_ostream.hpp | 8 ++++---- 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 26582c276..77a6e3ed3 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -514,6 +514,11 @@ type_domain_t::find_offset_at_loc(const crab::reg_with_loc_t& loc) const { return m_offset.find_offset_at_loc(loc); } +std::optional +type_domain_t::find_interval_at_loc(const crab::reg_with_loc_t& loc) const { + return m_interval.find_interval_at_loc(loc); +} + std::ostream& operator<<(std::ostream& o, const type_domain_t& typ) { typ.write(o); return o; @@ -544,14 +549,16 @@ void print_annotated(std::ostream& o, const crab::type_domain_t& typ, if (std::holds_alternative(statement)) { auto r0_reg = crab::reg_with_loc_t(register_t{R0_RETURN_VALUE}, loc); auto region = typ.find_ptr_or_mapfd_at_loc(r0_reg); - print_annotated(o, std::get(statement), region); + auto interval = typ.find_interval_at_loc(r0_reg); + print_annotated(o, std::get(statement), region, interval); } else if (std::holds_alternative(statement)) { auto b = std::get(statement); auto reg_with_loc = crab::reg_with_loc_t(b.dst.v, loc); auto region = typ.find_ptr_or_mapfd_at_loc(reg_with_loc); auto offset = typ.find_offset_at_loc(reg_with_loc); - print_annotated(o, b, region, offset); + auto interval = typ.find_interval_at_loc(reg_with_loc); + print_annotated(o, b, region, offset, interval); } else if (std::holds_alternative(statement)) { auto u = std::get(statement); @@ -560,7 +567,8 @@ void print_annotated(std::ostream& o, const crab::type_domain_t& typ, auto target_reg_loc = crab::reg_with_loc_t(target_reg.v, loc); auto region = typ.find_ptr_or_mapfd_at_loc(target_reg_loc); auto offset = typ.find_offset_at_loc(target_reg_loc); - print_annotated(o, u, region, offset); + auto interval = typ.find_interval_at_loc(target_reg_loc); + print_annotated(o, u, region, offset, interval); } else o << " " << u << "\n"; } diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 754903b0d..c6b0c23aa 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -89,6 +89,7 @@ class type_domain_t final { void print_stack() const; std::optional find_ptr_or_mapfd_at_loc(const crab::reg_with_loc_t&) const; std::optional find_offset_at_loc(const crab::reg_with_loc_t&) const; + std::optional find_interval_at_loc(const crab::reg_with_loc_t&) const; private: diff --git a/src/crab/type_ostream.cpp b/src/crab/type_ostream.cpp index 34f8de6b7..09c35b45b 100644 --- a/src/crab/type_ostream.cpp +++ b/src/crab/type_ostream.cpp @@ -15,7 +15,12 @@ void print_ptr_type(std::ostream& o, const crab::ptr_or_mapfd_t& ptr, std::optio void print_number(std::ostream& o, crab::interval_t n) { o << "number"; if (!n.is_top()) { - o << "<" << n << ">"; + if (auto n_singleton = n.singleton()) { + o << "<" << *n_singleton << ">"; + } + else { + o << "<" << n << ">"; + } } } @@ -28,41 +33,42 @@ void print_ptr_or_mapfd_type(std::ostream& o, const crab::ptr_or_mapfd_t& ptr_or } } -void print_register(std::ostream& o, const Reg& r, const std::optional& p, std::optional d) { +void print_register(std::ostream& o, const Reg& r, const std::optional& p, std::optional d, std::optional n) { o << r << " : "; if (p) { print_ptr_or_mapfd_type(o, *p, d); } - else { - print_number(o, crab::interval_t::top()); + else if (n) { + print_number(o, n->to_interval()); } } inline std::string size_(int w) { return std::string("u") + std::to_string(w * 8); } -void print_annotated(std::ostream& o, const Call& call, std::optional& p) { +void print_annotated(std::ostream& o, const Call& call, std::optional& p, + std::optional& n) { o << " "; - print_register(o, Reg{(uint8_t)R0_RETURN_VALUE}, p, std::nullopt); + print_register(o, Reg{(uint8_t)R0_RETURN_VALUE}, p, std::nullopt, n); o << " = " << call.name << ":" << call.func << "(...)\n"; } void print_annotated(std::ostream& o, const Bin& b, std::optional& p, - std::optional& d) { + std::optional& d, std::optional& n) { o << " "; - print_register(o, b.dst, p, d); + print_register(o, b.dst, p, d, n); o << " " << b.op << "= " << b.v << "\n"; } void print_annotated(std::ostream& o, const LoadMapFd& u, std::optional& p) { o << " "; - print_register(o, u.dst, p, std::nullopt); + print_register(o, u.dst, p, std::nullopt, std::nullopt); o << " = map_fd " << u.mapfd << "\n"; } void print_annotated(std::ostream& o, const Mem& b, std::optional& p, - std::optional& d) { + std::optional& d, std::optional& n) { o << " "; - print_register(o, std::get(b.value), p, d); + print_register(o, std::get(b.value), p, d, n); o << " = "; std::string sign = b.access.offset < 0 ? " - " : " + "; int offset = std::abs(b.access.offset); diff --git a/src/crab/type_ostream.hpp b/src/crab/type_ostream.hpp index 554a14e8e..0040f4ed2 100644 --- a/src/crab/type_ostream.hpp +++ b/src/crab/type_ostream.hpp @@ -12,8 +12,8 @@ void print_ptr_or_mapfd_type(std::ostream&, const crab::ptr_or_mapfd_t&, std::optional); void print_number(std::ostream&, crab::interval_t); void print_ptr_type(std::ostream&, const crab::ptr_or_mapfd_t& ptr, std::optional); -void print_register(std::ostream& o, const Reg& r, const std::optional& p, std::optional); -void print_annotated(std::ostream& o, const Call& call, std::optional& p); -void print_annotated(std::ostream& o, const Bin& b, std::optional& p, std::optional&); +void print_register(std::ostream& o, const Reg& r, const std::optional& p, std::optional, std::optional); +void print_annotated(std::ostream& o, const Call& call, std::optional& p, std::optional&); +void print_annotated(std::ostream& o, const Bin& b, std::optional& p, std::optional&, std::optional&); void print_annotated(std::ostream& o, const LoadMapFd& u, std::optional& p); -void print_annotated(std::ostream& o, const Mem& b, std::optional& p, std::optional&); +void print_annotated(std::ostream& o, const Mem& b, std::optional& p, std::optional&, std::optional&); From 9eb343e655438cfa46d36b7b183169b956f86c04 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Wed, 13 Sep 2023 00:01:11 -0400 Subject: [PATCH 125/373] Fixes and refactor in Bin, Call, Assume, and Mem transformers Better handling of certain Bin operations: LSH, LSHR, ASHR, AND; In Call, added support for removing overlap from the stack before storing numbers in Call; Fixes in Assume, w.r.t., GT, GE, LT, LE, NEQ Some fixes in Bin and Assume that caused wrong modification of intervals; In Mem, fixing Load from stack when reading a part of number, rather than the whole number; In Mem, some additions/fixes related to separate handling of ctx, packet, and shared when loading a number; Signed-off-by: Ameer Hamza --- src/crab/interval_prop_domain.cpp | 429 +++++++++++++++++++++--------- src/crab/interval_prop_domain.hpp | 26 +- src/crab/type_domain.cpp | 77 +++--- src/crab/type_domain.hpp | 4 +- 4 files changed, 357 insertions(+), 179 deletions(-) diff --git a/src/crab/interval_prop_domain.cpp b/src/crab/interval_prop_domain.cpp index 5ccc549c1..9a9994af4 100644 --- a/src/crab/interval_prop_domain.cpp +++ b/src/crab/interval_prop_domain.cpp @@ -86,20 +86,10 @@ void registers_cp_state_t::adjust_bb_for_registers(location_t loc) { } void registers_cp_state_t::operator-=(register_t var) { - if (is_bottom()) { - return; - } + if (is_bottom()) return; m_cur_def[var] = nullptr; } -void registers_cp_state_t::print_all_register_types() const { - std::cout << "\tinterval types: {\n"; - for (auto const& kv : *m_interval_env) { - std::cout << "\t\t" << kv.first << " : " << kv.second.to_interval() << "\n"; - } - std::cout << "\t}\n"; -} - bool stack_cp_state_t::is_bottom() const { return m_is_bottom; } @@ -418,32 +408,33 @@ void interval_prop_domain_t::operator()(const Packet &u, location_t loc, int pri m_registers_interval_values.insert(r0, r0_with_loc, interval_t::top()); } -void interval_prop_domain_t::assume_lt(bool strict, interval_t&& left_interval, - interval_t&& right_interval, register_t left, Value right, location_t loc) { +void interval_prop_domain_t::assume_lt(bool strict, + interval_t&& left_interval, interval_t&& right_interval, + interval_t&& left_interval_orig, interval_t&& right_interval_orig, + register_t left, Value right, location_t loc) { auto reg_with_loc_left = reg_with_loc_t(left, loc); auto rlb = right_interval.lb(); auto rub = right_interval.ub(); auto llb = left_interval.lb(); auto lub = left_interval.ub(); + auto rlb_orig = right_interval_orig.lb(); + auto rub_orig = right_interval_orig.ub(); + auto llb_orig = left_interval_orig.lb(); + auto lub_orig = left_interval_orig.ub(); - auto ub = strict ? (right_interval.lb() - number_t{1}) : right_interval.lb(); - if (lub >= rlb && llb < rlb) { - auto interval_to_insert = interval_t(llb, strict ? rlb - number_t{1} : rlb); - m_registers_interval_values.insert(left, reg_with_loc_left, interval_to_insert); - } - else if (right_interval <= left_interval && strict ? llb < rlb : llb <= rlb) { - auto interval_to_insert = interval_t(llb, strict ? rlb - number_t{1} : rlb); + if (strict ? llb < rlb : llb <= rlb && lub >= rlb) { + auto interval_to_insert = interval_t(llb_orig, strict ? rlb - number_t{1} : rlb); m_registers_interval_values.insert(left, reg_with_loc_left, interval_to_insert); } else if (left_interval <= right_interval && strict ? lub < rub : lub <= rub && std::holds_alternative(right)) { auto right_reg = std::get(right).v; auto reg_with_loc_right = reg_with_loc_t(right_reg, loc); - auto interval_to_insert = interval_t(strict ? lub + number_t{1} : lub, rub); + auto interval_to_insert = interval_t(strict ? lub + number_t{1} : lub, rub_orig); m_registers_interval_values.insert(right_reg, reg_with_loc_right, interval_to_insert); } - else if (lub > rub && strict ? llb < rub : llb <= rub) { - auto interval_to_insert_left = interval_t(llb, strict ? rub - number_t{1} : rub); + else if (lub >= rub && strict ? llb < rub : llb <= rub) { + auto interval_to_insert_left = interval_t(llb_orig, strict ? rub - number_t{1} : rub); m_registers_interval_values.insert(left, reg_with_loc_left, interval_to_insert_left); // this is only one way to resolve this scenario, i.e. set right to singleton value (rub) // and set left to the rest of the interval < (or <=) of right @@ -460,31 +451,33 @@ void interval_prop_domain_t::assume_lt(bool strict, interval_t&& left_interval, } } -void interval_prop_domain_t::assume_gt(bool strict, interval_t&& left_interval, - interval_t&& right_interval, register_t left, Value right, location_t loc) { +void interval_prop_domain_t::assume_gt(bool strict, + interval_t&& left_interval, interval_t&& right_interval, + interval_t&& left_interval_orig, interval_t&& right_interval_orig, + register_t left, Value right, location_t loc) { auto reg_with_loc_left = reg_with_loc_t(left, loc); auto rlb = right_interval.lb(); auto rub = right_interval.ub(); auto llb = left_interval.lb(); auto lub = left_interval.ub(); + auto rlb_orig = right_interval_orig.lb(); + auto rub_orig = right_interval_orig.ub(); + auto llb_orig = left_interval_orig.lb(); + auto lub_orig = left_interval_orig.ub(); - if (llb <= rub && lub > rub) { - auto interval_to_insert = interval_t(strict ? rub + number_t{1} : rub, lub); - m_registers_interval_values.insert(left, reg_with_loc_left, interval_to_insert); - } - else if (right_interval <= left_interval && strict ? lub > rub : lub >= rub) { - auto interval_to_insert = interval_t(strict ? rub + number_t{1} : rub, lub); + if (strict ? lub > rub : lub >= rub && llb <= rub) { + auto interval_to_insert = interval_t(strict ? rub + number_t{1} : rub, lub_orig); m_registers_interval_values.insert(left, reg_with_loc_left, interval_to_insert); } else if (left_interval <= right_interval && strict ? llb > rlb : llb >= rlb && std::holds_alternative(right)) { auto right_reg = std::get(right).v; auto reg_with_loc_right = reg_with_loc_t(right_reg, loc); - auto interval_to_insert = interval_t(rlb, strict ? llb - number_t{1} : llb); + auto interval_to_insert = interval_t(rlb_orig, strict ? llb - number_t{1} : llb); m_registers_interval_values.insert(right_reg, reg_with_loc_right, interval_to_insert); } - else if (llb < rlb && strict ? lub > rlb : lub >= rlb) { - auto interval_to_insert_left = interval_t(strict ? rlb + number_t{1} : rlb, lub); + else if (llb <= rlb && strict ? lub > rlb : lub >= rlb) { + auto interval_to_insert_left = interval_t(strict ? rlb + number_t{1} : rlb, lub_orig); m_registers_interval_values.insert(left, reg_with_loc_left, interval_to_insert_left); // this is only one way to resolve this scenario, i.e. set right to singleton value (rlb) // and set left to the rest of the interval > (or >=) of right @@ -502,8 +495,9 @@ void interval_prop_domain_t::assume_gt(bool strict, interval_t&& left_interval, } void interval_prop_domain_t::assume_gt_and_lt(bool is64, bool strict, bool is_lt, - interval_t&& left_interval, interval_t&& right_interval, register_t left, - Value right, location_t loc) { + interval_t&& left_interval, interval_t&& right_interval, + interval_t&& left_interval_orig, interval_t&& right_interval_orig, + register_t left, Value right, location_t loc) { auto llb = left_interval.lb(); auto lub = left_interval.ub(); @@ -512,32 +506,39 @@ void interval_prop_domain_t::assume_gt_and_lt(bool is64, bool strict, bool is_lt if (!is_lt && (strict ? (lub <= rlb) : (lub < rlb))) { // Left unsigned interval is lower than right unsigned interval. m_registers_interval_values.set_to_bottom(); + return; } else if (is_lt && (strict ? (llb >= rub) : (llb > rub))) { // Left unsigned interval is higher than right unsigned interval. m_registers_interval_values.set_to_bottom(); + return; } if (is_lt && (strict ? (lub < rlb) : (lub <= rlb))) { // Left unsigned interval is lower than right unsigned interval. // TODO: verify if setting to top is the correct equivalent of returning linear cst true m_registers_interval_values.set_to_top(); + return; } else if (!is_lt && (strict ? (llb > rub) : (llb >= rub))) { // Left unsigned interval is higher than right unsigned interval. m_registers_interval_values.set_to_top(); + return; } if (is_lt) - assume_lt(strict, std::move(left_interval), std::move(right_interval), left, right, loc); + assume_lt(strict, std::move(left_interval), std::move(right_interval), + std::move(left_interval_orig), std::move(right_interval_orig), left, right, loc); else - assume_gt(strict, std::move(left_interval), std::move(right_interval), left, right, loc); + assume_gt(strict, std::move(left_interval), std::move(right_interval), + std::move(left_interval_orig), std::move(right_interval_orig), left, right, loc); } void interval_prop_domain_t::assume_unsigned_cst_interval(Condition::Op op, bool is64, - interval_t&& left_interval, interval_t&& right_interval, register_t left, Value right, - location_t loc) { + interval_t&& left_interval, interval_t&& right_interval, + interval_t&& left_interval_orig, interval_t&& right_interval_orig, + register_t left, Value right, location_t loc) { for (interval_t* interval : {&left_interval, &right_interval}) { - if (!(*interval <= interval_t::unsigned_int(true))) { - *interval = interval->truncate_to_uint(true); + if (!(*interval <= interval_t::unsigned_int(is64))) { + *interval = interval->truncate_to_uint(is64); } } @@ -564,12 +565,13 @@ void interval_prop_domain_t::assume_unsigned_cst_interval(Condition::Op op, bool bool strict = op == Condition::Op::LT || op == Condition::Op::GT; assume_gt_and_lt(is64, strict, is_lt, std::move(left_interval), std::move(right_interval), - left, right, loc); + std::move(left_interval_orig), std::move(right_interval_orig), left, right, loc); } void interval_prop_domain_t::assume_signed_cst_interval(Condition::Op op, bool is64, - interval_t&& left_interval, interval_t&& right_interval, register_t left, Value right, - location_t loc) { + interval_t&& left_interval, interval_t&& right_interval, + interval_t&& left_interval_orig, interval_t&& right_interval_orig, + register_t left, Value right, location_t loc) { for (interval_t* interval : {&left_interval, &right_interval}) { if (!(*interval <= interval_t::signed_int(is64))) { @@ -592,6 +594,7 @@ void interval_prop_domain_t::assume_signed_cst_interval(Condition::Op op, bool i bool strict = op == Condition::Op::SLT || op == Condition::Op::SGT; assume_gt_and_lt(is64, strict, is_lt, std::move(left_interval), std::move(right_interval), + std::move(left_interval_orig), std::move(right_interval_orig), left, right, loc); } @@ -611,11 +614,14 @@ void interval_prop_domain_t::assume_cst(Condition::Op op, bool is64, register_t } else { imm = static_cast(std::get(right).v); + right_interval = is64 ? interval_t(number_t{imm}) : interval_t(number_t{(uint64_t)imm}); } if (left_interval.is_bottom() || (is_right_reg && right_interval.is_bottom())) { - m_registers_interval_values.set_to_bottom(); return; } + interval_t left_interval_orig = left_interval; + interval_t right_interval_orig = right_interval; + switch (op) { case Op::EQ: case Op::SGE: @@ -623,8 +629,8 @@ void interval_prop_domain_t::assume_cst(Condition::Op op, bool is64, register_t case Op::SGT: case Op::SLT: { assume_signed_cst_interval(op, is64, std::move(left_interval), - is_right_reg ? std::move(right_interval) : interval_t(number_t{imm}), - left, right, loc); + std::move(right_interval), std::move(left_interval_orig), + std::move(right_interval_orig), left, right, loc); break; } case Op::SET: @@ -638,8 +644,8 @@ void interval_prop_domain_t::assume_cst(Condition::Op op, bool is64, register_t case Op::GT: case Op::LT: { assume_unsigned_cst_interval(op, is64, std::move(left_interval), - is_right_reg ? std::move(right_interval) : interval_t(number_t{(uint64_t)imm}), - left, right, loc); + std::move(right_interval), std::move(left_interval_orig), + std::move(right_interval_orig), left, right, loc); break; } } @@ -660,13 +666,14 @@ void interval_prop_domain_t::do_bin(const Bin& bin, // we skip handling in this domain is when dst is pointer and src is numerical value if (bin.op != Op::MOV && dst_ptr_or_mapfd_opt && src_interval_opt) return; // if op is MOV, - // we skip handling in this domain is when both dst and src are pointers - // when dst is not known and src is pointer - if (bin.op == Op::MOV && - ((dst_ptr_or_mapfd_opt && src_ptr_or_mapfd_opt) - || (!dst_interval_opt && src_ptr_or_mapfd_opt))) + // we skip handling in this domain is when src is pointer, + // additionally, we forget the dst pointer + if (bin.op == Op::MOV && src_ptr_or_mapfd_opt) { + m_registers_interval_values -= bin.dst.v; return; + } + int finite_width = bin.is64 ? 64 : 32; uint64_t imm = std::holds_alternative(bin.v) ? std::get(bin.v).v : 0; interval_t src_interval = interval_t::bottom(), dst_interval = interval_t::bottom(); if (src_interval_opt) src_interval = std::move(src_interval_opt.value()); @@ -677,9 +684,10 @@ void interval_prop_domain_t::do_bin(const Bin& bin, { // ra = b case Op::MOV: { - if (src_interval_opt) + if (src_interval_opt) { dst_interval = src_interval; - else if (dst_interval_opt) { + } + else { m_registers_interval_values -= bin.dst.v; return; } @@ -730,24 +738,166 @@ void interval_prop_domain_t::do_bin(const Bin& bin, } // ra &= b case Op::AND: { + if ((int32_t)imm > 0) { + dst_interval = interval_t(number_t{0}, number_t(static_cast(imm))); + break; + } + if (!(dst_interval <= interval_t::unsigned_int(bin.is64))) { + // TODO: This is only based on observation, need to verify this + // This is done because we do not track uvalues and svalues separately + // however, current implementation of eBPF using zoneCrab does + // this operation mimics uvalue being not set (i.e., TOP) + dst_interval = interval_t::top(); + } + if (!(src_interval <= interval_t::unsigned_int(bin.is64))) { + // likewise explanation as above + src_interval = interval_t::top(); + } + if (imm != 0) { + src_interval = interval_t(number_t{(uint64_t)imm}); + } dst_interval = dst_interval.And(src_interval); - if ((int32_t)imm > 0) - dst_interval = interval_t(number_t(0), number_t(static_cast(imm))); break; } // ra <<= b case Op::LSH: { - dst_interval = dst_interval.Shl(src_interval); + if (imm == 0) { + if (std::optional sn = src_interval.singleton()) { + // TODO: verify if this is correct + if (bin.is64) { + uint64_t imm_val = sn->cast_to_uint64() & 63; + if (!(imm_val <= INT32_MAX)) return; + imm = (int32_t)imm_val; + } + else { + uint32_t imm_val = sn->cast_to_uint32() & 31; + if (!(imm_val <= INT32_MAX)) return; + imm = (int32_t)imm_val; + } + } + } + if (imm != 0) { + // The BPF ISA requires masking the imm. + imm &= finite_width - 1; + if (!(dst_interval <= interval_t::unsigned_int(bin.is64))) { + // This is non-standard, but done to mimic uvalue being not set (i.e., TOP) + dst_interval = interval_t::top(); + } + if (dst_interval.finite_size()) { + number_t lb = dst_interval.lb().number().value(); + number_t ub = dst_interval.ub().number().value(); + uint64_t lb_n = lb.cast_to_uint64(); + uint64_t ub_n = ub.cast_to_uint64(); + uint64_t uint_max = (finite_width == 64) ? UINT64_MAX : UINT32_MAX; + if ((lb_n >> (finite_width - imm)) != (ub_n >> (finite_width - imm))) { + // The bits that will be shifted out to the left are different, + // which means all combinations of remaining bits are possible. + lb_n = 0; + ub_n = (uint_max << imm) & uint_max; + } else { + // The bits that will be shifted out to the left are identical + // for all values in the interval, so we can safely shift left + // to get a new interval. + lb_n = (lb_n << imm) & uint_max; + ub_n = (ub_n << imm) & uint_max; + } + dst_interval = interval_t(number_t{lb_n}, number_t{ub_n}); + } + } + else { + dst_interval = interval_t::top(); + } break; } // ra >>= b case Op::RSH: { - dst_interval = dst_interval.LShr(src_interval); + if (imm == 0) { + if (std::optional sn = src_interval.singleton()) { + // TODO: verify if this is correct + if (bin.is64) { + uint64_t imm_val = sn->cast_to_uint64() & 63; + if (!(imm_val <= INT32_MAX)) return; + imm = (int32_t)imm_val; + } + else { + uint32_t imm_val = sn->cast_to_uint32() & 31; + if (!(imm_val <= INT32_MAX)) return; + imm = (int32_t)imm_val; + } + } + } + if (imm != 0) { + imm &= finite_width - 1; + number_t lb_n{0}; + number_t ub_n{UINT64_MAX >> imm}; + if (!(dst_interval <= interval_t::unsigned_int(bin.is64))) { + // This is done because we do not track uvalues and svalues separately + // however, current implementation of eBPF using zoneCrab does + // this operation mimics uvalue being not set (i.e., TOP) + dst_interval = interval_t::top(); + } + if (dst_interval.finite_size()) { + number_t lb = dst_interval.lb().number().value(); + number_t ub = dst_interval.ub().number().value(); + if (finite_width == 64) { + lb_n = lb.cast_to_uint64() >> imm; + ub_n = ub.cast_to_uint64() >> imm; + } else { + number_t lb_w = lb.cast_to_signed_finite_width(finite_width); + number_t ub_w = ub.cast_to_signed_finite_width(finite_width); + lb_n = lb_w.cast_to_uint32() >> imm; + ub_n = ub_w.cast_to_uint32() >> imm; + + // The interval must be valid since a signed range crossing 0 + // was earlier converted to a full unsigned range. + assert(lb_n <= ub_n); + } + } + if ((int64_t)ub_n >= (int64_t)lb_n) { + dst_interval = interval_t(number_t{lb_n}, number_t{ub_n}); + } else { + dst_interval = interval_t::top(); + } + } + else { + dst_interval = interval_t::top(); + } break; } // ra >>>= b case Op::ARSH: { - dst_interval = dst_interval.AShr(src_interval); + for (interval_t* interval : {&dst_interval, &src_interval}) { + if (!(*interval <= interval_t::signed_int(finite_width == 64))) { + *interval = interval->truncate_to_sint(finite_width == 64); + } + } + + if (auto sn = src_interval.singleton()) { + // The BPF ISA requires masking the imm. + int64_t imm = sn->cast_to_sint64() & (finite_width - 1); + + int64_t lb_n = INT64_MIN >> imm; + int64_t ub_n = INT64_MAX >> imm; + if (dst_interval.finite_size()) { + number_t lb = dst_interval.lb().number().value(); + number_t ub = dst_interval.ub().number().value(); + if (finite_width == 64) { + lb_n = lb.cast_to_sint64() >> imm; + ub_n = ub.cast_to_sint64() >> imm; + } else { + number_t lb_w = lb.cast_to_signed_finite_width(finite_width) >> (int)imm; + number_t ub_w = ub.cast_to_signed_finite_width(finite_width) >> (int)imm; + if (lb_w.cast_to_uint32() <= ub_w.cast_to_uint32()) { + lb_n = lb_w.cast_to_uint32(); + ub_n = ub_w.cast_to_uint32(); + } + } + } + dst_interval = interval_t(number_t{lb_n}, number_t{ub_n}); + } + else { + dst_interval = interval_t::top(); + } break; } // ra ^= b @@ -756,7 +906,7 @@ void interval_prop_domain_t::do_bin(const Bin& bin, break; } default: { - dst_interval = interval_t::top(); + dst_interval = interval_t::bottom(); break; } } @@ -765,99 +915,104 @@ void interval_prop_domain_t::do_bin(const Bin& bin, void interval_prop_domain_t::do_load(const Mem& b, const Reg& target_reg, - std::optional basereg_type, std::optional targetreg_type, - location_t loc) { + std::optional basereg_type, bool load_in_region, location_t loc) { + if (!basereg_type) { m_registers_interval_values -= target_reg.v; return; } - auto basereg_ptr_or_mapfd_type = basereg_type.value(); - int offset = b.access.offset; + auto reg_with_loc = reg_with_loc_t(target_reg.v, loc); + // we check if we already loaded a pointer from ctx or stack in region domain, + // we then do not store a number + if (load_in_region) { + m_registers_interval_values -= target_reg.v; + return; + } int width = b.access.width; + int offset = b.access.offset; + auto basereg_ptr_or_mapfd_type = basereg_type.value(); - auto reg_with_loc = reg_with_loc_t(target_reg.v, loc); - if (std::holds_alternative(basereg_ptr_or_mapfd_type)) { - auto p_with_off = std::get(basereg_ptr_or_mapfd_type); - if (p_with_off.get_region() == crab::region_t::T_STACK) { - auto ptr_offset = p_with_off.get_offset(); - auto load_at_interval = ptr_offset.to_interval() + interval_t{number_t(static_cast(offset))}; - auto load_at_singleton = load_at_interval.singleton(); - if (load_at_singleton) { - auto load_at = load_at_singleton.value(); - auto loaded = m_stack_slots_interval_values.find((uint64_t)load_at); - if (loaded) { - auto loaded_cells = loaded.value(); - m_registers_interval_values.insert(target_reg.v, reg_with_loc, - loaded_cells.first.to_interval()); - return; - } - } - auto load_at_lb_opt = load_at_interval.lb().number(); - auto load_at_ub_opt = load_at_interval.ub().number(); - if (!load_at_lb_opt || !load_at_ub_opt) { - m_registers_interval_values -= target_reg.v; - //std::cout << "type error: missing offset information\n"; - m_errors.push_back("missing offset information"); - return; - } - auto load_at_lb = load_at_lb_opt.value(); - auto load_at_ub = load_at_ub_opt.value(); - auto start = (uint64_t)load_at_lb; - auto width_to_check = (int)(load_at_ub+number_t(width)-load_at_lb); + if (is_ctx_ptr(basereg_type)) { + m_registers_interval_values.insert(target_reg.v, reg_with_loc, interval_t::top()); + return; + } + if (is_packet_ptr(basereg_type) || is_shared_ptr(basereg_type)) { + interval_t to_insert = interval_t::top(); + if (width == 1) to_insert = interval_t(number_t{0}, number_t{UINT8_MAX}); + else if (width == 2) to_insert = interval_t(number_t{0}, number_t{UINT16_MAX}); + m_registers_interval_values.insert(target_reg.v, reg_with_loc, to_insert); + return; + } - if (m_stack_slots_interval_values.all_numeric(start, width_to_check)) { + if (is_stack_ptr(basereg_type)) { + auto ptr_with_off = std::get(basereg_ptr_or_mapfd_type); + auto p_offset = ptr_with_off.get_offset(); + auto load_at = p_offset.to_interval() + interval_t(number_t{offset}); + uint64_t start_offset = 0; + if (auto load_at_singleton = load_at.singleton()) { + start_offset = (uint64_t)(*load_at_singleton); + if (auto loaded = m_stack_slots_interval_values.find(start_offset)) { m_registers_interval_values.insert(target_reg.v, reg_with_loc, - interval_t::top()); + (*loaded).first.to_interval()); + return; } - else { - m_registers_interval_values -= target_reg.v; + } + else { + auto load_at_lb = load_at.lb(); + auto load_at_ub = load_at.ub(); + if (auto finite_size = load_at.finite_size()) { + if (auto load_at_lb = load_at.lb().number()) { + start_offset = (uint64_t)(*load_at_lb); + width = (int)(*finite_size + number_t{width}); + } } + } + if (m_stack_slots_interval_values.all_numeric(start_offset, width)) { + m_registers_interval_values.insert(target_reg.v, reg_with_loc, + interval_t::top()); return; } } - // we check targetreg_type because in case we already loaded a pointer from ctx, - // we then do not store a number - if (!targetreg_type) { // we are loading from ctx, packet or shared - m_registers_interval_values.insert(target_reg.v, reg_with_loc, interval_t::top()); - } - else { - m_registers_interval_values -= target_reg.v; - } + m_registers_interval_values -= target_reg.v; } -void interval_prop_domain_t::do_mem_store(const Mem& b, const Reg& target_reg, - std::optional basereg_type) { +void interval_prop_domain_t::do_mem_store(const Mem& b, std::optional basereg_type) { int offset = b.access.offset; int width = b.access.width; - if (!basereg_type) { + if (!is_stack_ptr(basereg_type)) { + // we only store for stack pointers return; } - auto basereg_ptr_or_mapfd_type = basereg_type.value(); - auto targetreg_type = m_registers_interval_values.find(target_reg.v); - if (std::holds_alternative(basereg_ptr_or_mapfd_type)) { - auto basereg_ptr_with_off_type = std::get(basereg_ptr_or_mapfd_type); - auto offset_singleton = basereg_ptr_with_off_type.get_offset().to_interval().singleton(); - if (!offset_singleton) { - //std::cout << "type error: doing a store with unknown offset\n"; - m_errors.push_back("doing a store with unknown offset"); - return; - } - auto store_at = (uint64_t)offset_singleton.value() + (uint64_t)offset; - if (basereg_ptr_with_off_type.get_region() == crab::region_t::T_STACK) { - auto overlapping_cells - = m_stack_slots_interval_values.find_overlapping_cells(store_at, width); - m_stack_slots_interval_values.remove_overlap(overlapping_cells, store_at, width); - - // if targetreg_type is empty, we are storing a pointer - if (!targetreg_type) return; - auto type_to_store = targetreg_type.value(); - m_stack_slots_interval_values.store(store_at, type_to_store, width); + auto basereg_ptr_with_off_type = std::get(*basereg_type); + auto offset_singleton = basereg_ptr_with_off_type.get_offset().to_interval().singleton(); + if (!offset_singleton) { + //std::cout << "type error: doing a store with unknown offset\n"; + m_errors.push_back("doing a store with unknown offset"); + return; + } + auto store_at = (uint64_t)(*offset_singleton + offset); + auto overlapping_cells = m_stack_slots_interval_values.find_overlapping_cells(store_at, width); + m_stack_slots_interval_values.remove_overlap(overlapping_cells, store_at, width); + + std::optional targetreg_type; + if (std::holds_alternative(b.value)) { + auto target_reg = std::get(b.value); + if (auto targetreg_mock_interval = m_registers_interval_values.find(target_reg.v)) { + targetreg_type = targetreg_mock_interval->to_interval(); } } - else {} + else { + auto imm = static_cast(std::get(b.value).v); + targetreg_type = interval_t(number_t{imm}); + } + + // if targetreg_type is empty, we are storing a pointer + // else, we store a number + if (targetreg_type) + m_stack_slots_interval_values.store(store_at, *targetreg_type, width); } void interval_prop_domain_t::set_require_check(check_require_func_t f) {} @@ -878,4 +1033,14 @@ bool interval_prop_domain_t::all_numeric_in_stack(uint64_t start_loc, int width) return m_stack_slots_interval_values.all_numeric(start_loc, width); } +std::vector interval_prop_domain_t::find_overlapping_cells_in_stack(uint64_t start_loc, + int width) const { + return m_stack_slots_interval_values.find_overlapping_cells(start_loc, width); +} + +void interval_prop_domain_t::remove_overlap_in_stack(const std::vector& overlap, + uint64_t start_loc, int width) { + m_stack_slots_interval_values.remove_overlap(overlap, start_loc, width); +} + } // namespace crab diff --git a/src/crab/interval_prop_domain.hpp b/src/crab/interval_prop_domain.hpp index 6e87513b7..844ecbbdf 100644 --- a/src/crab/interval_prop_domain.hpp +++ b/src/crab/interval_prop_domain.hpp @@ -43,7 +43,6 @@ class registers_cp_state_t { bool is_bottom = false) : m_interval_env(interval_env), m_is_bottom(is_bottom) {} void adjust_bb_for_registers(location_t); - void print_all_register_types() const; }; using interval_cells_t = std::pair; // intervals with width @@ -144,28 +143,31 @@ class interval_prop_domain_t final { string_invariant to_set(); void set_require_check(check_require_func_t f); - void do_load(const Mem&, const Reg&, std::optional, - std::optional, location_t); - void do_mem_store(const Mem&, const Reg&, std::optional); + void do_load(const Mem&, const Reg&, std::optional, bool, location_t); + void do_mem_store(const Mem&, std::optional); void do_call(const Call&, const interval_values_stack_t&, location_t); void do_bin(const Bin&, const std::optional&, const std::optional&, const std::optional&, const std::optional&, const interval_t&, location_t); - void assume_unsigned_cst_interval(Condition::Op, bool, interval_t&&, interval_t&&, register_t, - Value, location_t); - void assume_signed_cst_interval(Condition::Op, bool, interval_t&&, interval_t&&, register_t, - Value, location_t); + void assume_unsigned_cst_interval(Condition::Op, bool, interval_t&&, interval_t&&, + interval_t&&, interval_t&&, register_t, Value, location_t); + void assume_signed_cst_interval(Condition::Op, bool, interval_t&&, interval_t&&, + interval_t&&, interval_t&&, register_t, Value, location_t); void assume_cst(Condition::Op, bool, register_t, Value, location_t); - void assume_lt(bool, interval_t&&, interval_t&&, register_t, Value, location_t); - void assume_gt(bool, interval_t&&, interval_t&&, register_t, Value, location_t); - void assume_gt_and_lt(bool, bool, bool, interval_t&&, interval_t&&, register_t, - Value, location_t); + void assume_lt(bool, interval_t&&, interval_t&&, interval_t&&, interval_t&&, + register_t, Value, location_t); + void assume_gt(bool, interval_t&&, interval_t&&, interval_t&&, interval_t&&, + register_t, Value, location_t); + void assume_gt_and_lt(bool, bool, bool, interval_t&&, interval_t&&, + interval_t&&, interval_t&&, register_t, Value, location_t); std::optional find_interval_value(register_t) const; std::optional find_interval_at_loc(const reg_with_loc_t reg) const; std::optional find_in_stack(uint64_t) const; void adjust_bb_for_types(location_t); std::vector get_stack_keys() const; bool all_numeric_in_stack(uint64_t, int) const; + std::vector find_overlapping_cells_in_stack(uint64_t, int) const; + void remove_overlap_in_stack(const std::vector&, uint64_t, int); [[nodiscard]] std::vector& get_errors() { return m_errors; } void reset_errors() { m_errors.clear(); } }; // end interval_prop_domain_t diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 77a6e3ed3..a06bee2a4 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -129,23 +129,27 @@ void type_domain_t::operator()(const Call& u, location_t loc, int print) { auto maybe_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(param.mem.v); auto maybe_width_interval = m_interval.find_interval_value(param.size.v); if (!maybe_ptr_or_mapfd || !maybe_width_interval) continue; - auto ptr_or_mapfd = maybe_ptr_or_mapfd.value(); - auto width_interval = maybe_width_interval->to_interval(); - if (std::holds_alternative(ptr_or_mapfd)) { - auto ptr_with_off = std::get(ptr_or_mapfd); - if (ptr_with_off.get_region() == region_t::T_STACK) { - auto offset_singleton = ptr_with_off.get_offset().to_interval().singleton(); - if (!offset_singleton) { - //std::cout << "type error: storing at an unknown offset in stack\n"; - m_errors.push_back("storing at an unknown offset in stack"); - continue; - } - // TODO: forget the stack at [offset, offset+width] - auto offset = (uint64_t)offset_singleton.value(); - if (auto single_width = width_interval.singleton(); single_width) { - int width = (int)single_width.value(); - stack_values[offset] = std::make_pair(interval_t::top(), width); - } + if (is_stack_ptr(maybe_ptr_or_mapfd)) { + auto ptr_with_off = std::get(*maybe_ptr_or_mapfd); + auto width_interval = maybe_width_interval->to_interval(); + + auto offset_singleton = ptr_with_off.get_offset().to_interval().singleton(); + if (!offset_singleton) { + //std::cout << "type error: storing at an unknown offset in stack\n"; + m_errors.push_back("storing at an unknown offset in stack"); + continue; + } + auto offset = (uint64_t)offset_singleton.value(); + if (auto single_width = width_interval.singleton()) { + int width = (int)single_width.value(); + + // Removing overlapping cells + // TODO: do it for all domains + auto overlapping_cells + = m_interval.find_overlapping_cells_in_stack(offset, width); + m_interval.remove_overlap_in_stack(overlapping_cells, offset, width); + + stack_values[offset] = std::make_pair(interval_t::top(), width); } } } @@ -342,10 +346,6 @@ type_domain_t type_domain_t::setup_entry() { void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { if (is_bottom()) return; - auto dst_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(bin.dst.v); - auto dst_interval = m_interval.find_interval_value(bin.dst.v); - assert(!dst_ptr_or_mapfd || !dst_interval); - std::optional src_ptr_or_mapfd; std::optional src_interval; @@ -367,7 +367,13 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { } src_interval = interval_t{number_t{imm}}; } - //assert(!src_ptr_or_mapfd || !src_interval); + assert(!src_ptr_or_mapfd || !src_interval); + + auto dst_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(bin.dst.v); + auto dst_mock_interval = m_interval.find_interval_value(bin.dst.v); + auto dst_interval = dst_mock_interval ? dst_mock_interval->to_interval() : + std::optional(); + assert(!dst_ptr_or_mapfd.has_value() || !dst_interval.has_value()); using Op = Bin::Op; // for all operations except mov, add, sub, the src and dst should be numbers @@ -384,30 +390,34 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { interval_t subtracted_reg = m_region.do_bin(bin, src_interval, src_ptr_or_mapfd, dst_ptr_or_mapfd, loc); interval_t subtracted_off = - m_offset.do_bin(bin, src_interval, interval_t::top(), src_ptr_or_mapfd, dst_ptr_or_mapfd, loc); + m_offset.do_bin(bin, src_interval, dst_interval, src_ptr_or_mapfd, dst_ptr_or_mapfd, loc); auto subtracted = subtracted_reg.is_bottom() ? subtracted_off : subtracted_reg; - m_interval.do_bin(bin, src_interval, interval_t::top(), src_ptr_or_mapfd, dst_ptr_or_mapfd, + m_interval.do_bin(bin, src_interval, dst_interval, src_ptr_or_mapfd, dst_ptr_or_mapfd, subtracted, loc); } void type_domain_t::do_load(const Mem& b, const Reg& target_reg, bool unknown_ptr, - std::optional basereg_opt, location_t loc, int print) { + std::optional basereg_opt, location_t loc) { m_region.do_load(b, target_reg, unknown_ptr, loc); m_offset.do_load(b, target_reg, basereg_opt, loc); - m_interval.do_load(b, target_reg, basereg_opt, std::nullopt, loc); + // TODO: replace with a bool value returned from region do_load + auto load_in_region = m_region.find_ptr_or_mapfd_type(target_reg.v).has_value(); + m_interval.do_load(b, target_reg, basereg_opt, load_in_region, loc); } void type_domain_t::do_mem_store(const Mem& b, std::optional target_opt, - std::optional& basereg_opt, location_t loc, int print) { + std::optional& basereg_opt, location_t loc) { m_region.do_mem_store(b, loc); - m_interval.do_mem_store(b, get(b.value), basereg_opt); + m_interval.do_mem_store(b, basereg_opt); + // TODO: replace target_opt with a bool value representing whether we have a packet pointer, + // because that is the case target_opt is needed for m_offset.do_mem_store(b, target_opt, basereg_opt); } void type_domain_t::operator()(const Mem& b, location_t loc, int print) { auto basereg = b.access.basereg; - auto ptr_or_mapfd_opt = m_region.find_ptr_or_mapfd_type(basereg.v); - bool unknown_ptr = !ptr_or_mapfd_opt.has_value(); + auto base_ptr_or_mapfd_opt = m_region.find_ptr_or_mapfd_type(basereg.v); + bool unknown_ptr = !base_ptr_or_mapfd_opt.has_value(); if (unknown_ptr) { std::string s = std::to_string(static_cast(basereg.v)); m_errors.push_back( @@ -416,11 +426,11 @@ void type_domain_t::operator()(const Mem& b, location_t loc, int print) { if (std::holds_alternative(b.value)) { auto targetreg = std::get(b.value); auto targetreg_type = m_region.find_ptr_or_mapfd_type(targetreg.v); - if (b.is_load) do_load(b, targetreg, unknown_ptr, ptr_or_mapfd_opt, loc, print); - else if (!unknown_ptr) do_mem_store(b, targetreg_type, ptr_or_mapfd_opt, loc, print); + if (b.is_load) do_load(b, targetreg, unknown_ptr, base_ptr_or_mapfd_opt, loc); + else if (!unknown_ptr) do_mem_store(b, targetreg_type, base_ptr_or_mapfd_opt, loc); } else if (!unknown_ptr && !b.is_load) { - do_mem_store(b, std::nullopt, ptr_or_mapfd_opt, loc, print); + do_mem_store(b, std::nullopt, base_ptr_or_mapfd_opt, loc); } } @@ -482,6 +492,7 @@ void type_domain_t::operator()(const basic_block_t& bb, int print) { print_annotated(std::cout, *this, bb, print); return; } + // A temporary fix to avoid printing errors for multiple basic blocks m_errors.clear(); m_region.reset_errors(); diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index c6b0c23aa..5438a508e 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -94,9 +94,9 @@ class type_domain_t final { private: void do_load(const Mem&, const Reg&, bool, std::optional, - location_t, int print = 0); + location_t); void do_mem_store(const Mem&, std::optional, std::optional&, - location_t, int print = 0); + location_t); void report_type_error(std::string, location_t); void print_registers() const; void adjust_bb_for_types(location_t); From fdec8af3e5c41551b87bfd358c8ca2e0b2255aa6 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 26 Sep 2023 12:39:00 -0400 Subject: [PATCH 126/373] Implemented Un, fixed/improved multiple operations Implemented Un (unary) operations for Interval Propagation domain; Improved ValidSize, ValidStore, Addable, Comparable, TypeConstraint, LoadMapFd for Interval Prop.; Added implementation for ValidAccess for Interval Prop.; Refactored other code; Signed-off-by: Ameer Hamza --- src/crab/interval_prop_domain.cpp | 135 +++++++++++++++++++++++++++++- src/crab/interval_prop_domain.hpp | 32 +++---- src/crab/type_domain.cpp | 28 ++++++- 3 files changed, 173 insertions(+), 22 deletions(-) diff --git a/src/crab/interval_prop_domain.cpp b/src/crab/interval_prop_domain.cpp index 9a9994af4..3cbfc3305 100644 --- a/src/crab/interval_prop_domain.cpp +++ b/src/crab/interval_prop_domain.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MIT #include "crab/interval_prop_domain.hpp" +#include "boost/endian/conversion.hpp" namespace crab { @@ -359,8 +360,57 @@ interval_prop_domain_t interval_prop_domain_t::setup_entry() { } void interval_prop_domain_t::operator()(const Un& u, location_t loc, int print) { - auto reg_with_loc = reg_with_loc_t(u.dst.v, loc); - m_registers_interval_values.insert(u.dst.v, reg_with_loc, interval_t::top()); + auto swap_endianness = [&](interval_t&& v, auto input, const auto& be_or_le) { + if (std::optional n = v.singleton()) { + if (n->fits_cast_to_int64()) { + input = (decltype(input))n.value().cast_to_sint64(); + decltype(input) output = be_or_le(input); + auto reg_with_loc = reg_with_loc_t(u.dst.v, loc); + m_registers_interval_values.insert(u.dst.v, reg_with_loc, + interval_t(number_t(output))); + return; + } + } + m_registers_interval_values -= u.dst.v; + }; + + auto mock_interval = m_registers_interval_values.find(u.dst.v); + if (!mock_interval) return; + interval_t interval = mock_interval->to_interval(); + + // Swap bytes. For 64-bit types we need the weights to fit in a + // signed int64, but for smaller types we don't want sign extension, + // so we use unsigned which still fits in a signed int64. + switch (u.op) { + case Un::Op::BE16: + swap_endianness(std::move(interval), uint16_t(0), + boost::endian::native_to_big); + break; + case Un::Op::BE32: + swap_endianness(std::move(interval), uint32_t(0), + boost::endian::native_to_big); + break; + case Un::Op::BE64: + swap_endianness(std::move(interval), int64_t(0), + boost::endian::native_to_big); + break; + case Un::Op::LE16: + swap_endianness(std::move(interval), uint16_t(0), + boost::endian::native_to_little); + break; + case Un::Op::LE32: + swap_endianness(std::move(interval), uint32_t(0), + boost::endian::native_to_little); + break; + case Un::Op::LE64: + swap_endianness(std::move(interval), int64_t(0), + boost::endian::native_to_little); + break; + case Un::Op::NEG: + auto reg_with_loc = reg_with_loc_t(u.dst.v, loc); + m_registers_interval_values.insert(u.dst.v, reg_with_loc, -interval); + break; + } } void interval_prop_domain_t::operator()(const LoadMapFd &u, location_t loc, int print) { @@ -383,7 +433,6 @@ void interval_prop_domain_t::operator()(const ValidSize& s, location_t loc, int void interval_prop_domain_t::do_call(const Call& u, const interval_values_stack_t& store_in_stack, location_t loc) { - auto r0 = register_t{R0_RETURN_VALUE}; for (const auto& kv : store_in_stack) { auto key = kv.first; auto width = kv.second.second; @@ -393,6 +442,7 @@ void interval_prop_domain_t::do_call(const Call& u, const interval_values_stack_ m_stack_slots_interval_values.store(kv.first, kv.second.first.to_interval(), kv.second.second); } + auto r0 = register_t{R0_RETURN_VALUE}; if (u.is_map_lookup) { m_registers_interval_values -= r0; } @@ -402,10 +452,11 @@ void interval_prop_domain_t::do_call(const Call& u, const interval_values_stack_ } } -void interval_prop_domain_t::operator()(const Packet &u, location_t loc, int print) { +void interval_prop_domain_t::operator()(const Packet& u, location_t loc, int print) { auto r0 = register_t{R0_RETURN_VALUE}; auto r0_with_loc = reg_with_loc_t(r0, loc); m_registers_interval_values.insert(r0, r0_with_loc, interval_t::top()); + // TODO: scratch caller saved registers } void interval_prop_domain_t::assume_lt(bool strict, @@ -1015,6 +1066,82 @@ void interval_prop_domain_t::do_mem_store(const Mem& b, std::optionalto_interval().singleton()) { + if (*singleton == number_t{0}) return; + } + m_errors.push_back("Non-null number"); + } + else { + m_errors.push_back("Only pointers can be dereferenced"); + } + } + } +} + +void interval_prop_domain_t::operator()(const Undefined& u, location_t loc, int print) { + // nothing to do here +} + +void interval_prop_domain_t::operator()(const Bin& b, location_t loc, int print) { + // nothing to do here +} + +void interval_prop_domain_t::operator()(const Call&, location_t loc, int print) { + // nothing to do here +} + +void interval_prop_domain_t::operator()(const Exit&, location_t loc, int print) { + // nothing to do here +} + +void interval_prop_domain_t::operator()(const Jmp&, location_t loc, int print) { + // nothing to do here +} + +void interval_prop_domain_t::operator()(const Mem&, location_t loc, int print) { + // nothing to do here +} + +void interval_prop_domain_t::operator()(const LockAdd&, location_t loc, int print) { + // nothing to do here +} + +void interval_prop_domain_t::operator()(const Assert&, location_t loc, int print) { + // nothing to do here +} + +void interval_prop_domain_t::operator()(const Comparable&, location_t loc, int print) { + // nothing to do here +} + +void interval_prop_domain_t::operator()(const Addable&, location_t loc, int print) { + // nothing to do here +} + +void interval_prop_domain_t::operator()(const ValidStore&, location_t loc, int print) { + // nothing to do here +} + +void interval_prop_domain_t::operator()(const TypeConstraint&, location_t loc, int print) { + // nothing to do here +} + +void interval_prop_domain_t::operator()(const ValidMapKeyValue&, location_t loc, int print) { + // nothing to do here +} + +void interval_prop_domain_t::operator()(const ZeroCtxOffset&, location_t loc, int print) { + // nothing to do here +} + +void interval_prop_domain_t::operator()(const basic_block_t& bb, bool check_termination, int print) { + // nothing to do here +} void interval_prop_domain_t::set_require_check(check_require_func_t f) {} std::optional interval_prop_domain_t::find_in_stack(uint64_t key) const { diff --git a/src/crab/interval_prop_domain.hpp b/src/crab/interval_prop_domain.hpp index 844ecbbdf..ab8d0419b 100644 --- a/src/crab/interval_prop_domain.hpp +++ b/src/crab/interval_prop_domain.hpp @@ -116,27 +116,27 @@ class interval_prop_domain_t final { void operator-=(register_t reg) { m_registers_interval_values -= reg; } //// abstract transformers - void operator()(const Undefined&, location_t loc = boost::none, int print = 0) {} - void operator()(const Bin&, location_t loc = boost::none, int print = 0) {} + void operator()(const Undefined&, location_t loc = boost::none, int print = 0); + void operator()(const Bin&, location_t loc = boost::none, int print = 0); void operator()(const Un&, location_t loc = boost::none, int print = 0); void operator()(const LoadMapFd&, location_t loc = boost::none, int print = 0); - void operator()(const Call&, location_t loc = boost::none, int print = 0) {} - void operator()(const Exit&, location_t loc = boost::none, int print = 0) {} - void operator()(const Jmp&, location_t loc = boost::none, int print = 0) {} - void operator()(const Mem&, location_t loc = boost::none, int print = 0) {} + void operator()(const Call&, location_t loc = boost::none, int print = 0); + void operator()(const Exit&, location_t loc = boost::none, int print = 0); + void operator()(const Jmp&, location_t loc = boost::none, int print = 0); + void operator()(const Mem&, location_t loc = boost::none, int print = 0); void operator()(const Packet&, location_t loc = boost::none, int print = 0); - void operator()(const LockAdd&, location_t loc = boost::none, int print = 0) {} + void operator()(const LockAdd&, location_t loc = boost::none, int print = 0); void operator()(const Assume&, location_t loc = boost::none, int print = 0); - void operator()(const Assert&, location_t loc = boost::none, int print = 0) {} - void operator()(const ValidAccess&, location_t loc = boost::none, int print = 0) {} - void operator()(const Comparable&, location_t loc = boost::none, int print = 0) {} - void operator()(const Addable&, location_t loc = boost::none, int print = 0) {} - void operator()(const ValidStore&, location_t loc = boost::none, int print = 0) {} - void operator()(const TypeConstraint&, location_t loc = boost::none, int print = 0) {} + void operator()(const Assert&, location_t loc = boost::none, int print = 0); + void operator()(const ValidAccess&, location_t loc = boost::none, int print = 0); + void operator()(const Comparable&, location_t loc = boost::none, int print = 0); + void operator()(const Addable&, location_t loc = boost::none, int print = 0); + void operator()(const ValidStore&, location_t loc = boost::none, int print = 0); + void operator()(const TypeConstraint&, location_t loc = boost::none, int print = 0); void operator()(const ValidSize&, location_t loc = boost::none, int print = 0); - void operator()(const ValidMapKeyValue&, location_t loc = boost::none, int print = 0) {} - void operator()(const ZeroCtxOffset&, location_t loc = boost::none, int print = 0) {} - void operator()(const basic_block_t& bb, bool check_termination, int print = 0) {} + void operator()(const ValidMapKeyValue&, location_t loc = boost::none, int print = 0); + void operator()(const ZeroCtxOffset&, location_t loc = boost::none, int print = 0); + void operator()(const basic_block_t& bb, bool check_termination, int print = 0); void write(std::ostream& os) const; std::string domain_name() const; crab::bound_t get_instruction_count_upper_bound(); diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index a06bee2a4..124eead8e 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -104,7 +104,7 @@ void type_domain_t::operator()(const Undefined& u, location_t loc, int print) { } void type_domain_t::operator()(const Un& u, location_t loc, int print) { - /* WARNING: The operation is not implemented yet.*/ + m_interval(u, loc); } void type_domain_t::operator()(const LoadMapFd& u, location_t loc, int print) { @@ -229,9 +229,15 @@ void type_domain_t::operator()(const ValidAccess& s, location_t loc, int print) m_offset.check_valid_access(s, reg_type, interval_type, width_interval); } } + else { + m_interval(s, loc); + } } void type_domain_t::operator()(const TypeConstraint& s, location_t loc, int print) { + auto reg_type = m_region.find_ptr_or_mapfd_type(s.reg.v); + auto mock_interval_type = m_interval.find_interval_value(s.reg.v); + assert(!reg_type || !mock_interval_type); m_region(s, loc); } @@ -245,6 +251,8 @@ void type_domain_t::operator()(const Comparable& u, location_t loc, int print) { auto maybe_ptr_or_mapfd2 = m_region.find_ptr_or_mapfd_type(u.r2.v); auto maybe_num_type1 = m_interval.find_interval_value(u.r1.v); auto maybe_num_type2 = m_interval.find_interval_value(u.r2.v); + assert(!maybe_ptr_or_mapfd1 || !maybe_num_type1); + assert(!maybe_ptr_or_mapfd2 || !maybe_num_type2); if (maybe_ptr_or_mapfd1 && maybe_ptr_or_mapfd2) { if (is_mapfd_type(maybe_ptr_or_mapfd1) && is_mapfd_type(maybe_ptr_or_mapfd2)) return; if (!is_shared_ptr(maybe_ptr_or_mapfd1) @@ -261,14 +269,31 @@ void type_domain_t::operator()(const Comparable& u, location_t loc, int print) { } void type_domain_t::operator()(const Addable& u, location_t loc, int print) { + auto maybe_ptr_or_mapfd1 = m_region.find_ptr_or_mapfd_type(u.ptr.v); + auto maybe_ptr_or_mapfd2 = m_region.find_ptr_or_mapfd_type(u.num.v); + auto maybe_num_type1 = m_interval.find_interval_value(u.ptr.v); + auto maybe_num_type2 = m_interval.find_interval_value(u.num.v); + assert(!maybe_ptr_or_mapfd1 || !maybe_num_type1); + assert(!maybe_ptr_or_mapfd2 || !maybe_num_type2); m_region(u, loc); + // TODO: move the definition from the region domain to the type domain } void type_domain_t::operator()(const ValidStore& u, location_t loc, int print) { + auto maybe_ptr_or_mapfd1 = m_region.find_ptr_or_mapfd_type(u.mem.v); + auto maybe_ptr_or_mapfd2 = m_region.find_ptr_or_mapfd_type(u.val.v); + auto maybe_num_type1 = m_interval.find_interval_value(u.mem.v); + auto maybe_num_type2 = m_interval.find_interval_value(u.val.v); + assert(!maybe_ptr_or_mapfd1 || !maybe_num_type1); + assert(!maybe_ptr_or_mapfd2 || !maybe_num_type2); m_region(u, loc); + // TODO: move the definition from the region domain to the type domain } void type_domain_t::operator()(const ValidSize& u, location_t loc, int print) { + auto maybe_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(u.reg.v); + auto maybe_num_type = m_interval.find_interval_value(u.reg.v); + assert(!maybe_ptr_or_mapfd || !maybe_num_type); m_interval(u, loc); } @@ -298,7 +323,6 @@ void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int pr if (maybe_ptr_or_mapfd_basereg && maybe_mapfd) { auto mapfd = maybe_mapfd.value(); if (is_mapfd_type(maybe_mapfd)) { - // TODO: define width auto ptr_or_mapfd_basereg = maybe_ptr_or_mapfd_basereg.value(); if (std::holds_alternative(ptr_or_mapfd_basereg)) { auto ptr_with_off = std::get(ptr_or_mapfd_basereg); From 1085e8eba0cf585c9603e6cd68c0395d0634ad04 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 26 Sep 2023 13:47:46 -0400 Subject: [PATCH 127/373] Printing fix to fix unnecessary analysis Fixed printing control that was causing unnecessary analysis to run, and causing assert fails, when -v option not provided Signed-off-by: Ameer Hamza --- src/crab/interval_prop_domain.cpp | 16 ++++++---------- src/crab/interval_prop_domain.hpp | 13 ++++++++----- src/crab_verifier.cpp | 14 ++++++++------ 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/crab/interval_prop_domain.cpp b/src/crab/interval_prop_domain.cpp index 3cbfc3305..73b48f9dc 100644 --- a/src/crab/interval_prop_domain.cpp +++ b/src/crab/interval_prop_domain.cpp @@ -326,7 +326,7 @@ interval_prop_domain_t interval_prop_domain_t::operator&(const interval_prop_dom return abs; } -interval_prop_domain_t interval_prop_domain_t::widen(const interval_prop_domain_t& abs) const { +interval_prop_domain_t interval_prop_domain_t::widen(const interval_prop_domain_t& abs, bool to_constants) { /* WARNING: The operation is not implemented yet.*/ return abs; } @@ -338,13 +338,13 @@ interval_prop_domain_t interval_prop_domain_t::narrow(const interval_prop_domain void interval_prop_domain_t::write(std::ostream& os) const {} -std::string interval_prop_domain_t::domain_name() const { - return "interval_prop_domain"; +crab::bound_t interval_prop_domain_t::get_loop_count_upper_bound() { + /* WARNING: The operation is not implemented yet.*/ + return crab::bound_t{crab::number_t{0}}; } -crab::bound_t interval_prop_domain_t::get_instruction_count_upper_bound() { +void interval_prop_domain_t::initialize_loop_counter(const label_t& label) { /* WARNING: The operation is not implemented yet.*/ - return crab::bound_t{crab::number_t{0}}; } string_invariant interval_prop_domain_t::to_set() { @@ -1107,10 +1107,6 @@ void interval_prop_domain_t::operator()(const Mem&, location_t loc, int print) { // nothing to do here } -void interval_prop_domain_t::operator()(const LockAdd&, location_t loc, int print) { - // nothing to do here -} - void interval_prop_domain_t::operator()(const Assert&, location_t loc, int print) { // nothing to do here } @@ -1139,7 +1135,7 @@ void interval_prop_domain_t::operator()(const ZeroCtxOffset&, location_t loc, in // nothing to do here } -void interval_prop_domain_t::operator()(const basic_block_t& bb, bool check_termination, int print) { +void interval_prop_domain_t::operator()(const basic_block_t& bb, int print) { // nothing to do here } void interval_prop_domain_t::set_require_check(check_require_func_t f) {} diff --git a/src/crab/interval_prop_domain.hpp b/src/crab/interval_prop_domain.hpp index ab8d0419b..482e963f9 100644 --- a/src/crab/interval_prop_domain.hpp +++ b/src/crab/interval_prop_domain.hpp @@ -108,7 +108,7 @@ class interval_prop_domain_t final { // meet interval_prop_domain_t operator&(const interval_prop_domain_t& other) const; // widening - interval_prop_domain_t widen(const interval_prop_domain_t& other) const; + interval_prop_domain_t widen(const interval_prop_domain_t& other, bool); // narrowing interval_prop_domain_t narrow(const interval_prop_domain_t& other) const; //forget @@ -120,12 +120,13 @@ class interval_prop_domain_t final { void operator()(const Bin&, location_t loc = boost::none, int print = 0); void operator()(const Un&, location_t loc = boost::none, int print = 0); void operator()(const LoadMapFd&, location_t loc = boost::none, int print = 0); + void operator()(const Atomic&, location_t loc = boost::none, int print = 0) {} void operator()(const Call&, location_t loc = boost::none, int print = 0); + void operator()(const Callx&, location_t loc = boost::none, int print = 0) {} void operator()(const Exit&, location_t loc = boost::none, int print = 0); void operator()(const Jmp&, location_t loc = boost::none, int print = 0); void operator()(const Mem&, location_t loc = boost::none, int print = 0); void operator()(const Packet&, location_t loc = boost::none, int print = 0); - void operator()(const LockAdd&, location_t loc = boost::none, int print = 0); void operator()(const Assume&, location_t loc = boost::none, int print = 0); void operator()(const Assert&, location_t loc = boost::none, int print = 0); void operator()(const ValidAccess&, location_t loc = boost::none, int print = 0); @@ -136,10 +137,12 @@ class interval_prop_domain_t final { void operator()(const ValidSize&, location_t loc = boost::none, int print = 0); void operator()(const ValidMapKeyValue&, location_t loc = boost::none, int print = 0); void operator()(const ZeroCtxOffset&, location_t loc = boost::none, int print = 0); - void operator()(const basic_block_t& bb, bool check_termination, int print = 0); + void operator()(const FuncConstraint&, location_t loc = boost::none, int print = 0) {} + void operator()(const IncrementLoopCounter&, location_t loc = boost::none, int print = 0) {} + void operator()(const basic_block_t& bb, int print = 0); void write(std::ostream& os) const; - std::string domain_name() const; - crab::bound_t get_instruction_count_upper_bound(); + crab::bound_t get_loop_count_upper_bound(); + void initialize_loop_counter(const label_t&); string_invariant to_set(); void set_require_check(check_require_func_t f); diff --git a/src/crab_verifier.cpp b/src/crab_verifier.cpp index bbab52b47..4c5d904f6 100644 --- a/src/crab_verifier.cpp +++ b/src/crab_verifier.cpp @@ -128,12 +128,14 @@ static checks_db get_analysis_report(std::ostream& s, cfg_t& cfg, crab::invarian checks_db db; if (thread_local_options.abstract_domain == abstract_domain_kind::TYPE_DOMAIN) { db = generate_report_type_domain(cfg, post_invariants); - auto exit_state = post_invariants.at(label_t::exit); - // only to print ctx and stack, fix later - exit_state(cfg.get_node(label_t::exit), -1); - for (const label_t& label : cfg.sorted_labels()) { - auto post_state = post_invariants.at(label); - post_state(cfg.get_node(label), thread_local_options.print_invariants); + if (thread_local_options.print_invariants) { + auto exit_state = post_invariants.at(label_t::exit); + // only to print ctx and stack, fix later + exit_state(cfg.get_node(label_t::exit), -1); + for (const label_t& label : cfg.sorted_labels()) { + auto post_state = post_invariants.at(label); + post_state(cfg.get_node(label), 1); + } } } else { From e8289f225e512ab5b4a9d3bc3efea229b36e86ee Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Fri, 29 Sep 2023 13:27:23 -0400 Subject: [PATCH 128/373] Fixes with handling Assume Signed-off-by: Ameer Hamza --- src/crab/interval_prop_domain.cpp | 24 ++++++++++++++++++------ src/crab/interval_prop_domain.hpp | 4 ++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/crab/interval_prop_domain.cpp b/src/crab/interval_prop_domain.cpp index 73b48f9dc..2edd36bce 100644 --- a/src/crab/interval_prop_domain.cpp +++ b/src/crab/interval_prop_domain.cpp @@ -462,7 +462,7 @@ void interval_prop_domain_t::operator()(const Packet& u, location_t loc, int pri void interval_prop_domain_t::assume_lt(bool strict, interval_t&& left_interval, interval_t&& right_interval, interval_t&& left_interval_orig, interval_t&& right_interval_orig, - register_t left, Value right, location_t loc) { + register_t left, Value right, location_t loc, bool is_signed) { auto reg_with_loc_left = reg_with_loc_t(left, loc); auto rlb = right_interval.lb(); auto rub = right_interval.ub(); @@ -474,7 +474,8 @@ void interval_prop_domain_t::assume_lt(bool strict, auto lub_orig = left_interval_orig.ub(); if (strict ? llb < rlb : llb <= rlb && lub >= rlb) { - auto interval_to_insert = interval_t(llb_orig, strict ? rlb - number_t{1} : rlb); + auto lb = is_signed ? llb_orig : number_t{0}; + auto interval_to_insert = interval_t(lb, strict ? rlb - number_t{1} : rlb); m_registers_interval_values.insert(left, reg_with_loc_left, interval_to_insert); } else if (left_interval <= right_interval && strict ? lub < rub : lub <= rub && @@ -485,7 +486,8 @@ void interval_prop_domain_t::assume_lt(bool strict, m_registers_interval_values.insert(right_reg, reg_with_loc_right, interval_to_insert); } else if (lub >= rub && strict ? llb < rub : llb <= rub) { - auto interval_to_insert_left = interval_t(llb_orig, strict ? rub - number_t{1} : rub); + auto lb = is_signed ? llb_orig : number_t{0}; + auto interval_to_insert_left = interval_t(lb, strict ? rub - number_t{1} : rub); m_registers_interval_values.insert(left, reg_with_loc_left, interval_to_insert_left); // this is only one way to resolve this scenario, i.e. set right to singleton value (rub) // and set left to the rest of the interval < (or <=) of right @@ -548,7 +550,7 @@ void interval_prop_domain_t::assume_gt(bool strict, void interval_prop_domain_t::assume_gt_and_lt(bool is64, bool strict, bool is_lt, interval_t&& left_interval, interval_t&& right_interval, interval_t&& left_interval_orig, interval_t&& right_interval_orig, - register_t left, Value right, location_t loc) { + register_t left, Value right, location_t loc, bool is_signed) { auto llb = left_interval.lb(); auto lub = left_interval.ub(); @@ -572,11 +574,20 @@ void interval_prop_domain_t::assume_gt_and_lt(bool is64, bool strict, bool is_lt // Left unsigned interval is higher than right unsigned interval. m_registers_interval_values.set_to_top(); return; + } else if (left_interval_orig.is_top() && right_interval_orig.is_top()) { + m_registers_interval_values.insert(left, reg_with_loc_t(left, loc), interval_t::top()); + if (std::holds_alternative(right)) { + auto right_reg = std::get(right).v; + m_registers_interval_values.insert(right_reg, reg_with_loc_t(right_reg, loc), + interval_t::top()); + } + return; } if (is_lt) assume_lt(strict, std::move(left_interval), std::move(right_interval), - std::move(left_interval_orig), std::move(right_interval_orig), left, right, loc); + std::move(left_interval_orig), std::move(right_interval_orig), left, right, loc, + is_signed); else assume_gt(strict, std::move(left_interval), std::move(right_interval), std::move(left_interval_orig), std::move(right_interval_orig), left, right, loc); @@ -616,7 +627,8 @@ void interval_prop_domain_t::assume_unsigned_cst_interval(Condition::Op op, bool bool strict = op == Condition::Op::LT || op == Condition::Op::GT; assume_gt_and_lt(is64, strict, is_lt, std::move(left_interval), std::move(right_interval), - std::move(left_interval_orig), std::move(right_interval_orig), left, right, loc); + std::move(left_interval_orig), std::move(right_interval_orig), left, right, loc, + false); } void interval_prop_domain_t::assume_signed_cst_interval(Condition::Op op, bool is64, diff --git a/src/crab/interval_prop_domain.hpp b/src/crab/interval_prop_domain.hpp index 482e963f9..49ffb9e2a 100644 --- a/src/crab/interval_prop_domain.hpp +++ b/src/crab/interval_prop_domain.hpp @@ -158,11 +158,11 @@ class interval_prop_domain_t final { interval_t&&, interval_t&&, register_t, Value, location_t); void assume_cst(Condition::Op, bool, register_t, Value, location_t); void assume_lt(bool, interval_t&&, interval_t&&, interval_t&&, interval_t&&, - register_t, Value, location_t); + register_t, Value, location_t, bool); void assume_gt(bool, interval_t&&, interval_t&&, interval_t&&, interval_t&&, register_t, Value, location_t); void assume_gt_and_lt(bool, bool, bool, interval_t&&, interval_t&&, - interval_t&&, interval_t&&, register_t, Value, location_t); + interval_t&&, interval_t&&, register_t, Value, location_t, bool = true); std::optional find_interval_value(register_t) const; std::optional find_interval_at_loc(const reg_with_loc_t reg) const; std::optional find_in_stack(uint64_t) const; From 45a97e46317c7ab82e5b9c9ae4aedfddd3442f71 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Fri, 6 Oct 2023 12:36:33 -0400 Subject: [PATCH 129/373] Implemented stack all numeric in valid access Checking that stack is all numeric in certain location was needed in the Valid Access check; Signed-off-by: Ameer Hamza --- src/crab/interval_prop_domain.cpp | 39 +++++++++++++++++++++++++++---- src/crab/interval_prop_domain.hpp | 2 ++ src/crab/type_domain.cpp | 18 ++++++++++---- 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/crab/interval_prop_domain.cpp b/src/crab/interval_prop_domain.cpp index 2edd36bce..1992c8938 100644 --- a/src/crab/interval_prop_domain.cpp +++ b/src/crab/interval_prop_domain.cpp @@ -1078,12 +1078,39 @@ void interval_prop_domain_t::do_mem_store(const Mem& b, std::optional width_interval, bool check_stack_all_numeric) { + + if (check_stack_all_numeric) { + uint64_t start_offset = 0; + int width = 0; + auto start_interval = interval + interval_t{number_t{s.offset}}; + if (auto finite_size = start_interval.finite_size()) { + if (auto start_interval_lb = start_interval.lb().number()) { + start_offset = (uint64_t)(*start_interval_lb); + if (auto width_interval_ub = width_interval->ub().number()) { + width = (int)(*finite_size + *width_interval_ub); + if (!m_stack_slots_interval_values.all_numeric(start_offset, width)) { + m_errors.push_back("Stack access not numeric"); + } + } + else { + m_errors.push_back("Width information not available"); + } + } + else { + m_errors.push_back("Offset information not available"); + } + } + else { + m_errors.push_back("Register interval not finite for stack access"); + } + } + else { + bool is_comparison_check = s.width == (Value)Imm{0}; if (!is_comparison_check) { if (s.or_null) { - if (auto singleton = maybe_interval->to_interval().singleton()) { + if (auto singleton = interval.singleton()) { if (*singleton == number_t{0}) return; } m_errors.push_back("Non-null number"); @@ -1095,6 +1122,10 @@ void interval_prop_domain_t::operator()(const ValidAccess& s, location_t loc, in } } +void interval_prop_domain_t::operator()(const ValidAccess& s, location_t loc, int print) { + // nothing to do here +} + void interval_prop_domain_t::operator()(const Undefined& u, location_t loc, int print) { // nothing to do here } diff --git a/src/crab/interval_prop_domain.hpp b/src/crab/interval_prop_domain.hpp index 49ffb9e2a..9f674d9b7 100644 --- a/src/crab/interval_prop_domain.hpp +++ b/src/crab/interval_prop_domain.hpp @@ -152,6 +152,8 @@ class interval_prop_domain_t final { void do_bin(const Bin&, const std::optional&, const std::optional&, const std::optional&, const std::optional&, const interval_t&, location_t); + void check_valid_access(const ValidAccess&, interval_t&&, std::optional, + bool = false); void assume_unsigned_cst_interval(Condition::Op, bool, interval_t&&, interval_t&&, interval_t&&, interval_t&&, register_t, Value, location_t); void assume_signed_cst_interval(Condition::Op, bool, interval_t&&, interval_t&&, diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 124eead8e..087f2e8a7 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -218,19 +218,27 @@ void type_domain_t::operator()(const ValidAccess& s, location_t loc, int print) std::optional width_mock_interval; if (std::holds_alternative(s.width)) { width_mock_interval = m_interval.find_interval_value(std::get(s.width).v); + if (!width_mock_interval) { + m_errors.push_back("width is unknown for valid access"); + return; + } } else { auto imm = std::get(s.width); - width_mock_interval = mock_interval_t{interval_t(number_t{imm.v}, number_t{imm.v})}; + width_mock_interval = mock_interval_t{interval_t{number_t{imm.v}}}; } - auto width_interval = - width_mock_interval ? width_mock_interval->to_interval() : std::optional{}; + auto width_interval = width_mock_interval->to_interval(); if (is_packet_ptr(reg_type)) { - m_offset.check_valid_access(s, reg_type, interval_type, width_interval); + m_offset.check_valid_access(s, reg_type, interval_type, std::optional{}); + } + if (s.access_type == AccessType::read && is_stack_ptr(reg_type)) { + auto stack_ptr = std::get(*reg_type); + auto offset_ptr = stack_ptr.get_offset().to_interval(); + m_interval.check_valid_access(s, std::move(offset_ptr), width_interval, true); } } else { - m_interval(s, loc); + m_interval.check_valid_access(s, std::move(*interval_type), std::nullopt); } } From 63ddd8e80afc8fc1ab8f5870da3a369a90bd8e33 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Fri, 6 Oct 2023 13:59:46 -0400 Subject: [PATCH 130/373] Fixed issues with Valid Access for all domains The Valid Access was not being checked correctly when width is stored in a register, hence it passed the cases when it should fail; For Region and Offset domain, added required implementations; Refactoring and simplification in Type and Interval Propagation domains; Signed-off-by: Ameer Hamza --- src/crab/interval_prop_domain.cpp | 17 +++++------------ src/crab/interval_prop_domain.hpp | 3 +-- src/crab/offset_domain.cpp | 6 +----- src/crab/offset_domain.hpp | 3 +-- src/crab/region_domain.cpp | 9 ++++++--- src/crab/region_domain.hpp | 1 + src/crab/type_domain.cpp | 22 ++++++++++++++-------- 7 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/crab/interval_prop_domain.cpp b/src/crab/interval_prop_domain.cpp index 1992c8938..2000c3ffa 100644 --- a/src/crab/interval_prop_domain.cpp +++ b/src/crab/interval_prop_domain.cpp @@ -1079,23 +1079,16 @@ void interval_prop_domain_t::do_mem_store(const Mem& b, std::optional width_interval, bool check_stack_all_numeric) { + int width, bool check_stack_all_numeric) { if (check_stack_all_numeric) { - uint64_t start_offset = 0; - int width = 0; auto start_interval = interval + interval_t{number_t{s.offset}}; if (auto finite_size = start_interval.finite_size()) { if (auto start_interval_lb = start_interval.lb().number()) { - start_offset = (uint64_t)(*start_interval_lb); - if (auto width_interval_ub = width_interval->ub().number()) { - width = (int)(*finite_size + *width_interval_ub); - if (!m_stack_slots_interval_values.all_numeric(start_offset, width)) { - m_errors.push_back("Stack access not numeric"); - } - } - else { - m_errors.push_back("Width information not available"); + auto start_offset = (uint64_t)(*start_interval_lb); + int width_from_start = (int)(*finite_size) + width; + if (!m_stack_slots_interval_values.all_numeric(start_offset, width_from_start)) { + m_errors.push_back("Stack access not numeric"); } } else { diff --git a/src/crab/interval_prop_domain.hpp b/src/crab/interval_prop_domain.hpp index 9f674d9b7..7b3c9b093 100644 --- a/src/crab/interval_prop_domain.hpp +++ b/src/crab/interval_prop_domain.hpp @@ -152,8 +152,7 @@ class interval_prop_domain_t final { void do_bin(const Bin&, const std::optional&, const std::optional&, const std::optional&, const std::optional&, const interval_t&, location_t); - void check_valid_access(const ValidAccess&, interval_t&&, std::optional, - bool = false); + void check_valid_access(const ValidAccess&, interval_t&&, int, bool = false); void assume_unsigned_cst_interval(Condition::Op, bool, interval_t&&, interval_t&&, interval_t&&, interval_t&&, register_t, Value, location_t); void assume_signed_cst_interval(Condition::Op, bool, interval_t&&, interval_t&&, diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index ade57da3b..12f9a7f9b 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -840,17 +840,13 @@ bool offset_domain_t::check_packet_access(const Reg& r, int width, int offset, } void offset_domain_t::check_valid_access(const ValidAccess& s, - std::optional& reg_type, - std::optional, std::optional) { - if (std::holds_alternative(s.width)) return; - int w = std::get(s.width).v; + std::optional& reg_type, int w) { if (w == 0 || !reg_type) return; bool is_comparison_check = s.width == (Value)Imm{0}; if (check_packet_access(s.reg, w, s.offset, is_comparison_check)) return; m_errors.push_back("valid access check failed"); //std::cout << "type_error: valid access assert fail\n"; - //exit(1); } void offset_domain_t::operator()(const Assert &u, location_t loc, int print) { diff --git a/src/crab/offset_domain.hpp b/src/crab/offset_domain.hpp index ada6aa1a8..8867b6f22 100644 --- a/src/crab/offset_domain.hpp +++ b/src/crab/offset_domain.hpp @@ -283,8 +283,7 @@ class offset_domain_t final { bool upper_bound_satisfied(const dist_t&, int, int, bool) const; bool lower_bound_satisfied(const dist_t&, int) const; bool check_packet_access(const Reg&, int, int, bool) const; - void check_valid_access(const ValidAccess&, std::optional&, - std::optional, std::optional); + void check_valid_access(const ValidAccess&, std::optional&, int); std::optional find_in_ctx(int) const; std::optional find_in_stack(int) const; diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index d04381483..5b82789ac 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -620,10 +620,8 @@ void region_domain_t::operator()(const Addable &u, location_t loc, int print) { m_errors.push_back("Addable assertion fail"); } -void region_domain_t::operator()(const ValidAccess &s, location_t loc, int print) { +void region_domain_t::check_valid_access(const ValidAccess &s, int width) { bool is_comparison_check = s.width == (Value)Imm{0}; - if (std::holds_alternative(s.width)) return; - int width = std::get(s.width).v; auto maybe_ptr_or_mapfd_type = m_registers.find(s.reg.v); if (maybe_ptr_or_mapfd_type) { @@ -664,6 +662,11 @@ void region_domain_t::operator()(const ValidAccess &s, location_t loc, int print //std::cout << "type error: valid access assert fail\n"; m_errors.push_back("valid access assert fail"); } + +} + +void region_domain_t::operator()(const ValidAccess &s, location_t loc, int print) { + // nothing to do here } void region_domain_t::operator()(const ValidStore& u, location_t loc, int print) { diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index 2b74607b1..bf9f2d726 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -173,6 +173,7 @@ class region_domain_t final { interval_t do_bin(const Bin&, const std::optional&, const std::optional&, const std::optional&, location_t); + void check_valid_access(const ValidAccess &, int); void update_ptr_or_mapfd(crab::ptr_or_mapfd_t&&, const interval_t&&, const crab::reg_with_loc_t&, uint8_t); diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 087f2e8a7..05a7d3ad3 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -214,7 +214,6 @@ void type_domain_t::operator()(const ValidAccess& s, location_t loc, int print) auto interval_type = mock_interval_type ? mock_interval_type->to_interval() : std::optional{}; if (reg_type) { - m_region(s, loc); std::optional width_mock_interval; if (std::holds_alternative(s.width)) { width_mock_interval = m_interval.find_interval_value(std::get(s.width).v); @@ -228,17 +227,24 @@ void type_domain_t::operator()(const ValidAccess& s, location_t loc, int print) width_mock_interval = mock_interval_t{interval_t{number_t{imm.v}}}; } auto width_interval = width_mock_interval->to_interval(); - if (is_packet_ptr(reg_type)) { - m_offset.check_valid_access(s, reg_type, interval_type, std::optional{}); + if (auto width_number = width_interval.ub().number()) { + int width = (int)*width_number; + m_region.check_valid_access(s, width); + if (is_packet_ptr(reg_type)) { + m_offset.check_valid_access(s, reg_type, width); + } + if (s.access_type == AccessType::read && is_stack_ptr(reg_type)) { + auto stack_ptr = std::get(*reg_type); + auto offset_ptr = stack_ptr.get_offset().to_interval(); + m_interval.check_valid_access(s, std::move(offset_ptr), width, true); + } } - if (s.access_type == AccessType::read && is_stack_ptr(reg_type)) { - auto stack_ptr = std::get(*reg_type); - auto offset_ptr = stack_ptr.get_offset().to_interval(); - m_interval.check_valid_access(s, std::move(offset_ptr), width_interval, true); + else { + m_errors.push_back("width is unknown for valid access"); } } else { - m_interval.check_valid_access(s, std::move(*interval_type), std::nullopt); + m_interval.check_valid_access(s, std::move(*interval_type), -1); } } From a9ffe771b412689bbff4668c6d8c0bbb36357f08 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Fri, 6 Oct 2023 16:27:36 -0400 Subject: [PATCH 131/373] Proper forgetting of registers and stack cells in Call and Un In Un, the forgetting of the registers in Offset and Region domain was added; In Call, proper forgetting of stack cells in Offset and Region domain was added; Removed some redundant code in Type domain; Signed-off-by: Ameer Hamza --- src/crab/common.hpp | 2 ++ src/crab/interval_prop_domain.cpp | 13 ++++++------- src/crab/interval_prop_domain.hpp | 2 +- src/crab/offset_domain.cpp | 14 ++++++++++++-- src/crab/offset_domain.hpp | 2 +- src/crab/region_domain.cpp | 16 ++++++++++++++-- src/crab/region_domain.hpp | 1 + src/crab/type_domain.cpp | 16 ++++------------ 8 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/crab/common.hpp b/src/crab/common.hpp index e7f21acc2..efb7a3291 100644 --- a/src/crab/common.hpp +++ b/src/crab/common.hpp @@ -171,6 +171,8 @@ inline std::ostream& operator<<(std::ostream& o, const region_t& t) { return o; } +using stack_cells_t = std::vector>; + } // namespace crab diff --git a/src/crab/interval_prop_domain.cpp b/src/crab/interval_prop_domain.cpp index 2000c3ffa..f2f2e485b 100644 --- a/src/crab/interval_prop_domain.cpp +++ b/src/crab/interval_prop_domain.cpp @@ -430,17 +430,16 @@ void interval_prop_domain_t::operator()(const ValidSize& s, location_t loc, int m_errors.push_back("Valid Size assertion fail"); } -void interval_prop_domain_t::do_call(const Call& u, const interval_values_stack_t& store_in_stack, +void interval_prop_domain_t::do_call(const Call& u, const stack_cells_t& store_in_stack, location_t loc) { for (const auto& kv : store_in_stack) { - auto key = kv.first; - auto width = kv.second.second; + auto offset = kv.first; + auto width = kv.second; auto overlapping_cells - = m_stack_slots_interval_values.find_overlapping_cells(key, width); - m_stack_slots_interval_values.remove_overlap(overlapping_cells, key, width); - m_stack_slots_interval_values.store(kv.first, kv.second.first.to_interval(), - kv.second.second); + = m_stack_slots_interval_values.find_overlapping_cells(offset, width); + m_stack_slots_interval_values.remove_overlap(overlapping_cells, offset, width); + m_stack_slots_interval_values.store(offset, interval_t::top(), width); } auto r0 = register_t{R0_RETURN_VALUE}; if (u.is_map_lookup) { diff --git a/src/crab/interval_prop_domain.hpp b/src/crab/interval_prop_domain.hpp index 7b3c9b093..ff51ec6ec 100644 --- a/src/crab/interval_prop_domain.hpp +++ b/src/crab/interval_prop_domain.hpp @@ -148,7 +148,7 @@ class interval_prop_domain_t final { void do_load(const Mem&, const Reg&, std::optional, bool, location_t); void do_mem_store(const Mem&, std::optional); - void do_call(const Call&, const interval_values_stack_t&, location_t); + void do_call(const Call&, const stack_cells_t&, location_t); void do_bin(const Bin&, const std::optional&, const std::optional&, const std::optional&, const std::optional&, const interval_t&, location_t); diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index 12f9a7f9b..477a8941d 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -733,17 +733,27 @@ void offset_domain_t::operator()(const Undefined& u, location_t loc, int print) } void offset_domain_t::operator()(const Un& u, location_t loc, int print) { - // nothing to do here + m_reg_state -= u.dst.v; } void offset_domain_t::operator()(const LoadMapFd& u, location_t loc, int print) { m_reg_state -= u.dst.v; } -void offset_domain_t::operator()(const Call& u, location_t loc, int print) { +void offset_domain_t::do_call(const Call& u, const stack_cells_t& cells, location_t loc) { + for (const auto& kv : cells) { + auto offset = kv.first; + auto width = kv.second; + auto overlapping_cells + = m_stack_state.find_overlapping_cells(offset, width); + m_stack_state -= overlapping_cells; + } m_reg_state -= register_t{R0_RETURN_VALUE}; } +void offset_domain_t::operator()(const Call& u, location_t loc, int print) { + // nothing to do here +} void offset_domain_t::operator()(const Exit& u, location_t loc, int print) {} void offset_domain_t::operator()(const Jmp& u, location_t loc, int print) { diff --git a/src/crab/offset_domain.hpp b/src/crab/offset_domain.hpp index 8867b6f22..c2c638022 100644 --- a/src/crab/offset_domain.hpp +++ b/src/crab/offset_domain.hpp @@ -191,7 +191,6 @@ class ctx_offsets_t { int get_size() const; }; - class offset_domain_t final { bool m_is_bottom = false; @@ -280,6 +279,7 @@ class offset_domain_t final { const std::optional&, std::optional&, std::optional&, location_t); + void do_call(const Call&, const stack_cells_t&, location_t); bool upper_bound_satisfied(const dist_t&, int, int, bool) const; bool lower_bound_satisfied(const dist_t&, int) const; bool check_packet_access(const Reg&, int, int, bool) const; diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index 5b82789ac..b9f5b0d62 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -438,7 +438,7 @@ void region_domain_t::operator()(const basic_block_t& bb, int print) { } void region_domain_t::operator()(const Un& u, location_t loc, int print) { - // nothing to do here + m_registers -= u.dst.v; } void region_domain_t::operator()(const ValidDivisor& u, location_t loc, int print) { @@ -553,7 +553,14 @@ void region_domain_t::operator()(const LoadMapFd &u, location_t loc, int print) do_load_mapfd((register_t)u.dst.v, u.mapfd, loc); } -void region_domain_t::operator()(const Call& u, location_t loc, int print) { +void region_domain_t::do_call(const Call& u, const stack_cells_t& cells, location_t loc) { + for (const auto& kv : cells) { + auto offset = kv.first; + auto width = kv.second; + auto overlapping_cells + = m_stack.find_overlapping_cells(offset, width); + m_stack -= overlapping_cells; + } std::optional maybe_fd_reg{}; for (ArgSingle param : u.singles) { if (param.kind == ArgSingle::Kind::MAP_FD) maybe_fd_reg = param.reg; @@ -590,6 +597,11 @@ void region_domain_t::operator()(const Call& u, location_t loc, int print) { if (u.reallocate_packet) { m_registers.forget_packet_ptrs(); } + +} + +void region_domain_t::operator()(const Call& u, location_t loc, int print) { + // nothing to do here } void region_domain_t::operator()(const Callx &u, location_t loc, int print) { diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index bf9f2d726..79f189a73 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -173,6 +173,7 @@ class region_domain_t final { interval_t do_bin(const Bin&, const std::optional&, const std::optional&, const std::optional&, location_t); + void do_call(const Call&, const stack_cells_t&, location_t); void check_valid_access(const ValidAccess &, int); void update_ptr_or_mapfd(crab::ptr_or_mapfd_t&&, const interval_t&&, const crab::reg_with_loc_t&, uint8_t); diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 05a7d3ad3..53a2df339 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -122,8 +122,7 @@ void type_domain_t::operator()(const IncrementLoopCounter &u, location_t loc, in void type_domain_t::operator()(const Call& u, location_t loc, int print) { - using interval_values_stack_t = std::map; - interval_values_stack_t stack_values; + stack_cells_t stack_values; for (ArgPair param : u.pairs) { if (param.kind == ArgPair::Kind::PTR_TO_WRITABLE_MEM) { auto maybe_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(param.mem.v); @@ -142,20 +141,13 @@ void type_domain_t::operator()(const Call& u, location_t loc, int print) { auto offset = (uint64_t)offset_singleton.value(); if (auto single_width = width_interval.singleton()) { int width = (int)single_width.value(); - - // Removing overlapping cells - // TODO: do it for all domains - auto overlapping_cells - = m_interval.find_overlapping_cells_in_stack(offset, width); - m_interval.remove_overlap_in_stack(overlapping_cells, offset, width); - - stack_values[offset] = std::make_pair(interval_t::top(), width); + stack_values.push_back(std::make_pair(offset, width)); } } } } - m_region(u, loc); - m_offset(u, loc); + m_region.do_call(u, stack_values, loc); + m_offset.do_call(u, stack_values, loc); m_interval.do_call(u, stack_values, loc); } From 8d85ba708630e0cd5734df13d818c53e5f9d9e7a Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Fri, 6 Oct 2023 16:50:06 -0400 Subject: [PATCH 132/373] Implementing scratch caller saved registers, and packet reallocate In Call and Packet, scratch caller saved registers functionality is added for all domains; In Call, for the Offset domain, the packet pointer forgetting is added (it is already implemented in Region domain but was missing in Offset); Signed-off-by: Ameer Hamza --- src/crab/interval_prop_domain.cpp | 9 ++++++++- src/crab/interval_prop_domain.hpp | 1 + src/crab/offset_domain.cpp | 17 +++++++++++++++++ src/crab/offset_domain.hpp | 3 ++- src/crab/region_domain.cpp | 11 +++++++++-- src/crab/region_domain.hpp | 1 + 6 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/crab/interval_prop_domain.cpp b/src/crab/interval_prop_domain.cpp index f2f2e485b..1d135497a 100644 --- a/src/crab/interval_prop_domain.cpp +++ b/src/crab/interval_prop_domain.cpp @@ -91,6 +91,12 @@ void registers_cp_state_t::operator-=(register_t var) { m_cur_def[var] = nullptr; } +void registers_cp_state_t::scratch_caller_saved_registers() { + for (int i = R1_ARG; i <= R5_ARG; i++) { + operator-=(i); + } +} + bool stack_cp_state_t::is_bottom() const { return m_is_bottom; } @@ -449,13 +455,14 @@ void interval_prop_domain_t::do_call(const Call& u, const stack_cells_t& store_i auto r0_with_loc = reg_with_loc_t(r0, loc); m_registers_interval_values.insert(r0, r0_with_loc, interval_t::top()); } + m_registers_interval_values.scratch_caller_saved_registers(); } void interval_prop_domain_t::operator()(const Packet& u, location_t loc, int print) { auto r0 = register_t{R0_RETURN_VALUE}; auto r0_with_loc = reg_with_loc_t(r0, loc); m_registers_interval_values.insert(r0, r0_with_loc, interval_t::top()); - // TODO: scratch caller saved registers + m_registers_interval_values.scratch_caller_saved_registers(); } void interval_prop_domain_t::assume_lt(bool strict, diff --git a/src/crab/interval_prop_domain.hpp b/src/crab/interval_prop_domain.hpp index ff51ec6ec..a7c3d1422 100644 --- a/src/crab/interval_prop_domain.hpp +++ b/src/crab/interval_prop_domain.hpp @@ -43,6 +43,7 @@ class registers_cp_state_t { bool is_bottom = false) : m_interval_env(interval_env), m_is_bottom(is_bottom) {} void adjust_bb_for_registers(location_t); + void scratch_caller_saved_registers(); }; using interval_cells_t = std::pair; // intervals with width diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index 477a8941d..d8fd1694b 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -236,6 +236,18 @@ void registers_state_t::adjust_bb_for_registers(location_t loc) { } } +void registers_state_t::scratch_caller_saved_registers() { + for (int i = R1_ARG; i <= R5_ARG; i++) { + operator-=(i); + } +} + +void registers_state_t::forget_packet_pointers() { + for (int i = register_t{0}; i <= register_t{10}; i++) { + operator-=(i); + } +} + void stack_state_t::set_to_top() { m_slot_dists.clear(); m_is_bottom = false; @@ -749,6 +761,10 @@ void offset_domain_t::do_call(const Call& u, const stack_cells_t& cells, locatio m_stack_state -= overlapping_cells; } m_reg_state -= register_t{R0_RETURN_VALUE}; + m_reg_state.scratch_caller_saved_registers(); + if (u.reallocate_packet) { + m_reg_state.forget_packet_pointers(); + } } void offset_domain_t::operator()(const Call& u, location_t loc, int print) { @@ -762,6 +778,7 @@ void offset_domain_t::operator()(const Jmp& u, location_t loc, int print) { void offset_domain_t::operator()(const Packet& u, location_t loc, int print) { m_reg_state -= register_t{R0_RETURN_VALUE}; + m_reg_state.scratch_caller_saved_registers(); } void offset_domain_t::operator()(const ValidDivisor& u, location_t loc, int print) { diff --git a/src/crab/offset_domain.hpp b/src/crab/offset_domain.hpp index c2c638022..8f6743918 100644 --- a/src/crab/offset_domain.hpp +++ b/src/crab/offset_domain.hpp @@ -128,7 +128,8 @@ class registers_state_t { std::optional find(register_t key) const; friend std::ostream& operator<<(std::ostream& o, const registers_state_t& p); void adjust_bb_for_registers(location_t); - void print_all_register_types() const; + void scratch_caller_saved_registers(); + void forget_packet_pointers(); }; using dist_cells_t = std::pair; diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index b9f5b0d62..113fc0cb8 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -37,6 +37,12 @@ std::optional ctx_t::find(uint64_t key) const { return it->second; } +void register_types_t::scratch_caller_saved_registers() { + for (int i = R1_ARG; i <= R5_ARG; i++) { + operator-=(i); + } +} + void register_types_t::forget_packet_ptrs() { for (auto r = register_t{0}; r <= register_t{10}; ++r) { auto reg = find(r); @@ -594,10 +600,10 @@ void region_domain_t::do_call(const Call& u, const stack_cells_t& cells, locatio m_registers -= r0_reg; } out: + m_registers.scratch_caller_saved_registers(); if (u.reallocate_packet) { m_registers.forget_packet_ptrs(); } - } void region_domain_t::operator()(const Call& u, location_t loc, int print) { @@ -616,8 +622,9 @@ void region_domain_t::operator()(const Atomic &u, location_t loc, int print) { // WARNING: Not implemented yet. } -void region_domain_t::operator()(const Packet &u, location_t loc, int print) { +void region_domain_t::operator()(const Packet& u, location_t loc, int print) { m_registers -= register_t{R0_RETURN_VALUE}; + m_registers.scratch_caller_saved_registers(); } void region_domain_t::operator()(const Addable &u, location_t loc, int print) { diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index 79f189a73..3ea870652 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -84,6 +84,7 @@ class register_types_t { std::optional find(register_t key) const; [[nodiscard]] live_registers_t &get_vars() { return m_cur_def; } void forget_packet_ptrs(); + void scratch_caller_saved_registers(); void adjust_bb_for_registers(location_t loc); }; From f044718b42b336084e422514a7349fccd2e5bad8 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 9 Oct 2023 14:47:32 -0400 Subject: [PATCH 133/373] Fixed an issue with Un, along with printing it Previously, the value for dst in Un was being forgotten if certain conditions did not meet, but that should have just stored a TOP number, instead of forgetting; Printing the Un operation; Signed-off-by: Ameer Hamza --- src/crab/interval_prop_domain.cpp | 5 +++-- src/crab/type_domain.cpp | 6 ++++++ src/crab/type_ostream.cpp | 26 ++++++++++++++++++++++++++ src/crab/type_ostream.hpp | 1 + 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/crab/interval_prop_domain.cpp b/src/crab/interval_prop_domain.cpp index 1d135497a..6e9393fc0 100644 --- a/src/crab/interval_prop_domain.cpp +++ b/src/crab/interval_prop_domain.cpp @@ -367,17 +367,18 @@ interval_prop_domain_t interval_prop_domain_t::setup_entry() { void interval_prop_domain_t::operator()(const Un& u, location_t loc, int print) { auto swap_endianness = [&](interval_t&& v, auto input, const auto& be_or_le) { + auto reg_with_loc = reg_with_loc_t(u.dst.v, loc); if (std::optional n = v.singleton()) { if (n->fits_cast_to_int64()) { input = (decltype(input))n.value().cast_to_sint64(); decltype(input) output = be_or_le(input); - auto reg_with_loc = reg_with_loc_t(u.dst.v, loc); m_registers_interval_values.insert(u.dst.v, reg_with_loc, interval_t(number_t(output))); return; } } - m_registers_interval_values -= u.dst.v; + m_registers_interval_values.insert(u.dst.v, reg_with_loc, + interval_t::top()); }; auto mock_interval = m_registers_interval_values.find(u.dst.v); diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 53a2df339..e916be718 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -619,6 +619,12 @@ void print_annotated(std::ostream& o, const crab::type_domain_t& typ, auto region = typ.find_ptr_or_mapfd_at_loc(reg); print_annotated(o, u, region); } + else if (std::holds_alternative(statement)) { + auto u = std::get(statement); + auto reg = crab::reg_with_loc_t(u.dst.v, loc); + auto interval = typ.find_interval_at_loc(reg); + print_annotated(o, u, interval); + } else o << " " << statement << "\n"; } diff --git a/src/crab/type_ostream.cpp b/src/crab/type_ostream.cpp index 09c35b45b..d6efc2122 100644 --- a/src/crab/type_ostream.cpp +++ b/src/crab/type_ostream.cpp @@ -76,3 +76,29 @@ void print_annotated(std::ostream& o, const Mem& b, std::optional& n) { + o << " "; + print_register(o, b.dst, std::nullopt, std::nullopt, n); + o << " = " << op(b.op) << " " << b.dst << "\n"; +} diff --git a/src/crab/type_ostream.hpp b/src/crab/type_ostream.hpp index 0040f4ed2..fa258e585 100644 --- a/src/crab/type_ostream.hpp +++ b/src/crab/type_ostream.hpp @@ -17,3 +17,4 @@ void print_annotated(std::ostream& o, const Call& call, std::optional& p, std::optional&, std::optional&); void print_annotated(std::ostream& o, const LoadMapFd& u, std::optional& p); void print_annotated(std::ostream& o, const Mem& b, std::optional& p, std::optional&, std::optional&); +void print_annotated(std::ostream& o, const Un& b, std::optional&); From ca7ff863feb7c150b237f32f71d9754f7d9749f9 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 10 Oct 2023 05:24:29 -0400 Subject: [PATCH 134/373] Implemented ValidDivisor, refactored multiple Implemented ValidDivisor, that was missed; Refactored/Moved to Type domain from sub-domains the following: ValidSize, Addable, ValidStore; Signed-off-by: Ameer Hamza --- src/crab/common.hpp | 5 +++ src/crab/interval_prop_domain.cpp | 11 +---- src/crab/region_domain.cpp | 22 ++-------- src/crab/type_domain.cpp | 70 ++++++++++++++++++++++--------- 4 files changed, 60 insertions(+), 48 deletions(-) diff --git a/src/crab/common.hpp b/src/crab/common.hpp index efb7a3291..951737401 100644 --- a/src/crab/common.hpp +++ b/src/crab/common.hpp @@ -134,6 +134,11 @@ class reg_with_loc_t { using ptr_or_mapfd_t = std::variant; +inline bool is_ptr_type(const std::optional& ptr_or_mapfd) { + return (ptr_or_mapfd && (std::holds_alternative(*ptr_or_mapfd) + || std::holds_alternative(*ptr_or_mapfd))); +} + inline bool is_mapfd_type(const std::optional& ptr_or_mapfd) { return (ptr_or_mapfd && std::holds_alternative(*ptr_or_mapfd)); } diff --git a/src/crab/interval_prop_domain.cpp b/src/crab/interval_prop_domain.cpp index 6e9393fc0..1dcedf7dd 100644 --- a/src/crab/interval_prop_domain.cpp +++ b/src/crab/interval_prop_domain.cpp @@ -425,16 +425,7 @@ void interval_prop_domain_t::operator()(const LoadMapFd &u, location_t loc, int } void interval_prop_domain_t::operator()(const ValidSize& s, location_t loc, int print) { - auto reg_v = m_registers_interval_values.find(s.reg.v); - if (reg_v) { - auto reg_value = reg_v.value(); - if ((s.can_be_zero && reg_value.lb() >= bound_t{number_t{0}}) - || (!s.can_be_zero && reg_value.lb() > bound_t{number_t{0}})) { - return; - } - } - //std::cout << "type error: Valid Size assertion fail\n"; - m_errors.push_back("Valid Size assertion fail"); + // nothing to do here } void interval_prop_domain_t::do_call(const Call& u, const stack_cells_t& store_in_stack, diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index 113fc0cb8..3f484f604 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -448,7 +448,7 @@ void region_domain_t::operator()(const Un& u, location_t loc, int print) { } void region_domain_t::operator()(const ValidDivisor& u, location_t loc, int print) { - /* WARNING: The operation is not implemented yet.*/ + // nothing to do here } void region_domain_t::operator()(const ValidSize& u, location_t loc, int print) { @@ -628,15 +628,7 @@ void region_domain_t::operator()(const Packet& u, location_t loc, int print) { } void region_domain_t::operator()(const Addable &u, location_t loc, int print) { - - auto maybe_ptr_type1 = m_registers.find(u.ptr.v); - auto maybe_ptr_type2 = m_registers.find(u.num.v); - // a -> b <-> !a || b - if (!maybe_ptr_type1 || !maybe_ptr_type2) { - return; - } - //std::cout << "type error: Addable assertion fail\n"; - m_errors.push_back("Addable assertion fail"); + // nothing to do here } void region_domain_t::check_valid_access(const ValidAccess &s, int width) { @@ -689,15 +681,7 @@ void region_domain_t::operator()(const ValidAccess &s, location_t loc, int print } void region_domain_t::operator()(const ValidStore& u, location_t loc, int print) { - - bool is_stack_p = is_stack_ptr(m_registers.find(u.val.v)); - auto maybe_ptr_type2 = m_registers.find(u.val.v); - - if (is_stack_p || !maybe_ptr_type2) { - return; - } - //std::cout << "type error: Valid store assertion fail\n"; - m_errors.push_back("Valid store assertion fail"); + // nothing to do here } region_domain_t&& region_domain_t::setup_entry() { diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index e916be718..ebfd33da5 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -196,8 +196,20 @@ void type_domain_t::operator()(const Assume& s, location_t loc, int print) { } } -void type_domain_t::operator()(const ValidDivisor& s, location_t loc, int print) { - /* WARNING: The operation is not implemented yet.*/ +void type_domain_t::operator()(const ValidDivisor& u, location_t loc, int print) { + auto maybe_ptr_or_mapfd_reg = m_region.find_ptr_or_mapfd_type(u.reg.v); + auto maybe_num_type_reg = m_interval.find_interval_value(u.reg.v); + assert(!maybe_ptr_or_mapfd_reg.has_value() || !maybe_num_type_reg.has_value()); + + if (is_ptr_type(maybe_ptr_or_mapfd_reg)) { + m_errors.push_back("Only numbers can be used as divisors"); + } + else if (maybe_num_type_reg.has_value() && !thread_local_options.allow_division_by_zero) { + auto num_type_reg = maybe_num_type_reg->to_interval(); + if (interval_t{number_t{0}} <= num_type_reg) { + m_errors.push_back("Possible division by zero"); + } + } } void type_domain_t::operator()(const ValidAccess& s, location_t loc, int print) { @@ -275,32 +287,52 @@ void type_domain_t::operator()(const Comparable& u, location_t loc, int print) { } void type_domain_t::operator()(const Addable& u, location_t loc, int print) { - auto maybe_ptr_or_mapfd1 = m_region.find_ptr_or_mapfd_type(u.ptr.v); - auto maybe_ptr_or_mapfd2 = m_region.find_ptr_or_mapfd_type(u.num.v); - auto maybe_num_type1 = m_interval.find_interval_value(u.ptr.v); - auto maybe_num_type2 = m_interval.find_interval_value(u.num.v); - assert(!maybe_ptr_or_mapfd1 || !maybe_num_type1); - assert(!maybe_ptr_or_mapfd2 || !maybe_num_type2); - m_region(u, loc); - // TODO: move the definition from the region domain to the type domain + auto maybe_ptr_or_mapfd_ptr = m_region.find_ptr_or_mapfd_type(u.ptr.v); + auto maybe_ptr_or_mapfd_num = m_region.find_ptr_or_mapfd_type(u.num.v); + auto maybe_num_type_ptr = m_interval.find_interval_value(u.ptr.v); + auto maybe_num_type_num = m_interval.find_interval_value(u.num.v); + assert(!maybe_ptr_or_mapfd_ptr.has_value() || !maybe_num_type_ptr.has_value()); + assert(!maybe_ptr_or_mapfd_num.has_value() || !maybe_num_type_num.has_value()); + + // a -> b <-> !a || b + // is_ptr(ptr) -> is_num(num) <-> !is_ptr(ptr) || is_num(num) + if (!is_ptr_type(maybe_ptr_or_mapfd_ptr) || + (!maybe_ptr_or_mapfd_num.has_value() || maybe_num_type_num.has_value())) { + return; + } + m_errors.push_back("Addable assertion fail"); } void type_domain_t::operator()(const ValidStore& u, location_t loc, int print) { - auto maybe_ptr_or_mapfd1 = m_region.find_ptr_or_mapfd_type(u.mem.v); - auto maybe_ptr_or_mapfd2 = m_region.find_ptr_or_mapfd_type(u.val.v); - auto maybe_num_type1 = m_interval.find_interval_value(u.mem.v); - auto maybe_num_type2 = m_interval.find_interval_value(u.val.v); - assert(!maybe_ptr_or_mapfd1 || !maybe_num_type1); - assert(!maybe_ptr_or_mapfd2 || !maybe_num_type2); - m_region(u, loc); - // TODO: move the definition from the region domain to the type domain + auto maybe_ptr_or_mapfd_mem = m_region.find_ptr_or_mapfd_type(u.mem.v); + auto maybe_ptr_or_mapfd_val = m_region.find_ptr_or_mapfd_type(u.val.v); + auto maybe_num_type_mem = m_interval.find_interval_value(u.mem.v); + auto maybe_num_type_val = m_interval.find_interval_value(u.val.v); + assert(!maybe_ptr_or_mapfd_mem.has_value() || !maybe_num_type_mem.has_value()); + assert(!maybe_ptr_or_mapfd_val.has_value() || !maybe_num_type_val.has_value()); + + // a -> b <-> !a || b + // !is_stack_ptr(mem) -> is_num(val) <-> is_stack_ptr(mem) || is_num(val) + if (is_stack_ptr(maybe_ptr_or_mapfd_mem) || + (!maybe_ptr_or_mapfd_val.has_value() || maybe_num_type_val.has_value())) { + return; + } + m_errors.push_back("Valid store assertion fail"); } void type_domain_t::operator()(const ValidSize& u, location_t loc, int print) { auto maybe_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(u.reg.v); auto maybe_num_type = m_interval.find_interval_value(u.reg.v); assert(!maybe_ptr_or_mapfd || !maybe_num_type); - m_interval(u, loc); + + if (maybe_num_type) { + auto reg_value = maybe_num_type.value(); + if ((u.can_be_zero && reg_value.lb() >= bound_t{number_t{0}}) + || (!u.can_be_zero && reg_value.lb() > bound_t{number_t{0}})) { + return; + } + } + m_errors.push_back("Valid Size assertion fail"); } void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int print) { From 92c5d2657b097f50d7904732baa85e15e59883f8 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 17 Oct 2023 03:07:19 -0400 Subject: [PATCH 135/373] Refactoring in all domains Multiple refactorings done in order to simplify operations, remove redundant operations, and improve readability of the code; Signed-off-by: Ameer Hamza --- src/crab/common.cpp | 22 ++-- src/crab/common.hpp | 47 ++++---- src/crab/interval_prop_domain.cpp | 128 ++++++++------------ src/crab/interval_prop_domain.hpp | 6 +- src/crab/offset_domain.cpp | 127 ++++++++++---------- src/crab/offset_domain.hpp | 17 ++- src/crab/region_domain.cpp | 192 +++++++++++++----------------- src/crab/region_domain.hpp | 14 +-- src/crab/type_domain.cpp | 8 +- src/crab/type_ostream.cpp | 4 +- 10 files changed, 250 insertions(+), 315 deletions(-) diff --git a/src/crab/common.cpp b/src/crab/common.cpp index afd8000c0..54dc2fe58 100644 --- a/src/crab/common.cpp +++ b/src/crab/common.cpp @@ -9,7 +9,7 @@ namespace std { return std::visit( overloaded { []( const crab::ptr_with_off_t& x ){ return crab::ptr_t{x};}, - []( const crab::ptr_no_off_t& x ){ return crab::ptr_t{x};}, + []( const crab::packet_ptr_t& x ){ return crab::ptr_t{x};}, []( auto& ) { return crab::ptr_t{};} }, t ); @@ -32,11 +32,11 @@ bool ptr_with_off_t::operator!=(const ptr_with_off_t& other) const { return !(*this == other); } -bool ptr_no_off_t::operator==(const ptr_no_off_t& other) const { +bool packet_ptr_t::operator==(const packet_ptr_t& other) const { return (m_r == other.m_r); } -bool ptr_no_off_t::operator!=(const ptr_no_off_t& other) const { +bool packet_ptr_t::operator!=(const packet_ptr_t& other) const { return !(*this == other); } @@ -65,8 +65,6 @@ ptr_with_off_t ptr_with_off_t::operator|(const ptr_with_off_t& other) const { return ptr_with_off_t(m_r, std::move(mock_o), std::move(mock_r_s)); } -void ptr_no_off_t::set_region(region_t r) { m_r = r; } - std::ostream& operator<<(std::ostream& o, const mapfd_t& m) { m.write(o); return o; @@ -116,10 +114,10 @@ inline std::string get_reg_ptr(const region_t& r) { return "ctx_p"; case region_t::T_STACK: return "stack_p"; - case region_t::T_PACKET: - return "packet_p"; - default: + case region_t::T_SHARED: return "shared_p"; + default: + return "packet_p"; } } @@ -134,12 +132,8 @@ std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p) { return o; } -void ptr_no_off_t::write(std::ostream& o) const { - o << get_reg_ptr(get_region()); -} - -std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p) { - p.write(o); +std::ostream& operator<<(std::ostream& o, const packet_ptr_t& p) { + o << "packet_p"; return o; } diff --git a/src/crab/common.hpp b/src/crab/common.hpp index 951737401..f4a96529d 100644 --- a/src/crab/common.hpp +++ b/src/crab/common.hpp @@ -11,15 +11,15 @@ #include "string_constraints.hpp" #include "asm_syntax.hpp" -constexpr int NUM_REGISTERS = 11; +namespace crab { + +constexpr uint8_t NUM_REGISTERS = 11; constexpr int STACK_BEGIN = 0; constexpr int CTX_BEGIN = 0; constexpr int PACKET_BEGIN = 0; constexpr int SHARED_BEGIN = 0; -namespace crab { - enum class region_t { T_CTX, T_STACK, @@ -27,23 +27,19 @@ enum class region_t { T_SHARED }; -class ptr_no_off_t { - region_t m_r; +class packet_ptr_t { + region_t m_r = region_t::T_PACKET; public: - ptr_no_off_t() = default; - ptr_no_off_t(const ptr_no_off_t &) = default; - ptr_no_off_t(ptr_no_off_t &&) = default; - ptr_no_off_t &operator=(const ptr_no_off_t &) = default; - ptr_no_off_t &operator=(ptr_no_off_t &&) = default; - ptr_no_off_t(region_t _r) : m_r(_r) {} - - [[nodiscard]] region_t get_region() const { return m_r; } - void set_region(region_t); - void write(std::ostream&) const; - friend std::ostream& operator<<(std::ostream& o, const ptr_no_off_t& p); - bool operator==(const ptr_no_off_t&) const; - bool operator!=(const ptr_no_off_t&) const; + packet_ptr_t() = default; + packet_ptr_t(const packet_ptr_t &) = default; + packet_ptr_t(packet_ptr_t &&) = default; + packet_ptr_t &operator=(const packet_ptr_t &) = default; + packet_ptr_t &operator=(packet_ptr_t &&) = default; + + friend std::ostream& operator<<(std::ostream& o, const packet_ptr_t& p); + bool operator==(const packet_ptr_t&) const; + bool operator!=(const packet_ptr_t&) const; }; class mock_interval_t { @@ -80,7 +76,6 @@ class ptr_with_off_t { ptr_with_off_t(region_t _r, mock_interval_t _off, mock_interval_t _region_sz = mock_interval_t::top()) : m_r(_r), m_offset(_off), m_region_size(_region_sz) {} - ptr_with_off_t(region_t _r, mock_interval_t _off) : m_r(_r), m_offset(_off) {} ptr_with_off_t operator|(const ptr_with_off_t&) const; mock_interval_t get_region_size() const; void set_region_size(mock_interval_t); @@ -116,7 +111,7 @@ class mapfd_t { [[nodiscard]] mock_interval_t get_mapfd() const { return m_mapfd; } }; -using ptr_t = std::variant; +using ptr_t = std::variant; using register_t = uint8_t; using location_t = boost::optional>; @@ -132,11 +127,11 @@ class reg_with_loc_t { void write(std::ostream& ) const; }; -using ptr_or_mapfd_t = std::variant; +using ptr_or_mapfd_t = std::variant; inline bool is_ptr_type(const std::optional& ptr_or_mapfd) { return (ptr_or_mapfd && (std::holds_alternative(*ptr_or_mapfd) - || std::holds_alternative(*ptr_or_mapfd))); + || std::holds_alternative(*ptr_or_mapfd))); } inline bool is_mapfd_type(const std::optional& ptr_or_mapfd) { @@ -144,7 +139,7 @@ inline bool is_mapfd_type(const std::optional& ptr_or_mapfd) { } inline bool same_region(const ptr_or_mapfd_t& ptr1, const ptr_or_mapfd_t& ptr2) { - if (std::holds_alternative(ptr1) && std::holds_alternative(ptr2)) + if (std::holds_alternative(ptr1) && std::holds_alternative(ptr2)) return true; return (std::holds_alternative(ptr1) && std::holds_alternative(ptr2) @@ -163,7 +158,7 @@ inline bool is_ctx_ptr(const std::optional& ptr) { } inline bool is_packet_ptr(const std::optional& ptr) { - return (ptr && std::holds_alternative(*ptr)); + return (ptr && std::holds_alternative(*ptr)); } inline bool is_shared_ptr(const std::optional& ptr) { @@ -194,7 +189,7 @@ namespace std { return std::visit( overloaded { []( const crab::ptr_with_off_t& x, const crab::ptr_with_off_t& y ){ return x == y;}, - []( const crab::ptr_no_off_t& x, const crab::ptr_no_off_t& y ){ return x == y;}, + []( const crab::packet_ptr_t& x, const crab::packet_ptr_t& y ){ return x == y;}, []( auto& , auto& ) { return true;} }, lhs, rhs ); @@ -208,7 +203,7 @@ namespace std { return std::visit( overloaded { []( const crab::ptr_with_off_t& x, const crab::ptr_with_off_t& y ){ return x == y;}, - []( const crab::ptr_no_off_t& x, const crab::ptr_no_off_t& y ){ return x == y;}, + []( const crab::packet_ptr_t& x, const crab::packet_ptr_t& y ){ return x == y;}, []( const crab::mapfd_t& x, const crab::mapfd_t& y ){ return x == y;}, []( auto& , auto& ) { return true;} }, lhs, rhs diff --git a/src/crab/interval_prop_domain.cpp b/src/crab/interval_prop_domain.cpp index 1dcedf7dd..8dd8fcc29 100644 --- a/src/crab/interval_prop_domain.cpp +++ b/src/crab/interval_prop_domain.cpp @@ -28,9 +28,9 @@ void registers_cp_state_t::set_to_bottom() { m_is_bottom = true; } -void registers_cp_state_t::insert(register_t reg, const reg_with_loc_t& reg_with_loc, - interval_t interval) { - (*m_interval_env)[reg_with_loc] = mock_interval_t(interval); +void registers_cp_state_t::insert(register_t reg, const location_t& loc, interval_t interval) { + auto reg_with_loc = reg_with_loc_t{reg, loc}; + (*m_interval_env)[reg_with_loc] = mock_interval_t{interval}; m_cur_def[reg] = std::make_shared(reg_with_loc); } @@ -52,37 +52,27 @@ registers_cp_state_t registers_cp_state_t::operator|(const registers_cp_state_t& } else if (other.is_bottom() || is_top()) { return *this; } - live_registers_t intervals_joined; + auto intervals_env = std::make_shared(); + registers_cp_state_t intervals_joined(intervals_env); location_t loc = location_t(std::make_pair(label_t(-2, -2), 0)); - for (size_t i = 0; i < m_cur_def.size(); i++) { + for (uint8_t i = 0; i < NUM_REGISTERS; i++) { if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; auto it1 = find(*(m_cur_def[i])); auto it2 = other.find(*(other.m_cur_def[i])); if (it1 && it2) { auto interval1 = it1->to_interval(), interval2 = it2->to_interval(); - auto reg = reg_with_loc_t((register_t)i, loc); - intervals_joined[i] = std::make_shared(reg); - (*m_interval_env)[reg] = mock_interval_t(interval1 | interval2); + intervals_joined.insert(register_t{i}, loc, + std::move(interval1 | interval2)); } } - return registers_cp_state_t(std::move(intervals_joined), m_interval_env); + return intervals_joined; } void registers_cp_state_t::adjust_bb_for_registers(location_t loc) { - location_t old_loc = location_t(std::make_pair(label_t(-2, -2), 0)); - for (size_t i = 0; i < m_cur_def.size(); i++) { - auto new_reg = reg_with_loc_t((register_t)i, loc); - auto old_reg = reg_with_loc_t((register_t)i, old_loc); - - auto it = find((register_t)i); - if (!it) continue; - - if (*m_cur_def[i] == old_reg) - m_interval_env->erase(old_reg); - - m_cur_def[i] = std::make_shared(new_reg); - (*m_interval_env)[new_reg] = mock_interval_t(it.value()); - + for (uint8_t i = 0; i < NUM_REGISTERS; i++) { + if (auto it = find(register_t{i})) { + insert(register_t{i}, loc, it->to_interval()); + } } } @@ -92,8 +82,8 @@ void registers_cp_state_t::operator-=(register_t var) { } void registers_cp_state_t::scratch_caller_saved_registers() { - for (int i = R1_ARG; i <= R5_ARG; i++) { - operator-=(i); + for (uint8_t i = R1_ARG; i <= R5_ARG; i++) { + operator-=(register_t{i}); } } @@ -323,7 +313,8 @@ interval_prop_domain_t interval_prop_domain_t::operator|(interval_prop_domain_t& else if (other.is_bottom() || is_top()) { return *this; } - return interval_prop_domain_t(m_registers_interval_values | std::move(other.m_registers_interval_values), + return interval_prop_domain_t( + m_registers_interval_values | std::move(other.m_registers_interval_values), m_stack_slots_interval_values | std::move(other.m_stack_slots_interval_values)); } @@ -358,27 +349,22 @@ string_invariant interval_prop_domain_t::to_set() { } interval_prop_domain_t interval_prop_domain_t::setup_entry() { - std::shared_ptr all_intervals = std::make_shared(); - registers_cp_state_t registers(all_intervals); - + registers_cp_state_t registers(std::make_shared()); interval_prop_domain_t cp(std::move(registers), stack_cp_state_t::top()); return cp; } void interval_prop_domain_t::operator()(const Un& u, location_t loc, int print) { auto swap_endianness = [&](interval_t&& v, auto input, const auto& be_or_le) { - auto reg_with_loc = reg_with_loc_t(u.dst.v, loc); if (std::optional n = v.singleton()) { if (n->fits_cast_to_int64()) { input = (decltype(input))n.value().cast_to_sint64(); decltype(input) output = be_or_le(input); - m_registers_interval_values.insert(u.dst.v, reg_with_loc, - interval_t(number_t(output))); + m_registers_interval_values.insert(u.dst.v, loc, interval_t{number_t{output}}); return; } } - m_registers_interval_values.insert(u.dst.v, reg_with_loc, - interval_t::top()); + m_registers_interval_values.insert(u.dst.v, loc, interval_t::top()); }; auto mock_interval = m_registers_interval_values.find(u.dst.v); @@ -415,7 +401,7 @@ void interval_prop_domain_t::operator()(const Un& u, location_t loc, int print) break; case Un::Op::NEG: auto reg_with_loc = reg_with_loc_t(u.dst.v, loc); - m_registers_interval_values.insert(u.dst.v, reg_with_loc, -interval); + m_registers_interval_values.insert(u.dst.v, loc, -interval); break; } } @@ -444,16 +430,14 @@ void interval_prop_domain_t::do_call(const Call& u, const stack_cells_t& store_i m_registers_interval_values -= r0; } else { - auto r0_with_loc = reg_with_loc_t(r0, loc); - m_registers_interval_values.insert(r0, r0_with_loc, interval_t::top()); + m_registers_interval_values.insert(r0, loc, interval_t::top()); } m_registers_interval_values.scratch_caller_saved_registers(); } void interval_prop_domain_t::operator()(const Packet& u, location_t loc, int print) { auto r0 = register_t{R0_RETURN_VALUE}; - auto r0_with_loc = reg_with_loc_t(r0, loc); - m_registers_interval_values.insert(r0, r0_with_loc, interval_t::top()); + m_registers_interval_values.insert(r0, loc, interval_t::top()); m_registers_interval_values.scratch_caller_saved_registers(); } @@ -461,7 +445,6 @@ void interval_prop_domain_t::assume_lt(bool strict, interval_t&& left_interval, interval_t&& right_interval, interval_t&& left_interval_orig, interval_t&& right_interval_orig, register_t left, Value right, location_t loc, bool is_signed) { - auto reg_with_loc_left = reg_with_loc_t(left, loc); auto rlb = right_interval.lb(); auto rub = right_interval.ub(); auto llb = left_interval.lb(); @@ -474,26 +457,24 @@ void interval_prop_domain_t::assume_lt(bool strict, if (strict ? llb < rlb : llb <= rlb && lub >= rlb) { auto lb = is_signed ? llb_orig : number_t{0}; auto interval_to_insert = interval_t(lb, strict ? rlb - number_t{1} : rlb); - m_registers_interval_values.insert(left, reg_with_loc_left, interval_to_insert); + m_registers_interval_values.insert(left, loc, interval_to_insert); } else if (left_interval <= right_interval && strict ? lub < rub : lub <= rub && std::holds_alternative(right)) { auto right_reg = std::get(right).v; - auto reg_with_loc_right = reg_with_loc_t(right_reg, loc); auto interval_to_insert = interval_t(strict ? lub + number_t{1} : lub, rub_orig); - m_registers_interval_values.insert(right_reg, reg_with_loc_right, interval_to_insert); + m_registers_interval_values.insert(right_reg, loc, interval_to_insert); } else if (lub >= rub && strict ? llb < rub : llb <= rub) { auto lb = is_signed ? llb_orig : number_t{0}; auto interval_to_insert_left = interval_t(lb, strict ? rub - number_t{1} : rub); - m_registers_interval_values.insert(left, reg_with_loc_left, interval_to_insert_left); + m_registers_interval_values.insert(left, loc, interval_to_insert_left); // this is only one way to resolve this scenario, i.e. set right to singleton value (rub) // and set left to the rest of the interval < (or <=) of right // a more sound analysis is needed if (std::holds_alternative(right)) { auto right_reg = std::get(right).v; - auto reg_with_loc_right = reg_with_loc_t(right_reg, loc); - m_registers_interval_values.insert(right_reg, reg_with_loc_right, interval_t(rub)); + m_registers_interval_values.insert(right_reg, loc, interval_t{rub}); } } else { @@ -506,7 +487,6 @@ void interval_prop_domain_t::assume_gt(bool strict, interval_t&& left_interval, interval_t&& right_interval, interval_t&& left_interval_orig, interval_t&& right_interval_orig, register_t left, Value right, location_t loc) { - auto reg_with_loc_left = reg_with_loc_t(left, loc); auto rlb = right_interval.lb(); auto rub = right_interval.ub(); auto llb = left_interval.lb(); @@ -518,25 +498,23 @@ void interval_prop_domain_t::assume_gt(bool strict, if (strict ? lub > rub : lub >= rub && llb <= rub) { auto interval_to_insert = interval_t(strict ? rub + number_t{1} : rub, lub_orig); - m_registers_interval_values.insert(left, reg_with_loc_left, interval_to_insert); + m_registers_interval_values.insert(left, loc, interval_to_insert); } else if (left_interval <= right_interval && strict ? llb > rlb : llb >= rlb && std::holds_alternative(right)) { auto right_reg = std::get(right).v; - auto reg_with_loc_right = reg_with_loc_t(right_reg, loc); auto interval_to_insert = interval_t(rlb_orig, strict ? llb - number_t{1} : llb); - m_registers_interval_values.insert(right_reg, reg_with_loc_right, interval_to_insert); + m_registers_interval_values.insert(right_reg, loc, interval_to_insert); } else if (llb <= rlb && strict ? lub > rlb : lub >= rlb) { auto interval_to_insert_left = interval_t(strict ? rlb + number_t{1} : rlb, lub_orig); - m_registers_interval_values.insert(left, reg_with_loc_left, interval_to_insert_left); + m_registers_interval_values.insert(left, loc, interval_to_insert_left); // this is only one way to resolve this scenario, i.e. set right to singleton value (rlb) // and set left to the rest of the interval > (or >=) of right // a more sound analysis is needed if (std::holds_alternative(right)) { auto right_reg = std::get(right).v; - auto reg_with_loc_right = reg_with_loc_t(right_reg, loc); - m_registers_interval_values.insert(right_reg, reg_with_loc_right, interval_t(rlb)); + m_registers_interval_values.insert(right_reg, loc, interval_t{rlb}); } } else { @@ -573,11 +551,10 @@ void interval_prop_domain_t::assume_gt_and_lt(bool is64, bool strict, bool is_lt m_registers_interval_values.set_to_top(); return; } else if (left_interval_orig.is_top() && right_interval_orig.is_top()) { - m_registers_interval_values.insert(left, reg_with_loc_t(left, loc), interval_t::top()); + m_registers_interval_values.insert(left, loc, interval_t::top()); if (std::holds_alternative(right)) { auto right_reg = std::get(right).v; - m_registers_interval_values.insert(right_reg, reg_with_loc_t(right_reg, loc), - interval_t::top()); + m_registers_interval_values.insert(right_reg, loc, interval_t::top()); } return; } @@ -642,11 +619,10 @@ void interval_prop_domain_t::assume_signed_cst_interval(Condition::Op op, bool i if (op == Condition::Op::EQ) { auto eq_interval = right_interval & left_interval; - m_registers_interval_values.insert(left, reg_with_loc_t(left, loc), eq_interval); + m_registers_interval_values.insert(left, loc, eq_interval); if (std::holds_alternative(right)) { auto right_reg = std::get(right).v; - m_registers_interval_values.insert(right_reg, - reg_with_loc_t(right_reg, loc), eq_interval); + m_registers_interval_values.insert(right_reg, loc, eq_interval); } return; } @@ -729,8 +705,10 @@ void interval_prop_domain_t::do_bin(const Bin& bin, // if op is MOV, // we skip handling in this domain is when src is pointer, // additionally, we forget the dst pointer + + auto dst_register = register_t{bin.dst.v}; if (bin.op == Op::MOV && src_ptr_or_mapfd_opt) { - m_registers_interval_values -= bin.dst.v; + m_registers_interval_values -= dst_register; return; } @@ -740,7 +718,6 @@ void interval_prop_domain_t::do_bin(const Bin& bin, if (src_interval_opt) src_interval = std::move(src_interval_opt.value()); if (dst_interval_opt) dst_interval = std::move(dst_interval_opt.value()); - auto reg_with_loc = reg_with_loc_t(bin.dst.v, loc); switch (bin.op) { // ra = b @@ -749,7 +726,7 @@ void interval_prop_domain_t::do_bin(const Bin& bin, dst_interval = src_interval; } else { - m_registers_interval_values -= bin.dst.v; + m_registers_interval_values -= dst_register; return; } break; @@ -760,7 +737,7 @@ void interval_prop_domain_t::do_bin(const Bin& bin, if (dst_interval_opt && src_interval_opt) dst_interval += src_interval; else if (dst_interval_opt && src_ptr_or_mapfd_opt) { - m_registers_interval_values -= bin.dst.v; + m_registers_interval_values -= dst_register; return; } break; @@ -772,7 +749,7 @@ void interval_prop_domain_t::do_bin(const Bin& bin, else if (dst_interval_opt && src_interval_opt) dst_interval -= src_interval; else if (dst_interval_opt && src_ptr_or_mapfd_opt) { - m_registers_interval_values -= bin.dst.v; + m_registers_interval_values -= dst_register; return; } break; @@ -971,23 +948,22 @@ void interval_prop_domain_t::do_bin(const Bin& bin, break; } } - m_registers_interval_values.insert(bin.dst.v, reg_with_loc, dst_interval); + m_registers_interval_values.insert(dst_register, loc, dst_interval); } -void interval_prop_domain_t::do_load(const Mem& b, const Reg& target_reg, +void interval_prop_domain_t::do_load(const Mem& b, const register_t& target_register, std::optional basereg_type, bool load_in_region, location_t loc) { if (!basereg_type) { - m_registers_interval_values -= target_reg.v; + m_registers_interval_values -= target_register; return; } - auto reg_with_loc = reg_with_loc_t(target_reg.v, loc); // we check if we already loaded a pointer from ctx or stack in region domain, // we then do not store a number if (load_in_region) { - m_registers_interval_values -= target_reg.v; + m_registers_interval_values -= target_register; return; } int width = b.access.width; @@ -995,14 +971,14 @@ void interval_prop_domain_t::do_load(const Mem& b, const Reg& target_reg, auto basereg_ptr_or_mapfd_type = basereg_type.value(); if (is_ctx_ptr(basereg_type)) { - m_registers_interval_values.insert(target_reg.v, reg_with_loc, interval_t::top()); + m_registers_interval_values.insert(target_register, loc, interval_t::top()); return; } if (is_packet_ptr(basereg_type) || is_shared_ptr(basereg_type)) { interval_t to_insert = interval_t::top(); if (width == 1) to_insert = interval_t(number_t{0}, number_t{UINT8_MAX}); else if (width == 2) to_insert = interval_t(number_t{0}, number_t{UINT16_MAX}); - m_registers_interval_values.insert(target_reg.v, reg_with_loc, to_insert); + m_registers_interval_values.insert(target_register, loc, to_insert); return; } @@ -1014,7 +990,7 @@ void interval_prop_domain_t::do_load(const Mem& b, const Reg& target_reg, if (auto load_at_singleton = load_at.singleton()) { start_offset = (uint64_t)(*load_at_singleton); if (auto loaded = m_stack_slots_interval_values.find(start_offset)) { - m_registers_interval_values.insert(target_reg.v, reg_with_loc, + m_registers_interval_values.insert(target_register, loc, (*loaded).first.to_interval()); return; } @@ -1030,15 +1006,15 @@ void interval_prop_domain_t::do_load(const Mem& b, const Reg& target_reg, } } if (m_stack_slots_interval_values.all_numeric(start_offset, width)) { - m_registers_interval_values.insert(target_reg.v, reg_with_loc, - interval_t::top()); + m_registers_interval_values.insert(target_register, loc, interval_t::top()); return; } } - m_registers_interval_values -= target_reg.v; + m_registers_interval_values -= target_register; } -void interval_prop_domain_t::do_mem_store(const Mem& b, std::optional basereg_type) { +void interval_prop_domain_t::do_mem_store(const Mem& b, + std::optional basereg_type) { int offset = b.access.offset; int width = b.access.width; diff --git a/src/crab/interval_prop_domain.hpp b/src/crab/interval_prop_domain.hpp index a7c3d1422..2db62b793 100644 --- a/src/crab/interval_prop_domain.hpp +++ b/src/crab/interval_prop_domain.hpp @@ -15,7 +15,7 @@ namespace crab { -using live_registers_t = std::array, 11>; +using live_registers_t = std::array, NUM_REGISTERS>; using global_interval_env_t = std::unordered_map; class registers_cp_state_t { @@ -31,7 +31,7 @@ class registers_cp_state_t { void set_to_top(); std::optional find(reg_with_loc_t reg) const; std::optional find(register_t key) const; - void insert(register_t, const reg_with_loc_t&, interval_t); + void insert(register_t, const location_t&, interval_t); void operator-=(register_t); registers_cp_state_t operator|(const registers_cp_state_t& other) const; registers_cp_state_t(bool is_bottom = false) : m_interval_env(nullptr), @@ -147,7 +147,7 @@ class interval_prop_domain_t final { string_invariant to_set(); void set_require_check(check_require_func_t f); - void do_load(const Mem&, const Reg&, std::optional, bool, location_t); + void do_load(const Mem&, const register_t&, std::optional, bool, location_t); void do_mem_store(const Mem&, std::optional); void do_call(const Call&, const stack_cells_t&, location_t); void do_bin(const Bin&, const std::optional&, const std::optional&, diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index d8fd1694b..1c9e139f0 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -125,9 +125,9 @@ void equality_t::write(std::ostream& o) const { o << m_lhs << " = " << m_rhs; } -void registers_state_t::insert(register_t reg, const reg_with_loc_t& reg_with_loc, - const dist_t& dist) { - (*m_offset_env)[reg_with_loc] = dist; +void registers_state_t::insert(register_t reg, const location_t& loc, dist_t&& dist) { + reg_with_loc_t reg_with_loc{reg, loc}; + (*m_offset_env)[reg_with_loc] = std::move(dist); m_cur_def[reg] = std::make_shared(reg_with_loc); } @@ -139,8 +139,7 @@ std::optional registers_state_t::find(reg_with_loc_t reg) const { std::optional registers_state_t::find(register_t key) const { if (m_cur_def[key] == nullptr) return {}; - const reg_with_loc_t& reg = *(m_cur_def[key]); - return find(reg); + return find(*(m_cur_def[key])); } std::vector stack_state_t::find_overlapping_cells(uint64_t start, int width) const { @@ -200,51 +199,42 @@ registers_state_t registers_state_t::operator|(const registers_state_t& other) c } else if (other.is_bottom() || is_top()) { return *this; } - live_registers_t out_vars; + + auto region_env = std::make_shared(); + registers_state_t joined_state(region_env); location_t loc = location_t(std::make_pair(label_t(-2, -2), 0)); - for (size_t i = 0; i < m_cur_def.size(); i++) { + for (uint8_t i = 0; i < NUM_REGISTERS; i++) { if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; auto it1 = find(*(m_cur_def[i])); auto it2 = other.find(*(other.m_cur_def[i])); if (it1 && it2) { - dist_t dist1 = it1.value(), dist2 = it2.value(); - auto reg = reg_with_loc_t((register_t)i, loc); + auto dist1 = *it1, dist2 = *it2; if (dist1.m_slack != dist2.m_slack) continue; auto dist_joined = dist_t(std::move(dist1.m_dist | dist2.m_dist), dist1.m_slack); - out_vars[i] = std::make_shared(reg); - (*m_offset_env)[reg] = dist_joined; + joined_state.insert(register_t{i}, loc, std::move(dist_joined)); } } - return registers_state_t(std::move(out_vars), m_offset_env, false); + return joined_state; } void registers_state_t::adjust_bb_for_registers(location_t loc) { - location_t old_loc = location_t(std::make_pair(label_t(-2, -2), 0)); - for (size_t i = 0; i < m_cur_def.size(); i++) { - auto new_reg = reg_with_loc_t((register_t)i, loc); - auto old_reg = reg_with_loc_t((register_t)i, old_loc); - - auto it = find((register_t)i); - if (!it) continue; - - if (*m_cur_def[i] == old_reg) - m_offset_env->erase(old_reg); - - m_cur_def[i] = std::make_shared(new_reg); - (*m_offset_env)[new_reg] = it.value(); + for (uint8_t i = 0; i < NUM_REGISTERS; i++) { + if (auto it = find(register_t{i})) { + insert(register_t{i}, loc, std::move(*it)); + } } } void registers_state_t::scratch_caller_saved_registers() { - for (int i = R1_ARG; i <= R5_ARG; i++) { - operator-=(i); + for (uint8_t r = R1_ARG; r <= R5_ARG; r++) { + operator-=(register_t{r}); } } void registers_state_t::forget_packet_pointers() { - for (int i = register_t{0}; i <= register_t{10}; i++) { - operator-=(i); + for (uint8_t r = R0_RETURN_VALUE; r < NUM_REGISTERS; r++) { + operator-=(register_t{r}); } } @@ -300,6 +290,7 @@ stack_state_t stack_state_t::operator|(const stack_state_t& other) const { } else if (other.is_bottom() || is_top()) { return *this; } + stack_slot_dists_t out_stack_dists; // We do not join dist cells because different dist values different types of offsets for (auto const&kv: m_slot_dists) { @@ -455,13 +446,13 @@ std::optional ctx_offsets_t::find(int key) const { return it->second; } -offset_domain_t offset_domain_t::setup_entry() { - std::shared_ptr ctx = std::make_shared(global_program_info->type.context_descriptor); - std::shared_ptr all_types = std::make_shared(); - registers_state_t regs(all_types); +offset_domain_t&& offset_domain_t::setup_entry() { + std::shared_ptr ctx + = std::make_shared(global_program_info->type.context_descriptor); + registers_state_t regs(std::make_shared()); - offset_domain_t off_d(std::move(regs), stack_state_t::top(), ctx); - return off_d; + static offset_domain_t off_d(std::move(regs), stack_state_t::top(), ctx); + return std::move(off_d); } offset_domain_t offset_domain_t::bottom() { @@ -513,7 +504,12 @@ offset_domain_t offset_domain_t::operator|(const offset_domain_t& other) const { else if (other.is_bottom() || is_top()) { return *this; } - return offset_domain_t(m_reg_state | other.m_reg_state, m_stack_state | other.m_stack_state, m_extra_constraints | other.m_extra_constraints, m_ctx_dists, std::max(m_slack, other.m_slack)); + return offset_domain_t( + m_reg_state | other.m_reg_state, + m_stack_state | other.m_stack_state, + m_extra_constraints | other.m_extra_constraints, + m_ctx_dists, std::max(m_slack, other.m_slack) + ); } offset_domain_t offset_domain_t::operator|(offset_domain_t&& other) const { @@ -523,7 +519,12 @@ offset_domain_t offset_domain_t::operator|(offset_domain_t&& other) const { else if (other.is_bottom() || is_top()) { return *this; } - return offset_domain_t(m_reg_state | std::move(other.m_reg_state), m_stack_state | std::move(other.m_stack_state), m_extra_constraints | std::move(other.m_extra_constraints), m_ctx_dists, std::max(m_slack, other.m_slack)); + return offset_domain_t( + m_reg_state | std::move(other.m_reg_state), + m_stack_state | std::move(other.m_stack_state), + m_extra_constraints | std::move(other.m_extra_constraints), + m_ctx_dists, std::max(m_slack, other.m_slack) + ); } // meet @@ -597,7 +598,7 @@ void offset_domain_t::operator()(const Assume &b, location_t loc, int print) { } void offset_domain_t::update_offset_info(const dist_t&& dist, const interval_t&& change, - const reg_with_loc_t& reg_with_loc, uint8_t reg, Bin::Op op) { + const location_t& loc, uint8_t reg, Bin::Op op) { auto offset = dist.m_dist; if (op == Bin::Op::ADD) { if (dist.is_forward_pointer()) offset += change; @@ -608,7 +609,7 @@ void offset_domain_t::update_offset_info(const dist_t&& dist, const interval_t&& // TODO: needs precise handling of subtraction offset = interval_t::top(); } - m_reg_state.insert(reg, reg_with_loc, dist_t(offset)); + m_reg_state.insert(reg, loc, dist_t(offset)); } interval_t offset_domain_t::do_bin(const Bin &bin, @@ -634,13 +635,13 @@ interval_t offset_domain_t::do_bin(const Bin &bin, Reg src; if (std::holds_alternative(bin.v)) src = std::get(bin.v); - auto reg_with_loc = reg_with_loc_t(bin.dst.v, loc); + auto dst_register = register_t{bin.dst.v}; switch (bin.op) { // ra = rb; case Op::MOV: { if (!is_packet_ptr(src_ptr_or_mapfd_opt)) { - m_reg_state -= bin.dst.v; + m_reg_state -= dst_register; return interval_t::bottom(); } auto src_offset_opt = m_reg_state.find(src.v); @@ -649,7 +650,7 @@ interval_t offset_domain_t::do_bin(const Bin &bin, //std::cout << "type_error: src is a packet_pointer and no offset info found\n"; return interval_t::bottom(); } - m_reg_state.insert(bin.dst.v, reg_with_loc, src_offset_opt.value()); + m_reg_state.insert(dst_register, loc, std::move(*src_offset_opt)); break; } // ra += rb @@ -658,15 +659,15 @@ interval_t offset_domain_t::do_bin(const Bin &bin, interval_t interval_to_add = interval_t::bottom(); if (is_packet_ptr(dst_ptr_or_mapfd_opt) && is_packet_ptr(src_ptr_or_mapfd_opt)) { - m_reg_state -= bin.dst.v; + m_reg_state -= dst_register; return interval_t::bottom(); } else if (is_packet_ptr(dst_ptr_or_mapfd_opt) && src_interval_opt) { - auto dst_offset_opt = m_reg_state.find(bin.dst.v); + auto dst_offset_opt = m_reg_state.find(dst_register); if (!dst_offset_opt) { m_errors.push_back("dst is a packet_pointer and no offset info found"); //std::cout << "type_error: dst is a packet_pointer and no offset info found\n"; - m_reg_state -= bin.dst.v; + m_reg_state -= dst_register; return interval_t::bottom(); } dist_to_update = std::move(dst_offset_opt.value()); @@ -678,7 +679,7 @@ interval_t offset_domain_t::do_bin(const Bin &bin, if (!src_offset_opt) { m_errors.push_back("src is a packet_pointer and no offset info found"); //std::cout << "type_error: src is a packet_pointer and no offset info found\n"; - m_reg_state -= bin.dst.v; + m_reg_state -= dst_register; return interval_t::bottom(); } dist_to_update = std::move(src_offset_opt.value()); @@ -686,11 +687,11 @@ interval_t offset_domain_t::do_bin(const Bin &bin, } else if (is_packet_ptr(dst_ptr_or_mapfd_opt)) { // this case is only needed till interval domain is added - m_reg_state.insert(bin.dst.v, reg_with_loc, dist_t()); + m_reg_state.insert(dst_register, loc, dist_t()); break; } update_offset_info(std::move(dist_to_update), std::move(interval_to_add), - reg_with_loc, bin.dst.v, bin.op); + loc, dst_register, bin.op); break; } // ra -= rb @@ -699,15 +700,15 @@ interval_t offset_domain_t::do_bin(const Bin &bin, interval_t interval_to_sub = interval_t::bottom(); if (is_packet_ptr(dst_ptr_or_mapfd_opt) && is_packet_ptr(src_ptr_or_mapfd_opt)) { - m_reg_state -= bin.dst.v; + m_reg_state -= dst_register; return interval_t::top(); } else if (is_packet_ptr(dst_ptr_or_mapfd_opt) && src_interval_opt) { - auto dst_offset_opt = m_reg_state.find(bin.dst.v); + auto dst_offset_opt = m_reg_state.find(dst_register); if (!dst_offset_opt) { m_errors.push_back("dst is a packet_pointer and no offset info found"); //std::cout << "type_error: dst is a packet_pointer and no offset info found\n"; - m_reg_state -= bin.dst.v; + m_reg_state -= dst_register; return interval_t::bottom(); } dist_to_update = std::move(dst_offset_opt.value()); @@ -718,18 +719,18 @@ interval_t offset_domain_t::do_bin(const Bin &bin, if (!src_offset_opt) { m_errors.push_back("src is a packet_pointer and no offset info found"); //std::cout << "type_error: src is a packet_pointer and no offset info found\n"; - m_reg_state -= bin.dst.v; + m_reg_state -= dst_register; return interval_t::bottom(); } dist_to_update = std::move(src_offset_opt.value()); interval_to_sub = std::move(dst_interval_opt.value()); } update_offset_info(std::move(dist_to_update), std::move(interval_to_sub), - reg_with_loc, bin.dst.v, bin.op); + loc, dst_register, bin.op); break; } default: { - m_reg_state -= bin.dst.v; + m_reg_state -= dst_register; break; } } @@ -908,14 +909,14 @@ void offset_domain_t::do_mem_store(const Mem& b, std::optional m } } -void offset_domain_t::do_load(const Mem& b, const Reg& target_reg, +void offset_domain_t::do_load(const Mem& b, const register_t& target_register, std::optional basereg_type, location_t loc) { bool is_stack_p = is_stack_ptr(basereg_type); bool is_ctx_p = is_ctx_ptr(basereg_type); if (!is_stack_p && !is_ctx_p) { - m_reg_state -= target_reg.v; + m_reg_state -= target_register; return; } @@ -924,7 +925,7 @@ void offset_domain_t::do_load(const Mem& b, const Reg& target_reg, auto p_offset = type_with_off.get_offset(); auto offset_singleton = p_offset.to_interval().singleton(); if (!offset_singleton) { - m_reg_state -= target_reg.v; + m_reg_state -= target_register; return; } auto ptr_offset = *offset_singleton; @@ -933,22 +934,18 @@ void offset_domain_t::do_load(const Mem& b, const Reg& target_reg, if (is_stack_p) { auto it = m_stack_state.find(load_at); if (!it) { - m_reg_state -= target_reg.v; + m_reg_state -= target_register; return; } - dist_t d = it->first; - auto reg = reg_with_loc_t(target_reg.v, loc); - m_reg_state.insert(target_reg.v, reg, d); + m_reg_state.insert(target_register, loc, std::move(it->first)); } else if (is_ctx_p) { auto it = m_ctx_dists->find(load_at); if (!it) { - m_reg_state -= target_reg.v; + m_reg_state -= target_register; return; } - dist_t d = it.value(); - auto reg = reg_with_loc_t(target_reg.v, loc); - m_reg_state.insert(target_reg.v, reg, dist_t(d)); + m_reg_state.insert(target_register, loc, std::move(*it)); } } diff --git a/src/crab/offset_domain.hpp b/src/crab/offset_domain.hpp index 8f6743918..595f3835b 100644 --- a/src/crab/offset_domain.hpp +++ b/src/crab/offset_domain.hpp @@ -10,10 +10,7 @@ #include "crab/common.hpp" namespace crab { -constexpr int STACK_BEGIN = 0; -constexpr int CTX_BEGIN = 0; -constexpr int PACKET_BEGIN = 0; -constexpr int SHARED_BEGIN = 0; + constexpr int PACKET_END = -4100; constexpr int PACKET_META = -1; constexpr int MAX_PACKET_SIZE = 0xffff; @@ -101,7 +98,7 @@ struct packet_constraint_t { friend std::ostream& operator<<(std::ostream&, const packet_constraint_t&); }; -using live_registers_t = std::array, 11>; +using live_registers_t = std::array, NUM_REGISTERS>; using global_offset_env_t = std::unordered_map; class registers_state_t { @@ -123,7 +120,7 @@ class registers_state_t { void set_to_bottom(); bool is_bottom() const; bool is_top() const; - void insert(register_t, const reg_with_loc_t&, const dist_t&); + void insert(register_t, const location_t&, dist_t&&); std::optional find(reg_with_loc_t reg) const; std::optional find(register_t key) const; friend std::ostream& operator<<(std::ostream& o, const registers_state_t& p); @@ -217,7 +214,7 @@ class offset_domain_t final { std::shared_ptr ctx, slack_var_t s = 0) : m_reg_state(std::move(reg)), m_stack_state(std::move(stack)), m_ctx_dists(ctx), m_slack(s) {} - static offset_domain_t setup_entry(); + static offset_domain_t&& setup_entry(); // bottom/top static offset_domain_t bottom(); void set_to_top(); @@ -274,7 +271,7 @@ class offset_domain_t final { string_invariant to_set(); void set_require_check(check_require_func_t f) {} - void do_load(const Mem&, const Reg&, std::optional, location_t loc); + void do_load(const Mem&, const register_t&, std::optional, location_t loc); void do_mem_store(const Mem&, std::optional, std::optional&); interval_t do_bin(const Bin&, const std::optional&, const std::optional&, @@ -290,8 +287,8 @@ class offset_domain_t final { std::optional find_in_stack(int) const; std::optional find_offset_at_loc(const reg_with_loc_t) const; std::optional find_offset_info(register_t reg) const; - void update_offset_info(const dist_t&&, const interval_t&&, - const reg_with_loc_t&, uint8_t, Bin::Op); + void update_offset_info(const dist_t&&, const interval_t&&, const location_t&, + uint8_t, Bin::Op); dist_t update_offset(const dist_t&, const weight_t&, const interval_t&, Bin::Op); void adjust_bb_for_types(location_t); [[nodiscard]] std::vector& get_errors() { return m_errors; } diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index 3f484f604..7ec4ec3a9 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -8,13 +8,13 @@ namespace crab { ctx_t::ctx_t(const ebpf_context_descriptor_t* desc) { if (desc->data >= 0) { - m_packet_ptrs[desc->data] = crab::ptr_no_off_t(crab::region_t::T_PACKET); + m_packet_ptrs[desc->data] = std::move(crab::packet_ptr_t{}); } if (desc->end >= 0) { - m_packet_ptrs[desc->end] = crab::ptr_no_off_t(crab::region_t::T_PACKET); + m_packet_ptrs[desc->end] = std::move(crab::packet_ptr_t{}); } if (desc->meta >= 0) { - m_packet_ptrs[desc->meta] = crab::ptr_no_off_t(crab::region_t::T_PACKET); + m_packet_ptrs[desc->meta] = std::move(crab::packet_ptr_t{}); } if (desc->size >= 0) { size = desc->size; @@ -31,23 +31,22 @@ std::vector ctx_t::get_keys() const { return keys; } -std::optional ctx_t::find(uint64_t key) const { +std::optional ctx_t::find(uint64_t key) const { auto it = m_packet_ptrs.find(key); if (it == m_packet_ptrs.end()) return {}; return it->second; } void register_types_t::scratch_caller_saved_registers() { - for (int i = R1_ARG; i <= R5_ARG; i++) { - operator-=(i); + for (uint8_t r = R1_ARG; r <= R5_ARG; r++) { + operator-=(register_t{r}); } } void register_types_t::forget_packet_ptrs() { - for (auto r = register_t{0}; r <= register_t{10}; ++r) { - auto reg = find(r); - if (is_packet_ptr(reg)) { - operator-=(r); + for (uint8_t r = R0_RETURN_VALUE; r < NUM_REGISTERS; r++) { + if (is_packet_ptr(find(register_t{r}))) { + operator-=(register_t{r}); } } } @@ -58,46 +57,44 @@ register_types_t register_types_t::operator|(const register_types_t& other) cons } else if (other.is_bottom() || is_top()) { return *this; } - live_registers_t out_vars; + auto region_env = std::make_shared(); + register_types_t joined_reg_types(region_env); // a hack to store region information at the start of a joined basic block // in join, we do not know the label of the bb, hence we store the information // at a bb that is not used anywhere else in the program, and later when we know // the bb label, we can fix - location_t loc = location_t(std::make_pair(label_t(-2, -2), 0)); + location_t loc = location_t{std::make_pair(label_t(-2, -2), 0)}; - for (size_t i = 0; i < m_cur_def.size(); i++) { + for (uint8_t i = 0; i < NUM_REGISTERS; i++) { if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; auto maybe_ptr1 = find(*(m_cur_def[i])); auto maybe_ptr2 = other.find(*(other.m_cur_def[i])); if (maybe_ptr1 && maybe_ptr2) { - ptr_or_mapfd_t ptr_or_mapfd1 = maybe_ptr1.value(), ptr_or_mapfd2 = maybe_ptr2.value(); - auto reg = reg_with_loc_t((register_t)i, loc); + ptr_or_mapfd_t ptr_or_mapfd1 = *maybe_ptr1, ptr_or_mapfd2 = *maybe_ptr2; if (ptr_or_mapfd1 == ptr_or_mapfd2) { - out_vars[i] = m_cur_def[i]; + joined_reg_types.insert(register_t{i}, loc, std::move(ptr_or_mapfd1)); } else { - auto shared_reg = std::make_shared(reg); if (std::holds_alternative(ptr_or_mapfd1) && std::holds_alternative(ptr_or_mapfd2)) { ptr_with_off_t ptr_with_off1 = std::get(ptr_or_mapfd1); ptr_with_off_t ptr_with_off2 = std::get(ptr_or_mapfd2); if (ptr_with_off1.get_region() == ptr_with_off2.get_region()) { - out_vars[i] = shared_reg; - (*m_region_env)[reg] = std::move(ptr_with_off1 | ptr_with_off2); + joined_reg_types.insert(register_t{i}, loc, + std::move(ptr_with_off1 | ptr_with_off2)); } } else if (std::holds_alternative(ptr_or_mapfd1) && std::holds_alternative(ptr_or_mapfd2)) { mapfd_t mapfd1 = std::get(ptr_or_mapfd1); mapfd_t mapfd2 = std::get(ptr_or_mapfd2); - out_vars[i] = shared_reg; - (*m_region_env)[reg] = std::move(mapfd1 | mapfd2); + joined_reg_types.insert(register_t{i}, loc, std::move(mapfd1 | mapfd2)); } } } } - return register_types_t(std::move(out_vars), m_region_env, false); + return joined_reg_types; } void register_types_t::operator-=(register_t var) { @@ -127,8 +124,8 @@ bool register_types_t::is_top() const { return true; } -void register_types_t::insert(register_t reg, const reg_with_loc_t& reg_with_loc, - const ptr_or_mapfd_t& type) { +void register_types_t::insert(register_t reg, const location_t& loc, const ptr_or_mapfd_t& type) { + reg_with_loc_t reg_with_loc = reg_with_loc_t{reg, loc}; (*m_region_env)[reg_with_loc] = type; m_cur_def[reg] = std::make_shared(reg_with_loc); } @@ -141,26 +138,14 @@ std::optional register_types_t::find(reg_with_loc_t reg) const { std::optional register_types_t::find(register_t key) const { if (m_cur_def[key] == nullptr) return {}; - const reg_with_loc_t& reg = *(m_cur_def[key]); - return find(reg); + return find(*(m_cur_def[key])); } void register_types_t::adjust_bb_for_registers(location_t loc) { - location_t old_loc = location_t(std::make_pair(label_t(-2, -2), 0)); - for (size_t i = 0; i < m_cur_def.size(); i++) { - auto new_reg = reg_with_loc_t((register_t)i, loc); - auto old_reg = reg_with_loc_t((register_t)i, old_loc); - - auto it = find((register_t)i); - if (!it) continue; - - if (*m_cur_def[i] == old_reg) { - m_region_env->erase(old_reg); + for (uint8_t i = 0; i < NUM_REGISTERS; i++) { + if (auto it = find(register_t{i})) { + insert(register_t{i}, loc, *it); } - - m_cur_def[i] = std::make_shared(new_reg); - (*m_region_env)[new_reg] = it.value(); - } } @@ -170,7 +155,7 @@ stack_t stack_t::operator|(const stack_t& other) const { } else if (other.is_bottom() || is_top()) { return *this; } - ptr_or_mapfd_types_t out_ptrs; + stack_t joined_stack; for (auto const&kv: m_ptrs) { auto maybe_ptr_or_mapfd_cells = other.find(kv.first); if (maybe_ptr_or_mapfd_cells) { @@ -180,28 +165,28 @@ stack_t stack_t::operator|(const stack_t& other) const { auto ptr_or_mapfd2 = ptr_or_mapfd_cells2.first; int width1 = ptr_or_mapfd_cells1.second; int width2 = ptr_or_mapfd_cells2.second; - int width_joined = std::max(width1, width2); + int width_joined = std::min(width1, width2); if (ptr_or_mapfd1 == ptr_or_mapfd2) { - out_ptrs[kv.first] = std::make_pair(ptr_or_mapfd1, width_joined); + joined_stack.store(kv.first, ptr_or_mapfd1, width_joined); } else if (std::holds_alternative(ptr_or_mapfd1) && std::holds_alternative(ptr_or_mapfd2)) { auto ptr_with_off1 = std::get(ptr_or_mapfd1); auto ptr_with_off2 = std::get(ptr_or_mapfd2); if (ptr_with_off1.get_region() == ptr_with_off2.get_region()) { - out_ptrs[kv.first] - = std::make_pair(ptr_with_off1 | ptr_with_off2, width_joined); + joined_stack.store(kv.first, std::move(ptr_with_off1 | ptr_with_off2), + width_joined); } } else if (std::holds_alternative(ptr_or_mapfd1) && std::holds_alternative(ptr_or_mapfd2)) { auto mapfd1 = std::get(ptr_or_mapfd1); auto mapfd2 = std::get(ptr_or_mapfd2); - out_ptrs[kv.first] = std::make_pair(mapfd1 | mapfd2, width_joined); + joined_stack.store(kv.first, std::move(mapfd1 | mapfd2), width_joined); } } } - return stack_t(std::move(out_ptrs), false); + return joined_stack; } void stack_t::operator-=(uint64_t key) { @@ -333,7 +318,7 @@ std::vector region_domain_t::get_stack_keys() const { return m_stack.get_keys(); } -std::optional region_domain_t::find_in_ctx(uint64_t key) const { +std::optional region_domain_t::find_in_ctx(uint64_t key) const { return m_ctx->find(key); } @@ -546,13 +531,12 @@ interval_t region_domain_t::get_map_value_size(const Reg& map_fd_reg) const { } void region_domain_t::do_load_mapfd(const register_t& dst_reg, int mapfd, location_t loc) { - auto reg_with_loc = reg_with_loc_t(dst_reg, loc); const auto& platform = global_program_info->platform; const EbpfMapDescriptor& desc = platform->get_map_descriptor(mapfd); const EbpfMapValueType& map_value_type = platform->get_map_type(desc.type).value_type; auto mapfd_interval = interval_t{number_t{mapfd}}; auto type = mapfd_t(mapfd_interval, map_value_type); - m_registers.insert(dst_reg, reg_with_loc, type); + m_registers.insert(dst_reg, loc, type); } void region_domain_t::operator()(const LoadMapFd &u, location_t loc, int print) { @@ -572,32 +556,31 @@ void region_domain_t::do_call(const Call& u, const stack_cells_t& cells, locatio if (param.kind == ArgSingle::Kind::MAP_FD) maybe_fd_reg = param.reg; break; } - register_t r0_reg{R0_RETURN_VALUE}; - auto r0 = reg_with_loc_t(r0_reg, loc); + register_t r0{R0_RETURN_VALUE}; if (u.is_map_lookup) { if (maybe_fd_reg) { if (auto map_type = get_map_type(*maybe_fd_reg)) { if (global_program_info->platform->get_map_type(*map_type).value_type == EbpfMapValueType::MAP) { if (auto inner_map_fd = get_map_inner_map_fd(*maybe_fd_reg)) { - do_load_mapfd(r0_reg, (int)*inner_map_fd, loc); + do_load_mapfd(r0, (int)*inner_map_fd, loc); goto out; } } else { auto type = ptr_with_off_t(crab::region_t::T_SHARED, interval_t{number_t{0}}, get_map_value_size(*maybe_fd_reg)); - m_registers.insert(r0_reg, r0, type); + m_registers.insert(r0, loc, type); } } } else { auto type = ptr_with_off_t( crab::region_t::T_SHARED, interval_t{number_t{0}}, crab::interval_t::top()); - m_registers.insert(r0_reg, r0, type); + m_registers.insert(r0, loc, type); } } else { - m_registers -= r0_reg; + m_registers -= r0; } out: m_registers.scratch_caller_saved_registers(); @@ -638,29 +621,29 @@ void region_domain_t::check_valid_access(const ValidAccess &s, int width) { if (maybe_ptr_or_mapfd_type) { auto reg_ptr_or_mapfd_type = *maybe_ptr_or_mapfd_type; if (std::holds_alternative(reg_ptr_or_mapfd_type)) { - auto reg_with_off_ptr_type = std::get(reg_ptr_or_mapfd_type); - auto offset = reg_with_off_ptr_type.get_offset(); + auto ptr_with_off_type = std::get(reg_ptr_or_mapfd_type); + auto offset = ptr_with_off_type.get_offset(); auto offset_to_check = offset.to_interval()+interval_t{s.offset}; auto offset_lb = offset_to_check.lb(); auto offset_plus_width_ub = offset_to_check.ub()+crab::bound_t{width}; - if (reg_with_off_ptr_type.get_region() == crab::region_t::T_STACK) { + if (ptr_with_off_type.get_region() == crab::region_t::T_STACK) { if (crab::bound_t{STACK_BEGIN} <= offset_lb && offset_plus_width_ub <= crab::bound_t{EBPF_STACK_SIZE}) return; } - else if (reg_with_off_ptr_type.get_region() == crab::region_t::T_CTX) { + else if (ptr_with_off_type.get_region() == crab::region_t::T_CTX) { if (crab::bound_t{CTX_BEGIN} <= offset_lb && offset_plus_width_ub <= crab::bound_t{ctx_size()}) return; } else { // shared if (crab::bound_t{SHARED_BEGIN} <= offset_lb && - offset_plus_width_ub <= reg_with_off_ptr_type.get_region_size().lb()) return; + offset_plus_width_ub <= ptr_with_off_type.get_region_size().lb()) return; // TODO: check null access //return; } } - else if (std::holds_alternative(reg_ptr_or_mapfd_type)) { + else if (std::holds_alternative(reg_ptr_or_mapfd_type)) { // We do not handle packet ptr access in region domain return; } @@ -686,20 +669,16 @@ void region_domain_t::operator()(const ValidStore& u, location_t loc, int print) region_domain_t&& region_domain_t::setup_entry() { - std::shared_ptr ctx = std::make_shared(global_program_info.get().type.context_descriptor); - std::shared_ptr all_types = std::make_shared(); + std::shared_ptr ctx + = std::make_shared(global_program_info.get().type.context_descriptor); - register_types_t typ(all_types); + register_types_t typ(std::make_shared()); - auto r1 = reg_with_loc_t(R1_ARG, std::make_pair(label_t::entry, static_cast(0))); - auto r10 = reg_with_loc_t(R10_STACK_POINTER, std::make_pair(label_t::entry, static_cast(0))); - - typ.insert(R1_ARG, r1, - ptr_with_off_t(crab::region_t::T_CTX, - mock_interval_t{number_t{0}}, mock_interval_t{number_t{-1}})); - typ.insert(R10_STACK_POINTER, r10, - ptr_with_off_t(crab::region_t::T_STACK, - mock_interval_t{number_t{512}}, mock_interval_t{number_t{-1}})); + auto loc = std::make_pair(label_t::entry, (unsigned int)0); + auto ctx_ptr_r1 = ptr_with_off_t(crab::region_t::T_CTX, mock_interval_t{number_t{0}}); + auto stack_ptr_r10 = ptr_with_off_t(crab::region_t::T_STACK, mock_interval_t{number_t{512}}); + typ.insert(register_t{R1_ARG}, loc, ctx_ptr_r1); + typ.insert(register_t{R10_STACK_POINTER}, loc, stack_ptr_r10); static region_domain_t inv(std::move(typ), crab::stack_t::top(), ctx); return std::move(inv); @@ -739,7 +718,7 @@ void region_domain_t::operator()(const TypeConstraint& s, location_t loc, int pr } } } - else if (std::holds_alternative(ptr_or_mapfd_type)) { + else if (std::holds_alternative(ptr_or_mapfd_type)) { if (s.types == TypeGroup::singleton_ptr) return; if (s.types == TypeGroup::mem || s.types == TypeGroup::mem_or_num) return; if (s.types == TypeGroup::packet || s.types == TypeGroup::stack_or_packet) return; @@ -757,17 +736,17 @@ void region_domain_t::operator()(const TypeConstraint& s, location_t loc, int pr } void region_domain_t::update_ptr_or_mapfd(ptr_or_mapfd_t&& ptr_or_mapfd, const interval_t&& change, - const reg_with_loc_t& reg_with_loc, uint8_t reg) { + const location_t& loc, register_t reg) { if (std::holds_alternative(ptr_or_mapfd)) { auto ptr_or_mapfd_with_off = std::get(ptr_or_mapfd); auto offset = ptr_or_mapfd_with_off.get_offset(); auto updated_offset = change == mock_interval_t::top() ? offset : offset.to_interval() + change; ptr_or_mapfd_with_off.set_offset(updated_offset); - m_registers.insert(reg, reg_with_loc, ptr_or_mapfd_with_off); + m_registers.insert(reg, loc, ptr_or_mapfd_with_off); } - else if (std::holds_alternative(ptr_or_mapfd)) { - m_registers.insert(reg, reg_with_loc, ptr_or_mapfd); + else if (std::holds_alternative(ptr_or_mapfd)) { + m_registers.insert(reg, loc, ptr_or_mapfd); } else { //std::cout << "type error: mapfd register cannot be incremented/decremented\n"; @@ -796,19 +775,18 @@ interval_t region_domain_t::do_bin(const Bin& bin, if (dst_ptr_or_mapfd_opt) dst_ptr_or_mapfd = std::move(dst_ptr_or_mapfd_opt.value()); if (src_interval_opt) src_interval = std::move(src_interval_opt.value()); - auto reg = reg_with_loc_t(bin.dst.v, loc); - interval_t to_return = interval_t::bottom(); - + interval_t subtracted = interval_t::bottom(); + auto dst_register = register_t{bin.dst.v}; switch (bin.op) { // ra = b, where b is a pointer/mapfd, a numerical register, or a constant; case Op::MOV: { // b is a pointer/mapfd if (src_ptr_or_mapfd_opt) - m_registers.insert(bin.dst.v, reg, src_ptr_or_mapfd); + m_registers.insert(dst_register, loc, src_ptr_or_mapfd); // b is a numerical register, or constant else if (dst_ptr_or_mapfd_opt) { - m_registers -= bin.dst.v; + m_registers -= dst_register; } break; } @@ -824,28 +802,28 @@ interval_t region_domain_t::do_bin(const Bin& bin, //std::cout << "type error: addition of two pointers\n"; m_errors.push_back("addition of two pointers"); } - m_registers -= bin.dst.v; + m_registers -= dst_register; } // ra is a pointer/mapfd // b is a numerical register, or a constant else if (dst_ptr_or_mapfd_opt && src_interval_opt) { update_ptr_or_mapfd(std::move(dst_ptr_or_mapfd), std::move(src_interval), - reg, bin.dst.v); + loc, dst_register); } // b is a pointer/mapfd // ra is a numerical register else if (src_ptr_or_mapfd_opt && !dst_ptr_or_mapfd_opt) { update_ptr_or_mapfd(std::move(src_ptr_or_mapfd), interval_t::top(), - reg, bin.dst.v); + loc, dst_register); } else if (dst_ptr_or_mapfd_opt && !src_ptr_or_mapfd_opt) { if (std::holds_alternative(dst_ptr_or_mapfd)) { auto updated_type = std::get(dst_ptr_or_mapfd); updated_type.set_offset(mock_interval_t::top()); - m_registers.insert(bin.dst.v, reg, updated_type); + m_registers.insert(dst_register, loc, updated_type); } - else if (std::holds_alternative(dst_ptr_or_mapfd)) { - m_registers.insert(bin.dst.v, reg, dst_ptr_or_mapfd); + else if (std::holds_alternative(dst_ptr_or_mapfd)) { + m_registers.insert(dst_register, loc, dst_ptr_or_mapfd); } } break; @@ -865,33 +843,34 @@ interval_t region_domain_t::do_bin(const Bin& bin, std::holds_alternative(src_ptr_or_mapfd)) { auto dst_ptr_with_off = std::get(dst_ptr_or_mapfd); auto src_ptr_with_off = std::get(src_ptr_or_mapfd); - to_return = - dst_ptr_with_off.get_offset().to_interval() - src_ptr_with_off.get_offset().to_interval(); + subtracted = + dst_ptr_with_off.get_offset().to_interval() - + src_ptr_with_off.get_offset().to_interval(); } } else { //std::cout << "type error: subtraction between pointers of different region\n"; m_errors.push_back("subtraction between pointers of different region"); } - m_registers -= bin.dst.v; + m_registers -= dst_register; } // b is a numerical register, or a constant else if (dst_ptr_or_mapfd_opt && src_interval_opt) { update_ptr_or_mapfd(std::move(dst_ptr_or_mapfd), -std::move(src_interval), - reg, bin.dst.v); + loc, dst_register); } break; } default: break; } - return to_return; + return subtracted; } -void region_domain_t::do_load(const Mem& b, const Reg& target_reg, bool unknown_ptr, +void region_domain_t::do_load(const Mem& b, const register_t& target_register, bool unknown_ptr, location_t loc) { if (unknown_ptr) { - m_registers -= target_reg.v; + m_registers -= target_register; return; } @@ -904,7 +883,7 @@ void region_domain_t::do_load(const Mem& b, const Reg& target_reg, bool unknown_ bool is_ctx_p = is_ctx_ptr(ptr_or_mapfd_opt); if (!is_ctx_p && !is_stack_p) { // loading from either packet or shared region or mapfd does not happen in region domain - m_registers -= target_reg.v; + m_registers -= target_register; return; } @@ -924,11 +903,11 @@ void region_domain_t::do_load(const Mem& b, const Reg& target_reg, bool unknown_ break; } } - m_registers -= target_reg.v; + m_registers -= target_register; } else { if (width != 1 && width != 2 && width != 4 && width != 8) { - m_registers -= target_reg.v; + m_registers -= target_register; return; } auto ptr_offset = offset_singleton.value(); @@ -937,12 +916,11 @@ void region_domain_t::do_load(const Mem& b, const Reg& target_reg, bool unknown_ auto loaded = m_stack.find(load_at); if (!loaded) { // no field at loaded offset in stack - m_registers -= target_reg.v; + m_registers -= target_register; return; } - auto reg = reg_with_loc_t(target_reg.v, loc); - m_registers.insert(target_reg.v, reg, (*loaded).first); + m_registers.insert(target_register, loc, (*loaded).first); } } else { @@ -957,7 +935,7 @@ void region_domain_t::do_load(const Mem& b, const Reg& target_reg, bool unknown_ break; } } - m_registers -= target_reg.v; + m_registers -= target_register; } else { auto ptr_offset = offset_singleton.value(); @@ -966,12 +944,10 @@ void region_domain_t::do_load(const Mem& b, const Reg& target_reg, bool unknown_ auto loaded = m_ctx->find(load_at); if (!loaded) { // no field at loaded offset in ctx - m_registers -= target_reg.v; + m_registers -= target_register; return; } - - auto reg = reg_with_loc_t(target_reg.v, loc); - m_registers.insert(target_reg.v, reg, *loaded); + m_registers.insert(target_register, loc, *loaded); } } } diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index 3ea870652..7138d47e8 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -13,7 +13,7 @@ namespace crab { class ctx_t { - using ptr_types_t = std::unordered_map; + using ptr_types_t = std::unordered_map; ptr_types_t m_packet_ptrs; size_t size = 0; @@ -22,7 +22,7 @@ class ctx_t { ctx_t(const ebpf_context_descriptor_t* desc); constexpr size_t get_size() const { return size; } std::vector get_keys() const; - std::optional find(uint64_t key) const; + std::optional find(uint64_t key) const; }; using ptr_or_mapfd_cells_t = std::pair; @@ -54,7 +54,7 @@ class stack_t { size_t size() const; }; -using live_registers_t = std::array, 11>; +using live_registers_t = std::array, NUM_REGISTERS>; using global_region_env_t = std::unordered_map; class register_types_t { @@ -79,7 +79,7 @@ class register_types_t { void set_to_top(); bool is_bottom() const; bool is_top() const; - void insert(register_t reg, const reg_with_loc_t& reg_with_loc, const ptr_or_mapfd_t& type); + void insert(register_t, const location_t&, const ptr_or_mapfd_t&); std::optional find(reg_with_loc_t reg) const; std::optional find(register_t key) const; [[nodiscard]] live_registers_t &get_vars() { return m_cur_def; } @@ -169,7 +169,7 @@ class region_domain_t final { std::optional get_map_type(const Reg&) const; std::optional get_map_inner_map_fd(const Reg&) const; void do_load_mapfd(const register_t&, int, location_t); - void do_load(const Mem&, const Reg&, bool, location_t); + void do_load(const Mem&, const register_t&, bool, location_t); void do_mem_store(const Mem&, location_t); interval_t do_bin(const Bin&, const std::optional&, const std::optional&, @@ -177,11 +177,11 @@ class region_domain_t final { void do_call(const Call&, const stack_cells_t&, location_t); void check_valid_access(const ValidAccess &, int); void update_ptr_or_mapfd(crab::ptr_or_mapfd_t&&, const interval_t&&, - const crab::reg_with_loc_t&, uint8_t); + const crab::location_t&, register_t); std::optional find_ptr_or_mapfd_type(register_t) const; [[nodiscard]] size_t ctx_size() const; - std::optional find_in_ctx(uint64_t key) const; + std::optional find_in_ctx(uint64_t key) const; [[nodiscard]] std::vector get_ctx_keys() const; std::optional find_in_stack(uint64_t key) const; std::optional find_ptr_or_mapfd_at_loc(const crab::reg_with_loc_t&) const; diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index ebfd33da5..734794f67 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -382,7 +382,7 @@ void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int pr return; } } - else if (std::holds_alternative(ptr_or_mapfd_basereg)) { + else if (std::holds_alternative(ptr_or_mapfd_basereg)) { if (m_offset.check_packet_access(u.access_reg, width, 0, true)) return; } else { @@ -460,11 +460,11 @@ void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { void type_domain_t::do_load(const Mem& b, const Reg& target_reg, bool unknown_ptr, std::optional basereg_opt, location_t loc) { - m_region.do_load(b, target_reg, unknown_ptr, loc); - m_offset.do_load(b, target_reg, basereg_opt, loc); + m_region.do_load(b, register_t{target_reg.v}, unknown_ptr, loc); + m_offset.do_load(b, register_t{target_reg.v}, basereg_opt, loc); // TODO: replace with a bool value returned from region do_load auto load_in_region = m_region.find_ptr_or_mapfd_type(target_reg.v).has_value(); - m_interval.do_load(b, target_reg, basereg_opt, load_in_region, loc); + m_interval.do_load(b, register_t{target_reg.v}, basereg_opt, load_in_region, loc); } void type_domain_t::do_mem_store(const Mem& b, std::optional target_opt, diff --git a/src/crab/type_ostream.cpp b/src/crab/type_ostream.cpp index d6efc2122..88f1fba50 100644 --- a/src/crab/type_ostream.cpp +++ b/src/crab/type_ostream.cpp @@ -7,8 +7,8 @@ void print_ptr_type(std::ostream& o, const crab::ptr_or_mapfd_t& ptr, std::optio if (std::holds_alternative(ptr)) { o << std::get(ptr); } - else if (std::holds_alternative(ptr)) { - o << std::get(ptr) << "<" << (d ? *d : crab::dist_t{}) << ">"; + else if (std::holds_alternative(ptr)) { + o << std::get(ptr) << "<" << (d ? *d : crab::dist_t{}) << ">"; } } From 90e633345b9f8da7179f9d015c2856bbf58cac8e Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 17 Oct 2023 03:59:48 -0400 Subject: [PATCH 136/373] Refactoring: removing unnecessary print option not needed print option was being passed to all transformers in all domains, since previously prints were done in respective transformers; it has been cleaned up; Signed-off-by: Ameer Hamza --- src/crab/abstract_domain.cpp | 66 +++++++++++++++--------------- src/crab/abstract_domain.hpp | 68 +++++++++++++++---------------- src/crab/ebpf_domain.cpp | 48 +++++++++++----------- src/crab/ebpf_domain.hpp | 48 +++++++++++----------- src/crab/interval_prop_domain.cpp | 38 ++++++++--------- src/crab/interval_prop_domain.hpp | 46 ++++++++++----------- src/crab/offset_domain.cpp | 40 +++++++++--------- src/crab/offset_domain.hpp | 48 +++++++++++----------- src/crab/region_domain.cpp | 47 ++++++++++----------- src/crab/region_domain.hpp | 49 +++++++++++----------- src/crab/type_domain.cpp | 53 ++++++++++++------------ src/crab/type_domain.hpp | 48 +++++++++++----------- 12 files changed, 300 insertions(+), 299 deletions(-) diff --git a/src/crab/abstract_domain.cpp b/src/crab/abstract_domain.cpp index 6ab87d730..e6a865122 100644 --- a/src/crab/abstract_domain.cpp +++ b/src/crab/abstract_domain.cpp @@ -116,58 +116,58 @@ void abstract_domain_t::abstract_domain_model::operator()(const basic_bl } template -void abstract_domain_t::abstract_domain_model::operator()(const Undefined& s, location_t loc, int print) { - m_abs_val.operator()(s, loc, print); +void abstract_domain_t::abstract_domain_model::operator()(const Undefined& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const Bin& s, location_t loc, int print) { - m_abs_val.operator()(s, loc, print); +void abstract_domain_t::abstract_domain_model::operator()(const Bin& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const Un& s, location_t loc, int print) { - m_abs_val.operator()(s, loc, print); +void abstract_domain_t::abstract_domain_model::operator()(const Un& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const LoadMapFd& s, location_t loc, int print) { - m_abs_val.operator()(s, loc, print); +void abstract_domain_t::abstract_domain_model::operator()(const LoadMapFd& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const Call& s, location_t loc, int print) { - m_abs_val.operator()(s, loc, print); +void abstract_domain_t::abstract_domain_model::operator()(const Call& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const Exit& s, location_t loc, int print) { - m_abs_val.operator()(s, loc, print); +void abstract_domain_t::abstract_domain_model::operator()(const Exit& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const Jmp& s, location_t loc, int print) { - m_abs_val.operator()(s, loc, print); +void abstract_domain_t::abstract_domain_model::operator()(const Jmp& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const Mem& s, location_t loc, int print) { - m_abs_val.operator()(s, loc, print); +void abstract_domain_t::abstract_domain_model::operator()(const Mem& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const Packet& s, location_t loc, int print) { - m_abs_val.operator()(s, loc, print); +void abstract_domain_t::abstract_domain_model::operator()(const Packet& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const Assume& s, location_t loc, int print) { - m_abs_val.operator()(s, loc, print); +void abstract_domain_t::abstract_domain_model::operator()(const Assume& s, location_t loc) { + m_abs_val.operator()(s, loc); } template -void abstract_domain_t::abstract_domain_model::operator()(const Assert& s, location_t loc, int print) { - m_abs_val.operator()(s, loc, print); +void abstract_domain_t::abstract_domain_model::operator()(const Assert& s, location_t loc) { + m_abs_val.operator()(s, loc); } template @@ -256,27 +256,27 @@ void abstract_domain_t::operator()(const basic_block_t& bb, int print) { m_concept->operator()(bb, print); } -void abstract_domain_t::operator()(const Undefined& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } +void abstract_domain_t::operator()(const Undefined& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const Bin& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } +void abstract_domain_t::operator()(const Bin& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const Un& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } +void abstract_domain_t::operator()(const Un& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const LoadMapFd& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } +void abstract_domain_t::operator()(const LoadMapFd& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const Call& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } +void abstract_domain_t::operator()(const Call& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const Exit& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } +void abstract_domain_t::operator()(const Exit& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const Jmp& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } +void abstract_domain_t::operator()(const Jmp& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const Mem& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } +void abstract_domain_t::operator()(const Mem& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const Packet& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } +void abstract_domain_t::operator()(const Packet& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const Assume& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } +void abstract_domain_t::operator()(const Assume& s, location_t loc) { m_concept->operator()(s, loc); } -void abstract_domain_t::operator()(const Assert& s, location_t loc, int print) { m_concept->operator()(s, loc, print); } +void abstract_domain_t::operator()(const Assert& s, location_t loc) { m_concept->operator()(s, loc); } void abstract_domain_t::write(std::ostream& os) const { m_concept->write(os); } diff --git a/src/crab/abstract_domain.hpp b/src/crab/abstract_domain.hpp index 7b5f297ab..a35887816 100644 --- a/src/crab/abstract_domain.hpp +++ b/src/crab/abstract_domain.hpp @@ -31,18 +31,18 @@ class abstract_domain_t { virtual std::unique_ptr operator&(const abstract_domain_concept& abs) const = 0; virtual std::unique_ptr widen(const abstract_domain_concept& abs, bool) = 0; virtual std::unique_ptr narrow(const abstract_domain_concept& abs) const = 0; - virtual void operator()(const basic_block_t&, int) = 0; - virtual void operator()(const Undefined&, location_t, int) = 0; - virtual void operator()(const Bin&, location_t, int) = 0; - virtual void operator()(const Un&, location_t, int) = 0; - virtual void operator()(const LoadMapFd&, location_t, int) = 0; - virtual void operator()(const Call&, location_t, int) = 0; - virtual void operator()(const Exit&, location_t, int) = 0; - virtual void operator()(const Jmp&, location_t, int) = 0; - virtual void operator()(const Mem&, location_t, int) = 0; - virtual void operator()(const Packet&, location_t, int) = 0; - virtual void operator()(const Assume&, location_t, int) = 0; - virtual void operator()(const Assert&, location_t, int) = 0; + virtual void operator()(const basic_block_t&, int print = 0) = 0; + virtual void operator()(const Undefined&, location_t) = 0; + virtual void operator()(const Bin&, location_t) = 0; + virtual void operator()(const Un&, location_t) = 0; + virtual void operator()(const LoadMapFd&, location_t) = 0; + virtual void operator()(const Call&, location_t) = 0; + virtual void operator()(const Exit&, location_t) = 0; + virtual void operator()(const Jmp&, location_t) = 0; + virtual void operator()(const Mem&, location_t) = 0; + virtual void operator()(const Packet&, location_t) = 0; + virtual void operator()(const Assume&, location_t) = 0; + virtual void operator()(const Assert&, location_t) = 0; virtual void write(std::ostream& os) const = 0; /* These operations are not very conventional for an abstract @@ -75,17 +75,17 @@ class abstract_domain_t { std::unique_ptr widen(const abstract_domain_concept& abs, bool) override; std::unique_ptr narrow(const abstract_domain_concept& abs) const override; void operator()(const basic_block_t& bb, int print = 0) override; - void operator()(const Undefined& s, location_t loc = boost::none, int print = 0) override; - void operator()(const Bin& s, location_t loc = boost::none, int print = 0) override; - void operator()(const Un& s, location_t loc = boost::none, int print = 0) override; - void operator()(const LoadMapFd& s, location_t loc = boost::none, int print = 0) override; - void operator()(const Call& s, location_t loc = boost::none, int print = 0) override; - void operator()(const Exit& s, location_t loc = boost::none, int print = 0) override; - void operator()(const Jmp& s, location_t loc = boost::none, int print = 0) override; - void operator()(const Mem& s, location_t loc = boost::none, int print = 0) override; - void operator()(const Packet& s, location_t loc = boost::none, int print = 0) override; - void operator()(const Assume& s, location_t loc = boost::none, int print = 0) override; - void operator()(const Assert& s, location_t loc = boost::none, int print = 0) override; + void operator()(const Undefined& s, location_t loc = boost::none) override; + void operator()(const Bin& s, location_t loc = boost::none) override; + void operator()(const Un& s, location_t loc = boost::none) override; + void operator()(const LoadMapFd& s, location_t loc = boost::none) override; + void operator()(const Call& s, location_t loc = boost::none) override; + void operator()(const Exit& s, location_t loc = boost::none) override; + void operator()(const Jmp& s, location_t loc = boost::none) override; + void operator()(const Mem& s, location_t loc = boost::none) override; + void operator()(const Packet& s, location_t loc = boost::none) override; + void operator()(const Assume& s, location_t loc = boost::none) override; + void operator()(const Assert& s, location_t loc = boost::none) override; void write(std::ostream& os) const override; void initialize_loop_counter(const label_t) override; crab::bound_t get_loop_count_upper_bound() override; @@ -118,17 +118,17 @@ class abstract_domain_t { abstract_domain_t widen(const abstract_domain_t& abs, bool); abstract_domain_t narrow(const abstract_domain_t& abs) const; void operator()(const basic_block_t& bb, int print = 0); - void operator()(const Undefined& s, location_t loc = boost::none, int print = 0); - void operator()(const Bin& s, location_t loc = boost::none, int print = 0); - void operator()(const Un& s, location_t loc = boost::none, int print = 0); - void operator()(const LoadMapFd& s, location_t loc = boost::none, int print = 0); - void operator()(const Call& s, location_t loc = boost::none, int print = 0); - void operator()(const Exit& s, location_t loc = boost::none, int print = 0); - void operator()(const Jmp& s, location_t loc = boost::none, int print = 0); - void operator()(const Mem& s, location_t loc = boost::none, int print = 0); - void operator()(const Packet& s, location_t loc = boost::none, int print = 0); - void operator()(const Assume& s, location_t loc = boost::none, int print = 0); - void operator()(const Assert& s, location_t loc = boost::none, int print = 0); + void operator()(const Undefined& s, location_t loc = boost::none); + void operator()(const Bin& s, location_t loc = boost::none); + void operator()(const Un& s, location_t loc = boost::none); + void operator()(const LoadMapFd& s, location_t loc = boost::none); + void operator()(const Call& s, location_t loc = boost::none); + void operator()(const Exit& s, location_t loc = boost::none); + void operator()(const Jmp& s, location_t loc = boost::none); + void operator()(const Mem& s, location_t loc = boost::none); + void operator()(const Packet& s, location_t loc = boost::none); + void operator()(const Assume& s, location_t loc = boost::none); + void operator()(const Assert& s, location_t loc = boost::none); void write(std::ostream& os) const; crab::bound_t get_loop_count_upper_bound(); void initialize_loop_counter(const label_t); diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index 039849bec..c4aa65799 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -1417,7 +1417,7 @@ void ebpf_domain_t::check_access_shared(NumAbsDomain& inv, const linear_expressi require(inv, ub <= region_size, std::string("Upper bound must be at most ") + region_size.name()); } -void ebpf_domain_t::operator()(const Assume& s, location_t loc, int print) { +void ebpf_domain_t::operator()(const Assume& s, location_t loc) { Condition cond = s.cond; auto dst = reg_pack(cond.left); if (std::holds_alternative(cond.right)) { @@ -1450,9 +1450,9 @@ void ebpf_domain_t::operator()(const Assume& s, location_t loc, int print) { } } -void ebpf_domain_t::operator()(const Undefined& a, location_t loc, int print) {} +void ebpf_domain_t::operator()(const Undefined& a, location_t loc) {} -void ebpf_domain_t::operator()(const Un& stmt, location_t loc, int print) { +void ebpf_domain_t::operator()(const Un& stmt, location_t loc) { auto dst = reg_pack(stmt.dst); auto swap_endianness = [&](variable_t v, auto input, const auto& be_or_le) { if (m_inv.entail(type_is_number(stmt.dst))) { @@ -1516,11 +1516,11 @@ void ebpf_domain_t::operator()(const Un& stmt, location_t loc, int print) { } } -void ebpf_domain_t::operator()(const Exit& a, location_t loc, int print) {} +void ebpf_domain_t::operator()(const Exit& a, location_t loc) {} -void ebpf_domain_t::operator()(const Jmp& a, location_t loc, int print) {} +void ebpf_domain_t::operator()(const Jmp& a, location_t loc) {} -void ebpf_domain_t::operator()(const Comparable& s, location_t loc, int print) { +void ebpf_domain_t::operator()(const Comparable& s, location_t loc) { using namespace crab::dsl_syntax; if (type_inv.same_type(m_inv, s.r1, s.r2)) { // Same type. If both are numbers, that's okay. Otherwise: @@ -1541,12 +1541,12 @@ void ebpf_domain_t::operator()(const Comparable& s, location_t loc, int print) { }; } -void ebpf_domain_t::operator()(const Addable& s, location_t loc, int print) { +void ebpf_domain_t::operator()(const Addable& s, location_t loc) { if (!type_inv.implies_type(m_inv, type_is_pointer(reg_pack(s.ptr)), type_is_number(s.num))) require(m_inv, linear_constraint_t::FALSE(), "Only numbers can be added to pointers"); } -void ebpf_domain_t::operator()(const ValidDivisor& s, location_t loc, int print) { +void ebpf_domain_t::operator()(const ValidDivisor& s, location_t loc) { using namespace crab::dsl_syntax; auto reg = reg_pack(s.reg); if (!type_inv.implies_type(m_inv, type_is_pointer(reg), type_is_number(s.reg))) @@ -1557,17 +1557,17 @@ void ebpf_domain_t::operator()(const ValidDivisor& s, location_t loc, int print) } } -void ebpf_domain_t::operator()(const ValidStore& s, location_t loc, int print) { +void ebpf_domain_t::operator()(const ValidStore& s, location_t loc) { if (!type_inv.implies_type(m_inv, type_is_not_stack(reg_pack(s.mem)), type_is_number(s.val))) require(m_inv, linear_constraint_t::FALSE(), "Only numbers can be stored to externally-visible regions"); } -void ebpf_domain_t::operator()(const TypeConstraint& s, location_t loc, int print) { +void ebpf_domain_t::operator()(const TypeConstraint& s, location_t loc) { if (!type_inv.is_in_group(m_inv, s.reg, s.types)) require(m_inv, linear_constraint_t::FALSE(), "Invalid type"); } -void ebpf_domain_t::operator()(const FuncConstraint& s, location_t loc, int print) { +void ebpf_domain_t::operator()(const FuncConstraint& s, location_t loc) { // Look up the helper function id. const reg_pack_t& reg = reg_pack(s.reg); auto src_interval = m_inv.eval_interval(reg.svalue); @@ -1589,7 +1589,7 @@ void ebpf_domain_t::operator()(const FuncConstraint& s, location_t loc, int prin require(m_inv, linear_constraint_t::FALSE(), "callx helper function id is not a valid singleton"); } -void ebpf_domain_t::operator()(const ValidSize& s, location_t loc, int print) { +void ebpf_domain_t::operator()(const ValidSize& s, location_t loc) { using namespace crab::dsl_syntax; auto r = reg_pack(s.reg); require(m_inv, s.can_be_zero ? r.svalue >= 0 : r.svalue > 0, "Invalid size"); @@ -1698,7 +1698,7 @@ crab::interval_t ebpf_domain_t::get_map_max_entries(const Reg& map_fd_reg) const return result; } -void ebpf_domain_t::operator()(const ValidMapKeyValue& s, location_t loc, int print) { +void ebpf_domain_t::operator()(const ValidMapKeyValue& s, location_t loc) { using namespace crab::dsl_syntax; auto fd_type = get_map_type(s.map_fd_reg); @@ -1764,7 +1764,7 @@ void ebpf_domain_t::operator()(const ValidMapKeyValue& s, location_t loc, int pr }); } -void ebpf_domain_t::operator()(const ValidAccess& s, location_t loc, int print) { +void ebpf_domain_t::operator()(const ValidAccess& s, location_t loc) { using namespace crab::dsl_syntax; bool is_comparison_check = s.width == (Value)Imm{0}; @@ -1850,13 +1850,13 @@ void ebpf_domain_t::operator()(const ValidAccess& s, location_t loc, int print) }); } -void ebpf_domain_t::operator()(const ZeroCtxOffset& s, location_t loc, int print) { +void ebpf_domain_t::operator()(const ZeroCtxOffset& s, location_t loc) { using namespace crab::dsl_syntax; auto reg = reg_pack(s.reg); require(m_inv, reg.ctx_offset == 0, "Nonzero context offset"); } -void ebpf_domain_t::operator()(const Assert& stmt, location_t loc, int print) { +void ebpf_domain_t::operator()(const Assert& stmt, location_t loc) { if (check_require || thread_local_options.assume_assertions) { this->current_assertion = to_string(stmt.cst); std::visit(*this, stmt.cst); @@ -1864,7 +1864,7 @@ void ebpf_domain_t::operator()(const Assert& stmt, location_t loc, int print) { } } -void ebpf_domain_t::operator()(const Packet& a, location_t loc, int print) { +void ebpf_domain_t::operator()(const Packet& a, location_t loc) { auto reg = reg_pack(R0_RETURN_VALUE); Reg r0_reg{(uint8_t)R0_RETURN_VALUE}; type_inv.assign_type(m_inv, r0_reg, T_NUM); @@ -2119,7 +2119,7 @@ void ebpf_domain_t::do_store_stack(NumAbsDomain& inv, const number_t& width, con } } -void ebpf_domain_t::operator()(const Mem& b, location_t loc, int print) { +void ebpf_domain_t::operator()(const Mem& b, location_t loc) { if (m_inv.is_bottom()) return; if (std::holds_alternative(b.value)) { @@ -2175,7 +2175,7 @@ static Bin atomic_to_bin(const Atomic& a) { return bin; } -void ebpf_domain_t::operator()(const Atomic& a, location_t loc, int print) { +void ebpf_domain_t::operator()(const Atomic& a, location_t loc) { if (m_inv.is_bottom()) return; if (!m_inv.entail(type_is_pointer(reg_pack(a.access.basereg))) || @@ -2224,7 +2224,7 @@ void ebpf_domain_t::operator()(const Atomic& a, location_t loc, int print) { type_inv.havoc_type(m_inv, r11); } -void ebpf_domain_t::operator()(const Call& call, location_t loc, int print) { +void ebpf_domain_t::operator()(const Call& call, location_t loc) { using namespace crab::dsl_syntax; if (m_inv.is_bottom()) return; @@ -2321,7 +2321,7 @@ void ebpf_domain_t::operator()(const Call& call, location_t loc, int print) { } } -void ebpf_domain_t::operator()(const Callx& callx, location_t loc, int print) { +void ebpf_domain_t::operator()(const Callx& callx, location_t loc) { using namespace crab::dsl_syntax; if (m_inv.is_bottom()) return; @@ -2356,7 +2356,7 @@ void ebpf_domain_t::do_load_mapfd(const Reg& dst_reg, int mapfd, bool maybe_null assign_valid_ptr(dst_reg, maybe_null); } -void ebpf_domain_t::operator()(const LoadMapFd& ins, location_t loc, int print) { do_load_mapfd(ins.dst, ins.mapfd, false); } +void ebpf_domain_t::operator()(const LoadMapFd& ins, location_t loc) { do_load_mapfd(ins.dst, ins.mapfd, false); } void ebpf_domain_t::assign_valid_ptr(const Reg& dst_reg, bool maybe_null) { using namespace crab::dsl_syntax; @@ -2578,7 +2578,7 @@ void ebpf_domain_t::ashr(const Reg& dst_reg, const linear_expression_t& right_sv havoc_offsets(dst_reg); } -void ebpf_domain_t::operator()(const Bin& bin, location_t loc, int print) { +void ebpf_domain_t::operator()(const Bin& bin, location_t loc) { using namespace crab::dsl_syntax; auto dst = reg_pack(bin.dst); @@ -3010,7 +3010,7 @@ bound_t ebpf_domain_t::get_loop_count_upper_bound() { return ub; } -void ebpf_domain_t::operator()(const IncrementLoopCounter& ins, location_t loc, int print) { +void ebpf_domain_t::operator()(const IncrementLoopCounter& ins, location_t loc) { this->add(variable_t::loop_counter(to_string(ins.name)), 1); } } // namespace crab diff --git a/src/crab/ebpf_domain.hpp b/src/crab/ebpf_domain.hpp index 6ace2e9a9..d5b9928a5 100644 --- a/src/crab/ebpf_domain.hpp +++ b/src/crab/ebpf_domain.hpp @@ -58,30 +58,30 @@ class ebpf_domain_t final { // abstract transformers void operator()(const basic_block_t& bb, int print = 0); - void operator()(const Addable&, location_t loc = boost::none, int print = 0); - void operator()(const Assert&, location_t loc = boost::none, int print = 0); - void operator()(const Assume&, location_t loc = boost::none, int print = 0); - void operator()(const Bin&, location_t loc = boost::none, int print = 0); - void operator()(const Call&, location_t loc = boost::none, int print = 0); - void operator()(const Callx&, location_t loc = boost::none, int print = 0); - void operator()(const Comparable&, location_t loc = boost::none, int print = 0); - void operator()(const Exit&, location_t loc = boost::none, int print = 0); - void operator()(const FuncConstraint&, location_t loc = boost::none, int print = 0); - void operator()(const Jmp&, location_t loc = boost::none, int print = 0); - void operator()(const LoadMapFd&, location_t loc = boost::none, int print = 0); - void operator()(const Atomic&, location_t loc = boost::none, int print = 0); - void operator()(const Mem&, location_t loc = boost::none, int print = 0); - void operator()(const ValidDivisor&, location_t loc = boost::none, int print = 0); - void operator()(const Packet&, location_t loc = boost::none, int print = 0); - void operator()(const TypeConstraint&, location_t loc = boost::none, int print = 0); - void operator()(const Un&, location_t loc = boost::none, int print = 0); - void operator()(const Undefined&, location_t loc = boost::none, int print = 0); - void operator()(const ValidAccess&, location_t loc = boost::none, int print = 0); - void operator()(const ValidMapKeyValue&, location_t loc = boost::none, int print = 0); - void operator()(const ValidSize&, location_t loc = boost::none, int print = 0); - void operator()(const ValidStore&, location_t loc = boost::none, int print = 0); - void operator()(const ZeroCtxOffset&, location_t loc = boost::none, int print = 0); - void operator()(const IncrementLoopCounter&, location_t loc = boost::none, int print = 0); + void operator()(const Addable&, location_t loc = boost::none); + void operator()(const Assert&, location_t loc = boost::none); + void operator()(const Assume&, location_t loc = boost::none); + void operator()(const Bin&, location_t loc = boost::none); + void operator()(const Call&, location_t loc = boost::none); + void operator()(const Callx&, location_t loc = boost::none); + void operator()(const Comparable&, location_t loc = boost::none); + void operator()(const Exit&, location_t loc = boost::none); + void operator()(const FuncConstraint&, location_t loc = boost::none); + void operator()(const Jmp&, location_t loc = boost::none); + void operator()(const LoadMapFd&, location_t loc = boost::none); + void operator()(const Atomic&, location_t loc = boost::none); + void operator()(const Mem&, location_t loc = boost::none); + void operator()(const ValidDivisor&, location_t loc = boost::none); + void operator()(const Packet&, location_t loc = boost::none); + void operator()(const TypeConstraint&, location_t loc = boost::none); + void operator()(const Un&, location_t loc = boost::none); + void operator()(const Undefined&, location_t loc = boost::none); + void operator()(const ValidAccess&, location_t loc = boost::none); + void operator()(const ValidMapKeyValue&, location_t loc = boost::none); + void operator()(const ValidSize&, location_t loc = boost::none); + void operator()(const ValidStore&, location_t loc = boost::none); + void operator()(const ZeroCtxOffset&, location_t loc = boost::none); + void operator()(const IncrementLoopCounter&, location_t loc = boost::none); // write operation is important to keep in ebpf_domain_t because of the parametric abstract domain void write(std::ostream& o) const; diff --git a/src/crab/interval_prop_domain.cpp b/src/crab/interval_prop_domain.cpp index 8dd8fcc29..3006c98fb 100644 --- a/src/crab/interval_prop_domain.cpp +++ b/src/crab/interval_prop_domain.cpp @@ -354,7 +354,7 @@ interval_prop_domain_t interval_prop_domain_t::setup_entry() { return cp; } -void interval_prop_domain_t::operator()(const Un& u, location_t loc, int print) { +void interval_prop_domain_t::operator()(const Un& u, location_t loc) { auto swap_endianness = [&](interval_t&& v, auto input, const auto& be_or_le) { if (std::optional n = v.singleton()) { if (n->fits_cast_to_int64()) { @@ -406,11 +406,11 @@ void interval_prop_domain_t::operator()(const Un& u, location_t loc, int print) } } -void interval_prop_domain_t::operator()(const LoadMapFd &u, location_t loc, int print) { +void interval_prop_domain_t::operator()(const LoadMapFd &u, location_t loc) { m_registers_interval_values -= u.dst.v; } -void interval_prop_domain_t::operator()(const ValidSize& s, location_t loc, int print) { +void interval_prop_domain_t::operator()(const ValidSize& s, location_t loc) { // nothing to do here } @@ -435,7 +435,7 @@ void interval_prop_domain_t::do_call(const Call& u, const stack_cells_t& store_i m_registers_interval_values.scratch_caller_saved_registers(); } -void interval_prop_domain_t::operator()(const Packet& u, location_t loc, int print) { +void interval_prop_domain_t::operator()(const Packet& u, location_t loc) { auto r0 = register_t{R0_RETURN_VALUE}; m_registers_interval_values.insert(r0, loc, interval_t::top()); m_registers_interval_values.scratch_caller_saved_registers(); @@ -688,7 +688,7 @@ void interval_prop_domain_t::assume_cst(Condition::Op op, bool is64, register_t } } -void interval_prop_domain_t::operator()(const Assume& s, location_t loc, int print) { +void interval_prop_domain_t::operator()(const Assume& s, location_t loc) { // nothing to do here } @@ -1089,59 +1089,59 @@ void interval_prop_domain_t::check_valid_access(const ValidAccess& s, interval_t } } -void interval_prop_domain_t::operator()(const ValidAccess& s, location_t loc, int print) { +void interval_prop_domain_t::operator()(const ValidAccess& s, location_t loc) { // nothing to do here } -void interval_prop_domain_t::operator()(const Undefined& u, location_t loc, int print) { +void interval_prop_domain_t::operator()(const Undefined& u, location_t loc) { // nothing to do here } -void interval_prop_domain_t::operator()(const Bin& b, location_t loc, int print) { +void interval_prop_domain_t::operator()(const Bin& b, location_t loc) { // nothing to do here } -void interval_prop_domain_t::operator()(const Call&, location_t loc, int print) { +void interval_prop_domain_t::operator()(const Call&, location_t loc) { // nothing to do here } -void interval_prop_domain_t::operator()(const Exit&, location_t loc, int print) { +void interval_prop_domain_t::operator()(const Exit&, location_t loc) { // nothing to do here } -void interval_prop_domain_t::operator()(const Jmp&, location_t loc, int print) { +void interval_prop_domain_t::operator()(const Jmp&, location_t loc) { // nothing to do here } -void interval_prop_domain_t::operator()(const Mem&, location_t loc, int print) { +void interval_prop_domain_t::operator()(const Mem&, location_t loc) { // nothing to do here } -void interval_prop_domain_t::operator()(const Assert&, location_t loc, int print) { +void interval_prop_domain_t::operator()(const Assert&, location_t loc) { // nothing to do here } -void interval_prop_domain_t::operator()(const Comparable&, location_t loc, int print) { +void interval_prop_domain_t::operator()(const Comparable&, location_t loc) { // nothing to do here } -void interval_prop_domain_t::operator()(const Addable&, location_t loc, int print) { +void interval_prop_domain_t::operator()(const Addable&, location_t loc) { // nothing to do here } -void interval_prop_domain_t::operator()(const ValidStore&, location_t loc, int print) { +void interval_prop_domain_t::operator()(const ValidStore&, location_t loc) { // nothing to do here } -void interval_prop_domain_t::operator()(const TypeConstraint&, location_t loc, int print) { +void interval_prop_domain_t::operator()(const TypeConstraint&, location_t loc) { // nothing to do here } -void interval_prop_domain_t::operator()(const ValidMapKeyValue&, location_t loc, int print) { +void interval_prop_domain_t::operator()(const ValidMapKeyValue&, location_t loc) { // nothing to do here } -void interval_prop_domain_t::operator()(const ZeroCtxOffset&, location_t loc, int print) { +void interval_prop_domain_t::operator()(const ZeroCtxOffset&, location_t loc) { // nothing to do here } diff --git a/src/crab/interval_prop_domain.hpp b/src/crab/interval_prop_domain.hpp index 2db62b793..836cc6e48 100644 --- a/src/crab/interval_prop_domain.hpp +++ b/src/crab/interval_prop_domain.hpp @@ -117,29 +117,29 @@ class interval_prop_domain_t final { void operator-=(register_t reg) { m_registers_interval_values -= reg; } //// abstract transformers - void operator()(const Undefined&, location_t loc = boost::none, int print = 0); - void operator()(const Bin&, location_t loc = boost::none, int print = 0); - void operator()(const Un&, location_t loc = boost::none, int print = 0); - void operator()(const LoadMapFd&, location_t loc = boost::none, int print = 0); - void operator()(const Atomic&, location_t loc = boost::none, int print = 0) {} - void operator()(const Call&, location_t loc = boost::none, int print = 0); - void operator()(const Callx&, location_t loc = boost::none, int print = 0) {} - void operator()(const Exit&, location_t loc = boost::none, int print = 0); - void operator()(const Jmp&, location_t loc = boost::none, int print = 0); - void operator()(const Mem&, location_t loc = boost::none, int print = 0); - void operator()(const Packet&, location_t loc = boost::none, int print = 0); - void operator()(const Assume&, location_t loc = boost::none, int print = 0); - void operator()(const Assert&, location_t loc = boost::none, int print = 0); - void operator()(const ValidAccess&, location_t loc = boost::none, int print = 0); - void operator()(const Comparable&, location_t loc = boost::none, int print = 0); - void operator()(const Addable&, location_t loc = boost::none, int print = 0); - void operator()(const ValidStore&, location_t loc = boost::none, int print = 0); - void operator()(const TypeConstraint&, location_t loc = boost::none, int print = 0); - void operator()(const ValidSize&, location_t loc = boost::none, int print = 0); - void operator()(const ValidMapKeyValue&, location_t loc = boost::none, int print = 0); - void operator()(const ZeroCtxOffset&, location_t loc = boost::none, int print = 0); - void operator()(const FuncConstraint&, location_t loc = boost::none, int print = 0) {} - void operator()(const IncrementLoopCounter&, location_t loc = boost::none, int print = 0) {} + void operator()(const Undefined&, location_t loc = boost::none); + void operator()(const Bin&, location_t loc = boost::none); + void operator()(const Un&, location_t loc = boost::none); + void operator()(const LoadMapFd&, location_t loc = boost::none); + void operator()(const Atomic&, location_t loc = boost::none) {} + void operator()(const Call&, location_t loc = boost::none); + void operator()(const Callx&, location_t loc = boost::none) {} + void operator()(const Exit&, location_t loc = boost::none); + void operator()(const Jmp&, location_t loc = boost::none); + void operator()(const Mem&, location_t loc = boost::none); + void operator()(const Packet&, location_t loc = boost::none); + void operator()(const Assume&, location_t loc = boost::none); + void operator()(const Assert&, location_t loc = boost::none); + void operator()(const ValidAccess&, location_t loc = boost::none); + void operator()(const Comparable&, location_t loc = boost::none); + void operator()(const Addable&, location_t loc = boost::none); + void operator()(const ValidStore&, location_t loc = boost::none); + void operator()(const TypeConstraint&, location_t loc = boost::none); + void operator()(const ValidSize&, location_t loc = boost::none); + void operator()(const ValidMapKeyValue&, location_t loc = boost::none); + void operator()(const ZeroCtxOffset&, location_t loc = boost::none); + void operator()(const FuncConstraint&, location_t loc = boost::none) {} + void operator()(const IncrementLoopCounter&, location_t loc = boost::none) {} void operator()(const basic_block_t& bb, int print = 0); void write(std::ostream& os) const; crab::bound_t get_loop_count_upper_bound(); diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index 1c9e139f0..bc7962700 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -565,7 +565,7 @@ void offset_domain_t::initialize_loop_counter(const label_t& label) { string_invariant offset_domain_t::to_set() { return string_invariant{}; } -void offset_domain_t::operator()(const Assume &b, location_t loc, int print) { +void offset_domain_t::operator()(const Assume &b, location_t loc) { Condition cond = b.cond; if (cond.op == Condition::Op::LE) { if (std::holds_alternative(cond.right)) { @@ -737,19 +737,19 @@ interval_t offset_domain_t::do_bin(const Bin &bin, return interval_t::bottom(); } -void offset_domain_t::operator()(const Bin& bin, location_t loc, int print) { +void offset_domain_t::operator()(const Bin& bin, location_t loc) { // nothing to do here } -void offset_domain_t::operator()(const Undefined& u, location_t loc, int print) { +void offset_domain_t::operator()(const Undefined& u, location_t loc) { // nothing to do here } -void offset_domain_t::operator()(const Un& u, location_t loc, int print) { +void offset_domain_t::operator()(const Un& u, location_t loc) { m_reg_state -= u.dst.v; } -void offset_domain_t::operator()(const LoadMapFd& u, location_t loc, int print) { +void offset_domain_t::operator()(const LoadMapFd& u, location_t loc) { m_reg_state -= u.dst.v; } @@ -768,53 +768,53 @@ void offset_domain_t::do_call(const Call& u, const stack_cells_t& cells, locatio } } -void offset_domain_t::operator()(const Call& u, location_t loc, int print) { +void offset_domain_t::operator()(const Call& u, location_t loc) { // nothing to do here } -void offset_domain_t::operator()(const Exit& u, location_t loc, int print) {} +void offset_domain_t::operator()(const Exit& u, location_t loc) {} -void offset_domain_t::operator()(const Jmp& u, location_t loc, int print) { +void offset_domain_t::operator()(const Jmp& u, location_t loc) { // nothing to do here } -void offset_domain_t::operator()(const Packet& u, location_t loc, int print) { +void offset_domain_t::operator()(const Packet& u, location_t loc) { m_reg_state -= register_t{R0_RETURN_VALUE}; m_reg_state.scratch_caller_saved_registers(); } -void offset_domain_t::operator()(const ValidDivisor& u, location_t loc, int print) { +void offset_domain_t::operator()(const ValidDivisor& u, location_t loc) { /* WARNING: This operation is not implemented yet. */ } -void offset_domain_t::operator()(const ValidAccess& u, location_t loc, int print) { +void offset_domain_t::operator()(const ValidAccess& u, location_t loc) { // nothing to do here } -void offset_domain_t::operator()(const Comparable& u, location_t loc, int print) { +void offset_domain_t::operator()(const Comparable& u, location_t loc) { // nothing to do here } -void offset_domain_t::operator()(const Addable& u, location_t loc, int print) { +void offset_domain_t::operator()(const Addable& u, location_t loc) { // nothing to do here } -void offset_domain_t::operator()(const ValidStore& u, location_t loc, int print) { +void offset_domain_t::operator()(const ValidStore& u, location_t loc) { // nothing to do here } -void offset_domain_t::operator()(const TypeConstraint& u, location_t loc, int print) { +void offset_domain_t::operator()(const TypeConstraint& u, location_t loc) { // nothing to do here } -void offset_domain_t::operator()(const ValidSize& u, location_t loc, int print) { +void offset_domain_t::operator()(const ValidSize& u, location_t loc) { /* WARNING: This operation is not implemented yet. */ } -void offset_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int print) { +void offset_domain_t::operator()(const ValidMapKeyValue& u, location_t loc) { /* WARNING: This operation is not implemented yet. */ } -void offset_domain_t::operator()(const ZeroCtxOffset&, location_t loc, int print) { +void offset_domain_t::operator()(const ZeroCtxOffset&, location_t loc) { // nothing to do here } @@ -877,7 +877,7 @@ void offset_domain_t::check_valid_access(const ValidAccess& s, //std::cout << "type_error: valid access assert fail\n"; } -void offset_domain_t::operator()(const Assert &u, location_t loc, int print) { +void offset_domain_t::operator()(const Assert &u, location_t loc) { // nothing to do here } @@ -949,7 +949,7 @@ void offset_domain_t::do_load(const Mem& b, const register_t& target_register, } } -void offset_domain_t::operator()(const Mem& b, location_t loc, int print) { +void offset_domain_t::operator()(const Mem& b, location_t loc) { // nothing to do here } diff --git a/src/crab/offset_domain.hpp b/src/crab/offset_domain.hpp index 595f3835b..5710f50cd 100644 --- a/src/crab/offset_domain.hpp +++ b/src/crab/offset_domain.hpp @@ -239,30 +239,30 @@ class offset_domain_t final { void operator-=(register_t reg) { m_reg_state -= reg; } //// abstract transformers - void operator()(const Undefined&, location_t loc = boost::none, int print = 0); - void operator()(const Bin&, location_t loc = boost::none, int print = 0); - void operator()(const Un&, location_t loc = boost::none, int print = 0); - void operator()(const LoadMapFd&, location_t loc = boost::none, int print = 0); - void operator()(const Atomic&, location_t loc = boost::none, int print = 0) {} - void operator()(const Call&, location_t loc = boost::none, int print = 0); - void operator()(const Callx&, location_t loc = boost::none, int print = 0); - void operator()(const Exit&, location_t loc = boost::none, int print = 0); - void operator()(const Jmp&, location_t loc = boost::none, int print = 0); - void operator()(const Mem&, location_t loc = boost::none, int print = 0); - void operator()(const Packet&, location_t loc = boost::none, int print = 0); - void operator()(const Assume&, location_t loc = boost::none, int print = 0); - void operator()(const Assert&, location_t loc = boost::none, int print = 0); - void operator()(const ValidAccess&, location_t loc = boost::none, int print = 0); - void operator()(const Comparable&, location_t loc = boost::none, int print = 0); - void operator()(const Addable&, location_t loc = boost::none, int print = 0); - void operator()(const ValidStore&, location_t loc = boost::none, int print = 0); - void operator()(const TypeConstraint&, location_t loc = boost::none, int print = 0); - void operator()(const ValidSize&, location_t loc = boost::none, int print = 0); - void operator()(const ValidMapKeyValue&, location_t loc = boost::none, int print = 0); - void operator()(const ZeroCtxOffset&, location_t loc = boost::none, int print = 0); - void operator()(const ValidDivisor&, location_t loc = boost::none, int print = 0); - void operator()(const FuncConstraint& s, location_t loc = boost::none, int print = 0) {}; - void operator()(const IncrementLoopCounter&, location_t loc = boost::none, int print = 0) {}; + void operator()(const Undefined&, location_t loc = boost::none); + void operator()(const Bin&, location_t loc = boost::none); + void operator()(const Un&, location_t loc = boost::none); + void operator()(const LoadMapFd&, location_t loc = boost::none); + void operator()(const Atomic&, location_t loc = boost::none) {} + void operator()(const Call&, location_t loc = boost::none); + void operator()(const Callx&, location_t loc = boost::none); + void operator()(const Exit&, location_t loc = boost::none); + void operator()(const Jmp&, location_t loc = boost::none); + void operator()(const Mem&, location_t loc = boost::none); + void operator()(const Packet&, location_t loc = boost::none); + void operator()(const Assume&, location_t loc = boost::none); + void operator()(const Assert&, location_t loc = boost::none); + void operator()(const ValidAccess&, location_t loc = boost::none); + void operator()(const Comparable&, location_t loc = boost::none); + void operator()(const Addable&, location_t loc = boost::none); + void operator()(const ValidStore&, location_t loc = boost::none); + void operator()(const TypeConstraint&, location_t loc = boost::none); + void operator()(const ValidSize&, location_t loc = boost::none); + void operator()(const ValidMapKeyValue&, location_t loc = boost::none); + void operator()(const ZeroCtxOffset&, location_t loc = boost::none); + void operator()(const ValidDivisor&, location_t loc = boost::none); + void operator()(const FuncConstraint& s, location_t loc = boost::none) {}; + void operator()(const IncrementLoopCounter&, location_t loc = boost::none) {}; void operator()(const basic_block_t& bb, int print = 0); void write(std::ostream& os) const; std::string domain_name() const; diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index 7ec4ec3a9..9411376a9 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -392,29 +392,30 @@ string_invariant region_domain_t::to_set() { return string_invariant{}; } -void region_domain_t::operator()(const Undefined &u, location_t loc, int print) {} +void region_domain_t::operator()(const Undefined &u, location_t loc) {} -void region_domain_t::operator()(const Exit &u, location_t loc, int print) {} +void region_domain_t::operator()(const Exit &u, location_t loc) {} -void region_domain_t::operator()(const Jmp &u, location_t loc, int print) {} +void region_domain_t::operator()(const Jmp &u, location_t loc) {} -void region_domain_t::operator()(const Assume& u, location_t loc, int print) { + +void region_domain_t::operator()(const Assume& u, location_t loc) { // nothing to do here } -void region_domain_t::operator()(const Assert& u, location_t loc, int print) { +void region_domain_t::operator()(const Assert& u, location_t loc) { // nothing to do here } -void region_domain_t::operator()(const Comparable& u, location_t loc, int print) { +void region_domain_t::operator()(const Comparable& u, location_t loc) { // nothing to do here } -void region_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int print) { +void region_domain_t::operator()(const ValidMapKeyValue& u, location_t loc) { // nothing to do here } -void region_domain_t::operator()(const ZeroCtxOffset& u, location_t loc, int print) { +void region_domain_t::operator()(const ZeroCtxOffset& u, location_t loc) { auto maybe_ptr_or_mapfd = m_registers.find(u.reg.v); if (is_ctx_ptr(maybe_ptr_or_mapfd)) { auto ctx_ptr = std::get(*maybe_ptr_or_mapfd); @@ -428,15 +429,15 @@ void region_domain_t::operator()(const basic_block_t& bb, int print) { // nothing to do here } -void region_domain_t::operator()(const Un& u, location_t loc, int print) { +void region_domain_t::operator()(const Un& u, location_t loc) { m_registers -= u.dst.v; } -void region_domain_t::operator()(const ValidDivisor& u, location_t loc, int print) { +void region_domain_t::operator()(const ValidDivisor& u, location_t loc) { // nothing to do here } -void region_domain_t::operator()(const ValidSize& u, location_t loc, int print) { +void region_domain_t::operator()(const ValidSize& u, location_t loc) { /* WARNING: The operation is not implemented yet.*/ } @@ -539,7 +540,7 @@ void region_domain_t::do_load_mapfd(const register_t& dst_reg, int mapfd, locati m_registers.insert(dst_reg, loc, type); } -void region_domain_t::operator()(const LoadMapFd &u, location_t loc, int print) { +void region_domain_t::operator()(const LoadMapFd &u, location_t loc) { do_load_mapfd((register_t)u.dst.v, u.mapfd, loc); } @@ -589,28 +590,28 @@ void region_domain_t::do_call(const Call& u, const stack_cells_t& cells, locatio } } -void region_domain_t::operator()(const Call& u, location_t loc, int print) { +void region_domain_t::operator()(const Call& u, location_t loc) { // nothing to do here } -void region_domain_t::operator()(const Callx &u, location_t loc, int print) { +void region_domain_t::operator()(const Callx &u, location_t loc) { // WARNING: Not implemented yet. } -void region_domain_t::operator()(const IncrementLoopCounter &u, location_t loc, int print) { +void region_domain_t::operator()(const IncrementLoopCounter &u, location_t loc) { // WARNING: Not implemented yet. } -void region_domain_t::operator()(const Atomic &u, location_t loc, int print) { +void region_domain_t::operator()(const Atomic &u, location_t loc) { // WARNING: Not implemented yet. } -void region_domain_t::operator()(const Packet& u, location_t loc, int print) { +void region_domain_t::operator()(const Packet& u, location_t loc) { m_registers -= register_t{R0_RETURN_VALUE}; m_registers.scratch_caller_saved_registers(); } -void region_domain_t::operator()(const Addable &u, location_t loc, int print) { +void region_domain_t::operator()(const Addable &u, location_t loc) { // nothing to do here } @@ -659,11 +660,11 @@ void region_domain_t::check_valid_access(const ValidAccess &s, int width) { } -void region_domain_t::operator()(const ValidAccess &s, location_t loc, int print) { +void region_domain_t::operator()(const ValidAccess &s, location_t loc) { // nothing to do here } -void region_domain_t::operator()(const ValidStore& u, location_t loc, int print) { +void region_domain_t::operator()(const ValidStore& u, location_t loc) { // nothing to do here } @@ -684,7 +685,7 @@ region_domain_t&& region_domain_t::setup_entry() { return std::move(inv); } -void region_domain_t::operator()(const TypeConstraint& s, location_t loc, int print) { +void region_domain_t::operator()(const TypeConstraint& s, location_t loc) { auto ptr_or_mapfd_opt = m_registers.find(s.reg.v); if (ptr_or_mapfd_opt) { // it is a pointer or mapfd @@ -755,7 +756,7 @@ void region_domain_t::update_ptr_or_mapfd(ptr_or_mapfd_t&& ptr_or_mapfd, const i } } -void region_domain_t::operator()(const Bin& b, location_t loc, int print) { +void region_domain_t::operator()(const Bin& b, location_t loc) { // nothing to do here } @@ -952,7 +953,7 @@ void region_domain_t::do_load(const Mem& b, const register_t& target_register, b } } -void region_domain_t::operator()(const Mem& m, location_t loc, int print) { +void region_domain_t::operator()(const Mem& m, location_t loc) { // nothing to do here } diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index 7138d47e8..33b5beb2a 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -131,30 +131,30 @@ class region_domain_t final { void operator-=(register_t var) { m_registers -= var; } //// abstract transformers - void operator()(const Undefined&, location_t loc = boost::none, int print = 0); - void operator()(const Bin&, location_t loc = boost::none, int print = 0); - void operator()(const Un&, location_t loc = boost::none, int print = 0); - void operator()(const LoadMapFd&, location_t loc = boost::none, int print = 0); - void operator()(const Atomic&, location_t loc = boost::none, int print = 0); - void operator()(const Call&, location_t loc = boost::none, int print = 0); - void operator()(const Callx&, location_t loc = boost::none, int print = 0); - void operator()(const Exit&, location_t loc = boost::none, int print = 0); - void operator()(const Jmp&, location_t loc = boost::none, int print = 0); - void operator()(const Mem&, location_t loc = boost::none, int print = 0); - void operator()(const Packet&, location_t loc = boost::none, int print = 0); - void operator()(const Assume&, location_t loc = boost::none, int print = 0); - void operator()(const Assert&, location_t loc = boost::none, int print = 0); - void operator()(const ValidAccess&, location_t loc = boost::none, int print = 0); - void operator()(const Comparable&, location_t loc = boost::none, int print = 0); - void operator()(const Addable&, location_t loc = boost::none, int print = 0); - void operator()(const ValidStore&, location_t loc = boost::none, int print = 0); - void operator()(const TypeConstraint&, location_t loc = boost::none, int print = 0); - void operator()(const ValidSize&, location_t loc = boost::none, int print = 0); - void operator()(const ValidMapKeyValue&, location_t loc = boost::none, int print = 0); - void operator()(const ZeroCtxOffset&, location_t loc = boost::none, int print = 0); - void operator()(const ValidDivisor&, location_t loc = boost::none, int print = 0); - void operator()(const FuncConstraint& s, location_t loc = boost::none, int print = 0) {}; - void operator()(const IncrementLoopCounter&, location_t loc = boost::none, int print = 0); + void operator()(const Undefined&, location_t loc = boost::none); + void operator()(const Bin&, location_t loc = boost::none); + void operator()(const Un&, location_t loc = boost::none); + void operator()(const LoadMapFd&, location_t loc = boost::none); + void operator()(const Atomic&, location_t loc = boost::none); + void operator()(const Call&, location_t loc = boost::none); + void operator()(const Callx&, location_t loc = boost::none); + void operator()(const Exit&, location_t loc = boost::none); + void operator()(const Jmp&, location_t loc = boost::none); + void operator()(const Mem&, location_t loc = boost::none); + void operator()(const Packet&, location_t loc = boost::none); + void operator()(const Assume&, location_t loc = boost::none); + void operator()(const Assert&, location_t loc = boost::none); + void operator()(const ValidAccess&, location_t loc = boost::none); + void operator()(const Comparable&, location_t loc = boost::none); + void operator()(const Addable&, location_t loc = boost::none); + void operator()(const ValidStore&, location_t loc = boost::none); + void operator()(const TypeConstraint&, location_t loc = boost::none); + void operator()(const ValidSize&, location_t loc = boost::none); + void operator()(const ValidMapKeyValue&, location_t loc = boost::none); + void operator()(const ZeroCtxOffset&, location_t loc = boost::none); + void operator()(const ValidDivisor&, location_t loc = boost::none); + void operator()(const FuncConstraint& s, location_t loc = boost::none) {}; + void operator()(const IncrementLoopCounter&, location_t loc = boost::none); void operator()(const basic_block_t& bb, int print = 0); void write(std::ostream& o) const {} crab::bound_t get_loop_count_upper_bound(); @@ -188,7 +188,6 @@ class region_domain_t final { [[nodiscard]] std::vector get_stack_keys() const; void set_registers_to_top(); void adjust_bb_for_types(location_t); - void print_all_register_types() const; [[nodiscard]] std::vector& get_errors() { return m_errors; } void reset_errors() { m_errors.clear(); } }; // end region_domain_t diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 734794f67..7e22d03b2 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -99,28 +99,29 @@ string_invariant type_domain_t::to_set() { return string_invariant{}; } -void type_domain_t::operator()(const Undefined& u, location_t loc, int print) { +void type_domain_t::operator()(const Undefined& u, location_t loc) { // nothing to do here } -void type_domain_t::operator()(const Un& u, location_t loc, int print) { +void type_domain_t::operator()(const Un& u, location_t loc) { m_interval(u, loc); } -void type_domain_t::operator()(const LoadMapFd& u, location_t loc, int print) { +void type_domain_t::operator()(const LoadMapFd& u, location_t loc) { m_region(u, loc); m_offset(u, loc); m_interval(u, loc); } -void type_domain_t::operator()(const Atomic &u, location_t loc, int print) { +void type_domain_t::operator()(const Atomic &u, location_t loc) { // WARNING: Not implemented yet } -void type_domain_t::operator()(const IncrementLoopCounter &u, location_t loc, int print) { + +void type_domain_t::operator()(const IncrementLoopCounter &u, location_t loc) { // WARNING: Not implemented yet } -void type_domain_t::operator()(const Call& u, location_t loc, int print) { +void type_domain_t::operator()(const Call& u, location_t loc) { stack_cells_t stack_values; for (ArgPair param : u.pairs) { @@ -151,23 +152,23 @@ void type_domain_t::operator()(const Call& u, location_t loc, int print) { m_interval.do_call(u, stack_values, loc); } -void type_domain_t::operator()(const Callx &u, location_t loc, int print) { +void type_domain_t::operator()(const Callx &u, location_t loc) { // WARNING: Not implemented yet } -void type_domain_t::operator()(const Exit& u, location_t loc, int print) { +void type_domain_t::operator()(const Exit& u, location_t loc) { // nothing to do here } -void type_domain_t::operator()(const Jmp& u, location_t loc, int print) {} +void type_domain_t::operator()(const Jmp& u, location_t loc) {} -void type_domain_t::operator()(const Packet& u, location_t loc, int print) { +void type_domain_t::operator()(const Packet& u, location_t loc) { m_region(u, loc); m_offset(u, loc); m_interval(u, loc); } -void type_domain_t::operator()(const Assume& s, location_t loc, int print) { +void type_domain_t::operator()(const Assume& s, location_t loc) { Condition cond = s.cond; const auto& maybe_left_type = m_region.find_ptr_or_mapfd_type(cond.left.v); if (std::holds_alternative(cond.right)) { @@ -176,7 +177,7 @@ void type_domain_t::operator()(const Assume& s, location_t loc, int print) { if (maybe_left_type && maybe_right_type) { // both pointers if (is_packet_ptr(maybe_left_type) && is_packet_ptr(maybe_right_type)) { - m_offset(s, loc, print); + m_offset(s, loc); } } else if (!maybe_left_type && !maybe_right_type) { @@ -196,7 +197,7 @@ void type_domain_t::operator()(const Assume& s, location_t loc, int print) { } } -void type_domain_t::operator()(const ValidDivisor& u, location_t loc, int print) { +void type_domain_t::operator()(const ValidDivisor& u, location_t loc) { auto maybe_ptr_or_mapfd_reg = m_region.find_ptr_or_mapfd_type(u.reg.v); auto maybe_num_type_reg = m_interval.find_interval_value(u.reg.v); assert(!maybe_ptr_or_mapfd_reg.has_value() || !maybe_num_type_reg.has_value()); @@ -212,7 +213,7 @@ void type_domain_t::operator()(const ValidDivisor& u, location_t loc, int print) } } -void type_domain_t::operator()(const ValidAccess& s, location_t loc, int print) { +void type_domain_t::operator()(const ValidAccess& s, location_t loc) { auto reg_type = m_region.find_ptr_or_mapfd_type(s.reg.v); auto mock_interval_type = m_interval.find_interval_value(s.reg.v); auto interval_type = @@ -252,18 +253,18 @@ void type_domain_t::operator()(const ValidAccess& s, location_t loc, int print) } } -void type_domain_t::operator()(const TypeConstraint& s, location_t loc, int print) { +void type_domain_t::operator()(const TypeConstraint& s, location_t loc) { auto reg_type = m_region.find_ptr_or_mapfd_type(s.reg.v); auto mock_interval_type = m_interval.find_interval_value(s.reg.v); assert(!reg_type || !mock_interval_type); m_region(s, loc); } -void type_domain_t::operator()(const Assert& u, location_t loc, int print) { - std::visit([this, loc, print](const auto& v) { std::apply(*this, std::make_tuple(v, loc, print)); }, u.cst); +void type_domain_t::operator()(const Assert& u, location_t loc) { + std::visit([this, loc](const auto& v) { std::apply(*this, std::make_tuple(v, loc)); }, u.cst); } -void type_domain_t::operator()(const Comparable& u, location_t loc, int print) { +void type_domain_t::operator()(const Comparable& u, location_t loc) { auto maybe_ptr_or_mapfd1 = m_region.find_ptr_or_mapfd_type(u.r1.v); auto maybe_ptr_or_mapfd2 = m_region.find_ptr_or_mapfd_type(u.r2.v); @@ -286,7 +287,7 @@ void type_domain_t::operator()(const Comparable& u, location_t loc, int print) { m_errors.push_back("Non-comparable types"); } -void type_domain_t::operator()(const Addable& u, location_t loc, int print) { +void type_domain_t::operator()(const Addable& u, location_t loc) { auto maybe_ptr_or_mapfd_ptr = m_region.find_ptr_or_mapfd_type(u.ptr.v); auto maybe_ptr_or_mapfd_num = m_region.find_ptr_or_mapfd_type(u.num.v); auto maybe_num_type_ptr = m_interval.find_interval_value(u.ptr.v); @@ -303,7 +304,7 @@ void type_domain_t::operator()(const Addable& u, location_t loc, int print) { m_errors.push_back("Addable assertion fail"); } -void type_domain_t::operator()(const ValidStore& u, location_t loc, int print) { +void type_domain_t::operator()(const ValidStore& u, location_t loc) { auto maybe_ptr_or_mapfd_mem = m_region.find_ptr_or_mapfd_type(u.mem.v); auto maybe_ptr_or_mapfd_val = m_region.find_ptr_or_mapfd_type(u.val.v); auto maybe_num_type_mem = m_interval.find_interval_value(u.mem.v); @@ -320,7 +321,7 @@ void type_domain_t::operator()(const ValidStore& u, location_t loc, int print) { m_errors.push_back("Valid store assertion fail"); } -void type_domain_t::operator()(const ValidSize& u, location_t loc, int print) { +void type_domain_t::operator()(const ValidSize& u, location_t loc) { auto maybe_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(u.reg.v); auto maybe_num_type = m_interval.find_interval_value(u.reg.v); assert(!maybe_ptr_or_mapfd || !maybe_num_type); @@ -335,7 +336,7 @@ void type_domain_t::operator()(const ValidSize& u, location_t loc, int print) { m_errors.push_back("Valid Size assertion fail"); } -void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int print) { +void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc) { // TODO: move map-related function to common //auto fd_type = m_region.get_map_type(u.map_fd_reg); @@ -394,7 +395,7 @@ void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc, int pr m_errors.push_back("valid map key value assertion failed"); } -void type_domain_t::operator()(const ZeroCtxOffset& u, location_t loc, int print) { +void type_domain_t::operator()(const ZeroCtxOffset& u, location_t loc) { m_region(u, loc); } @@ -406,7 +407,7 @@ type_domain_t type_domain_t::setup_entry() { return typ; } -void type_domain_t::operator()(const Bin& bin, location_t loc, int print) { +void type_domain_t::operator()(const Bin& bin, location_t loc) { if (is_bottom()) return; std::optional src_ptr_or_mapfd; std::optional src_interval; @@ -476,7 +477,7 @@ void type_domain_t::do_mem_store(const Mem& b, std::optional tar m_offset.do_mem_store(b, target_opt, basereg_opt); } -void type_domain_t::operator()(const Mem& b, location_t loc, int print) { +void type_domain_t::operator()(const Mem& b, location_t loc) { auto basereg = b.access.basereg; auto base_ptr_or_mapfd_opt = m_region.find_ptr_or_mapfd_type(basereg.v); bool unknown_ptr = !base_ptr_or_mapfd_opt.has_value(); @@ -569,7 +570,7 @@ void type_domain_t::operator()(const basic_block_t& bb, int print) { for (const Instruction& statement : bb) { loc = location_t(std::make_pair(label, ++curr_pos)); - std::visit([this, loc, print](const auto& v) { std::apply(*this, std::make_tuple(v, loc, print)); }, statement); + std::visit([this, loc](const auto& v) { std::apply(*this, std::make_tuple(v, loc)); }, statement); } operator+=(m_region.get_errors()); diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 5438a508e..482c3054c 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -53,30 +53,30 @@ class type_domain_t final { void operator-=(crab::variable_t var); //// abstract transformers - void operator()(const Undefined&, location_t loc = boost::none, int print = 0); - void operator()(const Bin&, location_t loc = boost::none, int print = 0); - void operator()(const Un&, location_t loc = boost::none, int print = 0); - void operator()(const LoadMapFd&, location_t loc = boost::none, int print = 0); - void operator()(const Atomic&, location_t loc = boost::none, int print = 0); - void operator()(const Call&, location_t loc = boost::none, int print = 0); - void operator()(const Callx&, location_t loc = boost::none, int print = 0); - void operator()(const Exit&, location_t loc = boost::none, int print = 0); - void operator()(const Jmp&, location_t loc = boost::none, int print = 0); - void operator()(const Mem&, location_t loc = boost::none, int print = 0); - void operator()(const Packet&, location_t loc = boost::none, int print = 0); - void operator()(const Assume&, location_t loc = boost::none, int print = 0); - void operator()(const Assert&, location_t loc = boost::none, int print = 0); - void operator()(const ValidAccess&, location_t loc = boost::none, int print = 0); - void operator()(const Comparable&, location_t loc = boost::none, int print = 0); - void operator()(const Addable&, location_t loc = boost::none, int print = 0); - void operator()(const ValidStore&, location_t loc = boost::none, int print = 0); - void operator()(const TypeConstraint&, location_t loc = boost::none, int print = 0); - void operator()(const ValidSize&, location_t loc = boost::none, int print = 0); - void operator()(const ValidMapKeyValue&, location_t loc = boost::none, int print = 0); - void operator()(const ZeroCtxOffset&, location_t loc = boost::none, int print = 0); - void operator()(const ValidDivisor&, location_t loc = boost::none, int print = 0); - void operator()(const FuncConstraint& s, location_t loc = boost::none, int print = 0) {}; - void operator()(const IncrementLoopCounter&, location_t loc = boost::none, int print = 0); + void operator()(const Undefined&, location_t loc = boost::none); + void operator()(const Bin&, location_t loc = boost::none); + void operator()(const Un&, location_t loc = boost::none); + void operator()(const LoadMapFd&, location_t loc = boost::none); + void operator()(const Atomic&, location_t loc = boost::none); + void operator()(const Call&, location_t loc = boost::none); + void operator()(const Callx&, location_t loc = boost::none); + void operator()(const Exit&, location_t loc = boost::none); + void operator()(const Jmp&, location_t loc = boost::none); + void operator()(const Mem&, location_t loc = boost::none); + void operator()(const Packet&, location_t loc = boost::none); + void operator()(const Assume&, location_t loc = boost::none); + void operator()(const Assert&, location_t loc = boost::none); + void operator()(const ValidAccess&, location_t loc = boost::none); + void operator()(const Comparable&, location_t loc = boost::none); + void operator()(const Addable&, location_t loc = boost::none); + void operator()(const ValidStore&, location_t loc = boost::none); + void operator()(const TypeConstraint&, location_t loc = boost::none); + void operator()(const ValidSize&, location_t loc = boost::none); + void operator()(const ValidMapKeyValue&, location_t loc = boost::none); + void operator()(const ZeroCtxOffset&, location_t loc = boost::none); + void operator()(const ValidDivisor&, location_t loc = boost::none); + void operator()(const FuncConstraint& s, location_t loc = boost::none) {}; + void operator()(const IncrementLoopCounter&, location_t loc = boost::none); void operator()(const basic_block_t& bb, int print = 0); void write(std::ostream& os) const {} friend std::ostream& operator<<(std::ostream& o, const type_domain_t& dom); From 46b44a78c9d19a0ef94193bf79bd0d2d8a2632ec Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Fri, 20 Oct 2023 00:42:05 -0400 Subject: [PATCH 137/373] Better checks for Assume across multiple domains Signed-off-by: Ameer Hamza --- src/crab/common.hpp | 11 ++++++++ src/crab/interval_prop_domain.cpp | 20 +------------- src/crab/interval_prop_domain.hpp | 2 +- src/crab/type_domain.cpp | 43 ++++++++++++++++++++++++------- 4 files changed, 46 insertions(+), 30 deletions(-) diff --git a/src/crab/common.hpp b/src/crab/common.hpp index f4a96529d..b7ea049e6 100644 --- a/src/crab/common.hpp +++ b/src/crab/common.hpp @@ -166,6 +166,17 @@ inline bool is_shared_ptr(const std::optional& ptr) { && std::get(*ptr).get_region() == region_t::T_SHARED); } +inline bool same_type(const std::optional& ptr_or_mapfd1, + const std::optional& ptr_or_mapfd2, + const std::optional& interval1, + const std::optional& interval2) { + if (is_mapfd_type(ptr_or_mapfd1) && is_mapfd_type(ptr_or_mapfd2)) return true; + if (ptr_or_mapfd1 && ptr_or_mapfd2 && same_region(*ptr_or_mapfd1, *ptr_or_mapfd2)) + return true; + if (interval1 && interval2) return true; + return false; +} + inline std::ostream& operator<<(std::ostream& o, const region_t& t) { o << static_cast::type>(t); return o; diff --git a/src/crab/interval_prop_domain.cpp b/src/crab/interval_prop_domain.cpp index 3006c98fb..d9dc30ef1 100644 --- a/src/crab/interval_prop_domain.cpp +++ b/src/crab/interval_prop_domain.cpp @@ -636,26 +636,8 @@ void interval_prop_domain_t::assume_signed_cst_interval(Condition::Op op, bool i } void interval_prop_domain_t::assume_cst(Condition::Op op, bool is64, register_t left, - Value right, location_t loc) { + Value right, interval_t&& left_interval, interval_t&& right_interval, location_t loc) { using Op = Condition::Op; - auto left_mock_interval = m_registers_interval_values.find(left); - if (!left_mock_interval) return; - auto left_interval = left_mock_interval->to_interval(); - interval_t right_interval = interval_t::bottom(); - int64_t imm; - bool is_right_reg = std::holds_alternative(right); - if (is_right_reg) { - auto right_mock_interval = m_registers_interval_values.find(std::get(right).v); - if (!right_mock_interval) return; - right_interval = right_mock_interval->to_interval(); - } - else { - imm = static_cast(std::get(right).v); - right_interval = is64 ? interval_t(number_t{imm}) : interval_t(number_t{(uint64_t)imm}); - } - if (left_interval.is_bottom() || (is_right_reg && right_interval.is_bottom())) { - return; - } interval_t left_interval_orig = left_interval; interval_t right_interval_orig = right_interval; diff --git a/src/crab/interval_prop_domain.hpp b/src/crab/interval_prop_domain.hpp index 836cc6e48..0ac2467df 100644 --- a/src/crab/interval_prop_domain.hpp +++ b/src/crab/interval_prop_domain.hpp @@ -158,7 +158,7 @@ class interval_prop_domain_t final { interval_t&&, interval_t&&, register_t, Value, location_t); void assume_signed_cst_interval(Condition::Op, bool, interval_t&&, interval_t&&, interval_t&&, interval_t&&, register_t, Value, location_t); - void assume_cst(Condition::Op, bool, register_t, Value, location_t); + void assume_cst(Condition::Op, bool, register_t, Value, interval_t&&, interval_t&&, location_t); void assume_lt(bool, interval_t&&, interval_t&&, interval_t&&, interval_t&&, register_t, Value, location_t, bool); void assume_gt(bool, interval_t&&, interval_t&&, interval_t&&, interval_t&&, diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 7e22d03b2..adcb359e8 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -171,19 +171,31 @@ void type_domain_t::operator()(const Packet& u, location_t loc) { void type_domain_t::operator()(const Assume& s, location_t loc) { Condition cond = s.cond; const auto& maybe_left_type = m_region.find_ptr_or_mapfd_type(cond.left.v); + const auto& maybe_left_interval = m_interval.find_interval_value(cond.left.v); + assert(!maybe_left_type.has_value() || !maybe_left_interval.has_value()); if (std::holds_alternative(cond.right)) { const auto& right_reg = std::get(cond.right); const auto& maybe_right_type = m_region.find_ptr_or_mapfd_type(right_reg.v); - if (maybe_left_type && maybe_right_type) { - // both pointers - if (is_packet_ptr(maybe_left_type) && is_packet_ptr(maybe_right_type)) { - m_offset(s, loc); + const auto& maybe_right_interval = m_interval.find_interval_value(right_reg.v); + assert(!maybe_right_type.has_value() || !maybe_right_interval.has_value()); + if (same_type(maybe_left_type, maybe_right_type, + maybe_left_interval, maybe_right_interval)) { + if (maybe_left_interval) { + // both numbers + auto left_interval = maybe_left_interval->to_interval(); + auto right_interval = maybe_right_interval->to_interval(); + m_interval.assume_cst(cond.op, cond.is64, cond.left.v, cond.right, + std::move(left_interval), std::move(right_interval), loc); + } + else if (maybe_left_type) { + if (is_packet_ptr(maybe_left_type)) { + // both packet pointers + m_offset(s, loc); + } + else { + // other cases, not implemented yet + } } - } - else if (!maybe_left_type && !maybe_right_type) { - // both numbers - auto left_interval = m_interval.find_interval_value(cond.left.v); - m_interval.assume_cst(cond.op, cond.is64, cond.left.v, cond.right, loc); } else { // We should only reach here if `--assume-assert` is off @@ -193,7 +205,18 @@ void type_domain_t::operator()(const Assume& s, location_t loc) { } } else { - m_interval.assume_cst(cond.op, cond.is64, cond.left.v, cond.right, loc); + if (is_shared_ptr(maybe_left_type) || is_mapfd_type(maybe_left_type)) { + // left is a shared pointer, or a mapfd + // TODO: need to work with values + } + else if (maybe_left_interval) { + auto left_interval = maybe_left_interval->to_interval(); + int64_t imm = static_cast(std::get(cond.right).v); + auto right_interval = cond.is64 + ? interval_t{number_t{imm}} : interval_t{number_t{(uint64_t)imm}}; + m_interval.assume_cst(cond.op, cond.is64, cond.left.v, cond.right, + std::move(left_interval), std::move(right_interval), loc); + } } } From 4be6248882e378e6702300733964a6fa3713e819 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Fri, 20 Oct 2023 00:43:42 -0400 Subject: [PATCH 138/373] Disabling the asserts related to checking types Temporarily disable all asserts that related to checking if the type of a register is defined in both region and interval propagation domain; These should not fail, but for certain benchmarks, these do (needs to be checked later); Some refactoring in the code of assert checks; Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index adcb359e8..7894cf76b 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -172,12 +172,12 @@ void type_domain_t::operator()(const Assume& s, location_t loc) { Condition cond = s.cond; const auto& maybe_left_type = m_region.find_ptr_or_mapfd_type(cond.left.v); const auto& maybe_left_interval = m_interval.find_interval_value(cond.left.v); - assert(!maybe_left_type.has_value() || !maybe_left_interval.has_value()); + //assert(!maybe_left_type.has_value() || !maybe_left_interval.has_value()); if (std::holds_alternative(cond.right)) { const auto& right_reg = std::get(cond.right); const auto& maybe_right_type = m_region.find_ptr_or_mapfd_type(right_reg.v); const auto& maybe_right_interval = m_interval.find_interval_value(right_reg.v); - assert(!maybe_right_type.has_value() || !maybe_right_interval.has_value()); + //assert(!maybe_right_type.has_value() || !maybe_right_interval.has_value()); if (same_type(maybe_left_type, maybe_right_type, maybe_left_interval, maybe_right_interval)) { if (maybe_left_interval) { @@ -223,7 +223,7 @@ void type_domain_t::operator()(const Assume& s, location_t loc) { void type_domain_t::operator()(const ValidDivisor& u, location_t loc) { auto maybe_ptr_or_mapfd_reg = m_region.find_ptr_or_mapfd_type(u.reg.v); auto maybe_num_type_reg = m_interval.find_interval_value(u.reg.v); - assert(!maybe_ptr_or_mapfd_reg.has_value() || !maybe_num_type_reg.has_value()); + //assert(!maybe_ptr_or_mapfd_reg.has_value() || !maybe_num_type_reg.has_value()); if (is_ptr_type(maybe_ptr_or_mapfd_reg)) { m_errors.push_back("Only numbers can be used as divisors"); @@ -279,7 +279,7 @@ void type_domain_t::operator()(const ValidAccess& s, location_t loc) { void type_domain_t::operator()(const TypeConstraint& s, location_t loc) { auto reg_type = m_region.find_ptr_or_mapfd_type(s.reg.v); auto mock_interval_type = m_interval.find_interval_value(s.reg.v); - assert(!reg_type || !mock_interval_type); + //assert(!reg_type.has_value() || !mock_interval_type.has_value()); m_region(s, loc); } @@ -293,8 +293,8 @@ void type_domain_t::operator()(const Comparable& u, location_t loc) { auto maybe_ptr_or_mapfd2 = m_region.find_ptr_or_mapfd_type(u.r2.v); auto maybe_num_type1 = m_interval.find_interval_value(u.r1.v); auto maybe_num_type2 = m_interval.find_interval_value(u.r2.v); - assert(!maybe_ptr_or_mapfd1 || !maybe_num_type1); - assert(!maybe_ptr_or_mapfd2 || !maybe_num_type2); + //assert(!maybe_ptr_or_mapfd1.has_value() || !maybe_num_type1.has_value()); + //assert(!maybe_ptr_or_mapfd2.has_value() || !maybe_num_type2.has_value()); if (maybe_ptr_or_mapfd1 && maybe_ptr_or_mapfd2) { if (is_mapfd_type(maybe_ptr_or_mapfd1) && is_mapfd_type(maybe_ptr_or_mapfd2)) return; if (!is_shared_ptr(maybe_ptr_or_mapfd1) @@ -315,8 +315,8 @@ void type_domain_t::operator()(const Addable& u, location_t loc) { auto maybe_ptr_or_mapfd_num = m_region.find_ptr_or_mapfd_type(u.num.v); auto maybe_num_type_ptr = m_interval.find_interval_value(u.ptr.v); auto maybe_num_type_num = m_interval.find_interval_value(u.num.v); - assert(!maybe_ptr_or_mapfd_ptr.has_value() || !maybe_num_type_ptr.has_value()); - assert(!maybe_ptr_or_mapfd_num.has_value() || !maybe_num_type_num.has_value()); + //assert(!maybe_ptr_or_mapfd_ptr.has_value() || !maybe_num_type_ptr.has_value()); + //assert(!maybe_ptr_or_mapfd_num.has_value() || !maybe_num_type_num.has_value()); // a -> b <-> !a || b // is_ptr(ptr) -> is_num(num) <-> !is_ptr(ptr) || is_num(num) @@ -332,8 +332,8 @@ void type_domain_t::operator()(const ValidStore& u, location_t loc) { auto maybe_ptr_or_mapfd_val = m_region.find_ptr_or_mapfd_type(u.val.v); auto maybe_num_type_mem = m_interval.find_interval_value(u.mem.v); auto maybe_num_type_val = m_interval.find_interval_value(u.val.v); - assert(!maybe_ptr_or_mapfd_mem.has_value() || !maybe_num_type_mem.has_value()); - assert(!maybe_ptr_or_mapfd_val.has_value() || !maybe_num_type_val.has_value()); + //assert(!maybe_ptr_or_mapfd_mem.has_value() || !maybe_num_type_mem.has_value()); + //assert(!maybe_ptr_or_mapfd_val.has_value() || !maybe_num_type_val.has_value()); // a -> b <-> !a || b // !is_stack_ptr(mem) -> is_num(val) <-> is_stack_ptr(mem) || is_num(val) @@ -347,7 +347,7 @@ void type_domain_t::operator()(const ValidStore& u, location_t loc) { void type_domain_t::operator()(const ValidSize& u, location_t loc) { auto maybe_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(u.reg.v); auto maybe_num_type = m_interval.find_interval_value(u.reg.v); - assert(!maybe_ptr_or_mapfd || !maybe_num_type); + //assert(!maybe_ptr_or_mapfd || !maybe_num_type); if (maybe_num_type) { auto reg_value = maybe_num_type.value(); @@ -453,13 +453,13 @@ void type_domain_t::operator()(const Bin& bin, location_t loc) { } src_interval = interval_t{number_t{imm}}; } - assert(!src_ptr_or_mapfd || !src_interval); + //assert(!src_ptr_or_mapfd.has_value() || !src_interval.has_value()); auto dst_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(bin.dst.v); auto dst_mock_interval = m_interval.find_interval_value(bin.dst.v); auto dst_interval = dst_mock_interval ? dst_mock_interval->to_interval() : std::optional(); - assert(!dst_ptr_or_mapfd.has_value() || !dst_interval.has_value()); + //assert(!dst_ptr_or_mapfd.has_value() || !dst_interval.has_value()); using Op = Bin::Op; // for all operations except mov, add, sub, the src and dst should be numbers From c1b7082fd5d1157b34021bd6c8c70be1a839268d Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Wed, 25 Oct 2023 10:32:20 -0400 Subject: [PATCH 139/373] Support for null checks for shared ptrs Added the concept of nullness of a pointer as MAYBE_NULL, NOT_NULL, and _NULL; Keeping track of pointer aliases for shared ptrs; Support for Assume operation for shared ptrs, and null check for shared ptrs in ValidAccess; Better join, including join for shared ptr aliases; Some refactoring, mainly related to join operations; Signed-off-by: Ameer Hamza --- src/crab/common.cpp | 11 +- src/crab/common.hpp | 16 ++- src/crab/offset_domain.hpp | 1 - src/crab/region_domain.cpp | 263 ++++++++++++++++++++++++++++++------- src/crab/region_domain.hpp | 11 +- src/crab/type_domain.cpp | 10 +- 6 files changed, 253 insertions(+), 59 deletions(-) diff --git a/src/crab/common.cpp b/src/crab/common.cpp index 54dc2fe58..3db09febd 100644 --- a/src/crab/common.cpp +++ b/src/crab/common.cpp @@ -50,7 +50,9 @@ mapfd_t mapfd_t::operator|(const mapfd_t& other) const { return mapfd_t(std::move(mock_i), value_type); } -mock_interval_t ptr_with_off_t::get_region_size() const { return m_region_size; } +void ptr_with_off_t::set_nullness(nullness_t n) { m_nullness = n; } + +void ptr_with_off_t::set_id(int id) { m_id = id; } void ptr_with_off_t::set_offset(mock_interval_t off) { m_offset = off; } @@ -59,10 +61,11 @@ void ptr_with_off_t::set_region_size(mock_interval_t region_sz) { m_region_size void ptr_with_off_t::set_region(region_t r) { m_r = r; } ptr_with_off_t ptr_with_off_t::operator|(const ptr_with_off_t& other) const { - const auto& mock_o = mock_interval_t(m_offset.to_interval() | other.m_offset.to_interval()); - const auto& mock_r_s = mock_interval_t( + auto&& mock_o = mock_interval_t(m_offset.to_interval() | other.m_offset.to_interval()); + auto&& mock_r_s = mock_interval_t( m_region_size.to_interval() | other.m_region_size.to_interval()); - return ptr_with_off_t(m_r, std::move(mock_o), std::move(mock_r_s)); + auto&& nullness = m_nullness == other.m_nullness ? m_nullness : nullness_t::MAYBE_NULL; + return ptr_with_off_t(m_r, -1, std::move(mock_o), std::move(nullness), std::move(mock_r_s)); } std::ostream& operator<<(std::ostream& o, const mapfd_t& m) { diff --git a/src/crab/common.hpp b/src/crab/common.hpp index b7ea049e6..082ecffa9 100644 --- a/src/crab/common.hpp +++ b/src/crab/common.hpp @@ -19,6 +19,7 @@ constexpr int STACK_BEGIN = 0; constexpr int CTX_BEGIN = 0; constexpr int PACKET_BEGIN = 0; constexpr int SHARED_BEGIN = 0; +constexpr int MAX_PACKET_SIZE = 0xffff; enum class region_t { T_CTX, @@ -27,6 +28,8 @@ enum class region_t { T_SHARED }; +enum class nullness_t { MAYBE_NULL, NOT_NULL, _NULL }; + class packet_ptr_t { region_t m_r = region_t::T_PACKET; @@ -64,7 +67,9 @@ class mock_interval_t { class ptr_with_off_t { region_t m_r; + int m_id; mock_interval_t m_offset; + nullness_t m_nullness = nullness_t::MAYBE_NULL; mock_interval_t m_region_size = mock_interval_t::top(); public: @@ -73,11 +78,16 @@ class ptr_with_off_t { ptr_with_off_t(ptr_with_off_t &&) = default; ptr_with_off_t &operator=(const ptr_with_off_t &) = default; ptr_with_off_t &operator=(ptr_with_off_t &&) = default; - ptr_with_off_t(region_t _r, mock_interval_t _off, + ptr_with_off_t(region_t _r, int _id, mock_interval_t _off, + nullness_t _nullness = nullness_t::MAYBE_NULL, mock_interval_t _region_sz = mock_interval_t::top()) - : m_r(_r), m_offset(_off), m_region_size(_region_sz) {} + : m_r(_r), m_id(_id), m_offset(_off), m_nullness(_nullness), m_region_size(_region_sz) {} ptr_with_off_t operator|(const ptr_with_off_t&) const; - mock_interval_t get_region_size() const; + [[nodiscard]] nullness_t get_nullness() const { return m_nullness; } + void set_nullness(nullness_t); + [[nodiscard]] int get_id() const { return m_id; } + void set_id(int); + [[nodiscard]] mock_interval_t get_region_size() const { return m_region_size; } void set_region_size(mock_interval_t); [[nodiscard]] mock_interval_t get_offset() const { return m_offset; } void set_offset(mock_interval_t); diff --git a/src/crab/offset_domain.hpp b/src/crab/offset_domain.hpp index 5710f50cd..2754defb6 100644 --- a/src/crab/offset_domain.hpp +++ b/src/crab/offset_domain.hpp @@ -13,7 +13,6 @@ namespace crab { constexpr int PACKET_END = -4100; constexpr int PACKET_META = -1; -constexpr int MAX_PACKET_SIZE = 0xffff; using weight_t = interval_t; using slack_var_t = int; diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index 9411376a9..32a88f38f 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -5,6 +5,49 @@ namespace crab { +static inline std::vector> join_shared_ptr_aliases( + const std::vector>& A, const std::vector>& B) { + auto flattenSet = [](const std::vector>& v) { + std::set s; + for (const auto& x : v) { + s.insert(x.begin(), x.end()); + } + return s; + }; + + std::set a, b, intersect; + a = flattenSet(A); + b = flattenSet(B); + + std::set_intersection(a.begin(), a.end(), b.begin(), b.end(), + std::inserter(intersect, intersect.begin())); + + std::vector> powerset; + powerset.push_back({}); + for (int n : intersect) { + auto size = (size_t)powerset.size(); + for (size_t i = 0; i < size; i++) { + auto newSet = powerset[(int)i]; + newSet.insert(n); + powerset.push_back(newSet); + } + } + + std::vector> result; + for (const auto& s : powerset) { + auto foundInA = std::find(A.begin(), A.end(), s); + auto foundInB = std::find(B.begin(), B.end(), s); + if (foundInA != A.end() || foundInB != B.end()) { + result.push_back(s); + } + auto flattened = flattenSet(result); + if (flattened.size() == intersect.size()) { + break; + } + } + return result; +} + ctx_t::ctx_t(const ebpf_context_descriptor_t* desc) { if (desc->data >= 0) { @@ -68,30 +111,29 @@ register_types_t register_types_t::operator|(const register_types_t& other) cons for (uint8_t i = 0; i < NUM_REGISTERS; i++) { if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; - auto maybe_ptr1 = find(*(m_cur_def[i])); - auto maybe_ptr2 = other.find(*(other.m_cur_def[i])); + auto maybe_ptr1 = find(register_t{i}); + auto maybe_ptr2 = other.find(register_t{i}); if (maybe_ptr1 && maybe_ptr2) { ptr_or_mapfd_t ptr_or_mapfd1 = *maybe_ptr1, ptr_or_mapfd2 = *maybe_ptr2; - if (ptr_or_mapfd1 == ptr_or_mapfd2) { - joined_reg_types.insert(register_t{i}, loc, std::move(ptr_or_mapfd1)); - } - else { - if (std::holds_alternative(ptr_or_mapfd1) - && std::holds_alternative(ptr_or_mapfd2)) { - ptr_with_off_t ptr_with_off1 = std::get(ptr_or_mapfd1); - ptr_with_off_t ptr_with_off2 = std::get(ptr_or_mapfd2); - if (ptr_with_off1.get_region() == ptr_with_off2.get_region()) { - joined_reg_types.insert(register_t{i}, loc, - std::move(ptr_with_off1 | ptr_with_off2)); - } - } - else if (std::holds_alternative(ptr_or_mapfd1) - && std::holds_alternative(ptr_or_mapfd2)) { - mapfd_t mapfd1 = std::get(ptr_or_mapfd1); - mapfd_t mapfd2 = std::get(ptr_or_mapfd2); - joined_reg_types.insert(register_t{i}, loc, std::move(mapfd1 | mapfd2)); + if (std::holds_alternative(ptr_or_mapfd1) + && std::holds_alternative(ptr_or_mapfd2)) { + ptr_with_off_t ptr_with_off1 = std::get(ptr_or_mapfd1); + ptr_with_off_t ptr_with_off2 = std::get(ptr_or_mapfd2); + if (ptr_with_off1.get_region() == ptr_with_off2.get_region()) { + auto joined_ptr = ptr_with_off1 | ptr_with_off2; + joined_reg_types.insert(register_t{i}, loc, std::move(joined_ptr)); } } + else if (std::holds_alternative(ptr_or_mapfd1) + && std::holds_alternative(ptr_or_mapfd2)) { + mapfd_t mapfd1 = std::get(ptr_or_mapfd1); + mapfd_t mapfd2 = std::get(ptr_or_mapfd2); + joined_reg_types.insert(register_t{i}, loc, std::move(mapfd1 | mapfd2)); + } + else if (std::holds_alternative(ptr_or_mapfd1) + && std::holds_alternative(ptr_or_mapfd2)) { + joined_reg_types.insert(register_t{i}, loc, std::move(packet_ptr_t())); + } } } return joined_reg_types; @@ -166,10 +208,7 @@ stack_t stack_t::operator|(const stack_t& other) const { int width1 = ptr_or_mapfd_cells1.second; int width2 = ptr_or_mapfd_cells2.second; int width_joined = std::min(width1, width2); - if (ptr_or_mapfd1 == ptr_or_mapfd2) { - joined_stack.store(kv.first, ptr_or_mapfd1, width_joined); - } - else if (std::holds_alternative(ptr_or_mapfd1) && + if (std::holds_alternative(ptr_or_mapfd1) && std::holds_alternative(ptr_or_mapfd2)) { auto ptr_with_off1 = std::get(ptr_or_mapfd1); auto ptr_with_off2 = std::get(ptr_or_mapfd2); @@ -184,6 +223,10 @@ stack_t stack_t::operator|(const stack_t& other) const { auto mapfd2 = std::get(ptr_or_mapfd2); joined_stack.store(kv.first, std::move(mapfd1 | mapfd2), width_joined); } + else if (std::holds_alternative(ptr_or_mapfd1) && + std::holds_alternative(ptr_or_mapfd2)) { + joined_stack.store(kv.first, std::move(packet_ptr_t()), width_joined); + } } } return joined_stack; @@ -351,7 +394,9 @@ region_domain_t region_domain_t::operator|(const region_domain_t& other) const { else if (other.is_bottom() || is_top()) { return *this; } - return region_domain_t(m_registers | other.m_registers, m_stack | other.m_stack, other.m_ctx); + auto aliases = join_shared_ptr_aliases(m_shared_ptr_aliases, other.m_shared_ptr_aliases); + return region_domain_t(m_registers | other.m_registers, m_stack | other.m_stack, other.m_ctx, + std::move(aliases)); } region_domain_t region_domain_t::operator|(region_domain_t&& other) const { @@ -361,7 +406,9 @@ region_domain_t region_domain_t::operator|(region_domain_t&& other) const { else if (other.is_bottom() || is_top()) { return *this; } - return region_domain_t(m_registers | std::move(other.m_registers), m_stack | std::move(other.m_stack), other.m_ctx); + auto aliases = join_shared_ptr_aliases(m_shared_ptr_aliases, other.m_shared_ptr_aliases); + return region_domain_t(m_registers | std::move(other.m_registers), + m_stack | std::move(other.m_stack), other.m_ctx, std::move(aliases)); } region_domain_t region_domain_t::operator&(const region_domain_t& abs) const { @@ -399,6 +446,70 @@ void region_domain_t::operator()(const Exit &u, location_t loc) {} void region_domain_t::operator()(const Jmp &u, location_t loc) {} +void region_domain_t::assume_cst(Condition::Op op, ptr_with_off_t&& shared_ptr, int64_t imm, + register_t left, location_t loc) { + // we only reach here when the ptr is shared ptr + auto nullness = shared_ptr.get_nullness(); + auto set_nullness = [this, loc, left](nullness_t n, int id) { + if (id == -1) { + for (size_t i = 0; i < m_shared_ptr_aliases.size(); i++) { + if (m_shared_ptr_aliases[i].count(left)) { + id = i; + break; + } + } + } + if (id == -1) return; + for (const auto& s : m_shared_ptr_aliases[id]) { + if (s <= 10) { + auto type = m_registers.find(register_t{(uint8_t)s}); + if (is_shared_ptr(type)) { + auto shared_ptr = std::get(*type); + shared_ptr.set_nullness(n); + m_registers.insert(register_t{(uint8_t)s}, loc, shared_ptr); + } + } + else { + auto offset = s - 11; + auto type_with_width = m_stack.find(offset); + if (!type_with_width) continue; + auto type = type_with_width->first; + if (is_shared_ptr(type)) { + auto shared_ptr = std::get(type); + shared_ptr.set_nullness(n); + m_stack.store(offset, shared_ptr, type_with_width->second); + } + } + } + }; + if (imm == 0) { + if (op == Condition::Op::EQ) { + if (nullness == nullness_t::_NULL) { + m_registers.set_to_top(); + } + else if (nullness == nullness_t::NOT_NULL) { + m_registers.set_to_bottom(); + } + else { + auto id = shared_ptr.get_id(); + set_nullness(nullness_t::_NULL, id); + } + } + else if (op == Condition::Op::NE) { + if (nullness == nullness_t::NOT_NULL) { + m_registers.set_to_top(); + } + else if (nullness == nullness_t::_NULL) { + m_registers.set_to_bottom(); + } + else { + auto id = shared_ptr.get_id(); + set_nullness(nullness_t::NOT_NULL, id); + } + } + } +} + void region_domain_t::operator()(const Assume& u, location_t loc) { // nothing to do here } @@ -544,6 +655,31 @@ void region_domain_t::operator()(const LoadMapFd &u, location_t loc) { do_load_mapfd((register_t)u.dst.v, u.mapfd, loc); } +void region_domain_t::set_aliases(int v, ptr_with_off_t& ptr) { + size_t i = 0; + for (; i < m_shared_ptr_aliases.size(); i++) { + if (m_shared_ptr_aliases[(int)i].count(v) > 0) { + break; + } + } + if (i < m_shared_ptr_aliases.size()) m_shared_ptr_aliases[(int)i].erase(v); + auto id = ptr.get_id(); + /* NOTE: this check is supposed to be for newly generated pointers, but it could also be the + * case that an existing pointer has id == -1. This is because, at join of two ptr_with_off_t, + * we set id == -1 for simplicity. Some code for correcting this is in the assume_cst + * function, although it should work here as well but doesn't (ideally, may be not). + * In future, check this again. + */ + if (id == -1) { + m_shared_ptr_aliases.push_back({v}); + ptr.set_id(m_shared_ptr_aliases.size() - 1); + } + else { + m_shared_ptr_aliases[id].insert(v); + ptr.set_id(id); + } +} + void region_domain_t::do_call(const Call& u, const stack_cells_t& cells, location_t loc) { for (const auto& kv : cells) { auto offset = kv.first; @@ -568,15 +704,18 @@ void region_domain_t::do_call(const Call& u, const stack_cells_t& cells, locatio goto out; } } else { - auto type = ptr_with_off_t(crab::region_t::T_SHARED, interval_t{number_t{0}}, + auto type = ptr_with_off_t(region_t::T_SHARED, -1, + interval_t{number_t{0}}, nullness_t::MAYBE_NULL, get_map_value_size(*maybe_fd_reg)); + set_aliases((int)r0, type); m_registers.insert(r0, loc, type); } } } else { - auto type = ptr_with_off_t( - crab::region_t::T_SHARED, interval_t{number_t{0}}, crab::interval_t::top()); + auto type = ptr_with_off_t(region_t::T_SHARED, -1, interval_t{number_t{0}}, + nullness_t::MAYBE_NULL); + set_aliases((int)r0, type); m_registers.insert(r0, loc, type); } } @@ -626,22 +765,29 @@ void region_domain_t::check_valid_access(const ValidAccess &s, int width) { auto offset = ptr_with_off_type.get_offset(); auto offset_to_check = offset.to_interval()+interval_t{s.offset}; auto offset_lb = offset_to_check.lb(); - auto offset_plus_width_ub = offset_to_check.ub()+crab::bound_t{width}; - if (ptr_with_off_type.get_region() == crab::region_t::T_STACK) { - if (crab::bound_t{STACK_BEGIN} <= offset_lb - && offset_plus_width_ub <= crab::bound_t{EBPF_STACK_SIZE}) + auto offset_plus_width_ub = offset_to_check.ub()+bound_t{width}; + if (ptr_with_off_type.get_region() == region_t::T_STACK) { + if (bound_t{STACK_BEGIN} <= offset_lb + && offset_plus_width_ub <= bound_t{EBPF_STACK_SIZE}) return; } - else if (ptr_with_off_type.get_region() == crab::region_t::T_CTX) { - if (crab::bound_t{CTX_BEGIN} <= offset_lb - && offset_plus_width_ub <= crab::bound_t{ctx_size()}) + else if (ptr_with_off_type.get_region() == region_t::T_CTX) { + if (bound_t{CTX_BEGIN} <= offset_lb + && offset_plus_width_ub <= bound_t{ctx_size()}) return; } else { // shared if (crab::bound_t{SHARED_BEGIN} <= offset_lb && - offset_plus_width_ub <= ptr_with_off_type.get_region_size().lb()) return; - // TODO: check null access - //return; + offset_plus_width_ub <= ptr_with_off_type.get_region_size().lb()) { + if (!is_comparison_check && !s.or_null) { + auto nullness = ptr_with_off_type.get_nullness(); + if (nullness != nullness_t::NOT_NULL) { + m_errors.push_back("possible null access"); + } + return; + } + return; + } } } else if (std::holds_alternative(reg_ptr_or_mapfd_type)) { @@ -676,12 +822,12 @@ region_domain_t&& region_domain_t::setup_entry() { register_types_t typ(std::make_shared()); auto loc = std::make_pair(label_t::entry, (unsigned int)0); - auto ctx_ptr_r1 = ptr_with_off_t(crab::region_t::T_CTX, mock_interval_t{number_t{0}}); - auto stack_ptr_r10 = ptr_with_off_t(crab::region_t::T_STACK, mock_interval_t{number_t{512}}); + auto ctx_ptr_r1 = ptr_with_off_t(region_t::T_CTX, -1, mock_interval_t{number_t{0}}); + auto stack_ptr_r10 = ptr_with_off_t(region_t::T_STACK, -1, mock_interval_t{number_t{512}}); typ.insert(register_t{R1_ARG}, loc, ctx_ptr_r1); typ.insert(register_t{R10_STACK_POINTER}, loc, stack_ptr_r10); - static region_domain_t inv(std::move(typ), crab::stack_t::top(), ctx); + static region_domain_t inv(std::move(typ), stack_t::top(), ctx); return std::move(inv); } @@ -783,8 +929,16 @@ interval_t region_domain_t::do_bin(const Bin& bin, // ra = b, where b is a pointer/mapfd, a numerical register, or a constant; case Op::MOV: { // b is a pointer/mapfd - if (src_ptr_or_mapfd_opt) - m_registers.insert(dst_register, loc, src_ptr_or_mapfd); + if (src_ptr_or_mapfd_opt) { + if (is_shared_ptr(src_ptr_or_mapfd_opt)) { + auto shared_ptr = std::get(src_ptr_or_mapfd); + set_aliases(dst_register, shared_ptr); + m_registers.insert(dst_register, loc, shared_ptr); + } + else { + m_registers.insert(dst_register, loc, src_ptr_or_mapfd); + } + } // b is a numerical register, or constant else if (dst_ptr_or_mapfd_opt) { m_registers -= dst_register; @@ -920,8 +1074,15 @@ void region_domain_t::do_load(const Mem& b, const register_t& target_register, b m_registers -= target_register; return; } - - m_registers.insert(target_register, loc, (*loaded).first); + auto ptr_or_mapfd = loaded->first; + if (is_shared_ptr(ptr_or_mapfd)) { + auto shared_ptr = std::get(ptr_or_mapfd); + set_aliases((int)target_register, shared_ptr); + m_registers.insert(target_register, loc, shared_ptr); + } + else { + m_registers.insert(target_register, loc, ptr_or_mapfd); + } } } else { @@ -1005,7 +1166,15 @@ void region_domain_t::do_mem_store(const Mem& b, location_t loc) { // if targetreg_type is empty, we are storing a number if (!targetreg_type) return; - m_stack.store(store_at, *targetreg_type, width); + auto type = *targetreg_type; + if (is_shared_ptr(type)) { + auto shared_ptr = std::get(type); + set_aliases(store_at+11, shared_ptr); + m_stack.store(store_at, shared_ptr, width); + } + else { + m_stack.store(store_at, type, width); + } } void region_domain_t::adjust_bb_for_types(location_t loc) { diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index 33b5beb2a..b78ad8c8a 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -12,6 +12,8 @@ namespace crab { +using shared_ptr_aliases_t = std::vector>; + class ctx_t { using ptr_types_t = std::unordered_map; @@ -94,6 +96,7 @@ class region_domain_t final { crab::stack_t m_stack; crab::register_types_t m_registers; std::shared_ptr m_ctx; + shared_ptr_aliases_t m_shared_ptr_aliases; std::vector m_errors; public: @@ -103,8 +106,10 @@ class region_domain_t final { region_domain_t(const region_domain_t& o) = default; region_domain_t& operator=(region_domain_t&& o) = default; region_domain_t& operator=(const region_domain_t& o) = default; - region_domain_t(crab::register_types_t&& _types, crab::stack_t&& _st, std::shared_ptr _ctx) - : m_stack(std::move(_st)), m_registers(std::move(_types)), m_ctx(_ctx) {} + region_domain_t(crab::register_types_t&& _types, crab::stack_t&& _st, + std::shared_ptr _ctx, shared_ptr_aliases_t&& _shared_ptr_aliases = {}) + : m_stack(std::move(_st)), m_registers(std::move(_types)), m_ctx(_ctx), + m_shared_ptr_aliases(std::move(_shared_ptr_aliases)) {} // eBPF initialization: R1 points to ctx, R10 to stack, etc. static region_domain_t&& setup_entry(); // bottom/top @@ -176,6 +181,7 @@ class region_domain_t final { const std::optional&, location_t); void do_call(const Call&, const stack_cells_t&, location_t); void check_valid_access(const ValidAccess &, int); + void assume_cst(Condition::Op, ptr_with_off_t&&, int64_t, register_t, location_t); void update_ptr_or_mapfd(crab::ptr_or_mapfd_t&&, const interval_t&&, const crab::location_t&, register_t); @@ -185,6 +191,7 @@ class region_domain_t final { [[nodiscard]] std::vector get_ctx_keys() const; std::optional find_in_stack(uint64_t key) const; std::optional find_ptr_or_mapfd_at_loc(const crab::reg_with_loc_t&) const; + void set_aliases(int, ptr_with_off_t&); [[nodiscard]] std::vector get_stack_keys() const; void set_registers_to_top(); void adjust_bb_for_types(location_t); diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 7894cf76b..1d8a14364 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -205,8 +205,14 @@ void type_domain_t::operator()(const Assume& s, location_t loc) { } } else { - if (is_shared_ptr(maybe_left_type) || is_mapfd_type(maybe_left_type)) { - // left is a shared pointer, or a mapfd + if (is_shared_ptr(maybe_left_type)) { + // left is a shared pointer + int64_t imm = static_cast(std::get(cond.right).v); + auto shared_ptr = std::get(*maybe_left_type); + m_region.assume_cst(cond.op, std::move(shared_ptr), imm, cond.left.v, loc); + } + if (is_mapfd_type(maybe_left_type)) { + // left is a mapfd // TODO: need to work with values } else if (maybe_left_interval) { From cb55812b477c7c22ea7cfebc381b4a57d6b93e84 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Wed, 1 Nov 2023 21:53:19 -0400 Subject: [PATCH 140/373] Updated tests to add for type domain Needs more work, some test cases might still not be added for type domain Signed-off-by: Ameer Hamza --- src/test/test_verify.cpp | 38 +- src/test/test_verify_type_domain.cpp | 577 --------------------------- 2 files changed, 33 insertions(+), 582 deletions(-) delete mode 100644 src/test/test_verify_type_domain.cpp diff --git a/src/test/test_verify.cpp b/src/test/test_verify.cpp index 32f90054e..3bf306364 100644 --- a/src/test/test_verify.cpp +++ b/src/test/test_verify.cpp @@ -48,17 +48,33 @@ FAIL_UNMARSHAL("invalid", "invalid-lddw.o", ".text") } while (0) #define TEST_SECTION(project, filename, section) \ - TEST_CASE("./check ebpf-samples/" project "/" filename " " section, "[verify][samples][" project "]") { \ + TEST_CASE("./check ebpf-samples/" project "/" filename " " section " --domain=zoneCrab", "[verify][samples][" project "][ebpf]") { \ VERIFY_SECTION(project, filename, section, nullptr, &g_ebpf_platform_linux, true); \ + } \ + TEST_CASE("./check ebpf-samples/" project "/" filename " " section " --domain=type", "[verify][samples][" project "][type]") { \ + ebpf_verifier_options_t options = ebpf_verifier_default_options; \ + options.abstract_domain = abstract_domain_kind::TYPE_DOMAIN; \ + VERIFY_SECTION(project, filename, section, &options, &g_ebpf_platform_linux, true); \ } #define TEST_SECTION_REJECT(project, filename, section) \ - TEST_CASE("./check ebpf-samples/" project "/" filename " " section, "[verify][samples][" project "]") { \ + TEST_CASE("./check ebpf-samples/" project "/" filename " " section " --domain=zoneCrab", "[verify][samples][" project "][ebpf]") { \ VERIFY_SECTION(project, filename, section, nullptr, &g_ebpf_platform_linux, false); \ + } \ + TEST_CASE("./check ebpf-samples/" project "/" filename " " section " --domain=type", "[verify][samples][" project "][type]") { \ + ebpf_verifier_options_t options = ebpf_verifier_default_options; \ + options.abstract_domain = abstract_domain_kind::TYPE_DOMAIN; \ + VERIFY_SECTION(project, filename, section, &options, &g_ebpf_platform_linux, false); \ } #define TEST_SECTION_REJECT_IF_STRICT(project, filename, section) \ - TEST_CASE("./check ebpf-samples/" project "/" filename " " section, "[verify][samples][" project "]") { \ + TEST_CASE("./check ebpf-samples/" project "/" filename " " section " --domain=zoneCrab", "[verify][samples][" project "][ebpf]") { \ + ebpf_verifier_options_t options = ebpf_verifier_default_options; \ + VERIFY_SECTION(project, filename, section, &options, &g_ebpf_platform_linux, true); \ + options.strict = true; \ + VERIFY_SECTION(project, filename, section, &options, &g_ebpf_platform_linux, false); \ + } \ + TEST_CASE("./check ebpf-samples/" project "/" filename " " section " --domain=type", "[verify][samples][" project "][type]") { \ ebpf_verifier_options_t options = ebpf_verifier_default_options; \ VERIFY_SECTION(project, filename, section, &options, &g_ebpf_platform_linux, true); \ options.strict = true; \ @@ -66,15 +82,26 @@ FAIL_UNMARSHAL("invalid", "invalid-lddw.o", ".text") } #define TEST_SECTION_FAIL(project, filename, section) \ - TEST_CASE("expect failure ebpf-samples/" project "/" filename " " section, "[!shouldfail][verify][samples][" project "]") { \ + TEST_CASE("expect failure ebpf-samples/" project "/" filename " " section " --domain=zoneCrab", "[!shouldfail][verify][samples][" project "][ebpf]") { \ VERIFY_SECTION(project, filename, section, nullptr, &g_ebpf_platform_linux, true); \ + } \ + TEST_CASE("expect failure ebpf-samples/" project "/" filename " " section " --domain=type", "[!shouldfail][verify][samples][" project "][type]") { \ + ebpf_verifier_options_t options = ebpf_verifier_default_options; \ + options.abstract_domain = abstract_domain_kind::TYPE_DOMAIN; \ + VERIFY_SECTION(project, filename, section, &options, &g_ebpf_platform_linux, true); \ } #define TEST_SECTION_REJECT_FAIL(project, filename, section) \ - TEST_CASE("expect failure ebpf-samples/" project "/" filename " " section, "[!shouldfail][verify][samples][" project "]") { \ + TEST_CASE("expect failure ebpf-samples/" project "/" filename " " section " --domain=zoneCrab", "[!shouldfail][verify][samples][" project "][ebpf]") { \ VERIFY_SECTION(project, filename, section, nullptr, &g_ebpf_platform_linux, false); \ + } \ + TEST_CASE("expect failure ebpf-samples/" project "/" filename " " section " --domain=type", "[!shouldfail][verify][samples][" project "][type]") { \ + ebpf_verifier_options_t options = ebpf_verifier_default_options; \ + options.abstract_domain = abstract_domain_kind::TYPE_DOMAIN; \ + VERIFY_SECTION(project, filename, section, &options, &g_ebpf_platform_linux, false); \ } +// need these defined for type domain as well #define TEST_SECTION_LEGACY(dirname, filename, sectionname) \ TEST_SECTION(dirname, filename, sectionname) \ TEST_CASE("Try unmarshalling bad program: " dirname "/" filename " " sectionname, "[unmarshal]") { \ @@ -87,6 +114,7 @@ FAIL_UNMARSHAL("invalid", "invalid-lddw.o", ".text") REQUIRE(std::holds_alternative(prog_or_error)); \ } +// Tests for zoneCrab domain TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "1/0xdc06") TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/1") TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/3") diff --git a/src/test/test_verify_type_domain.cpp b/src/test/test_verify_type_domain.cpp deleted file mode 100644 index cadc10f24..000000000 --- a/src/test/test_verify_type_domain.cpp +++ /dev/null @@ -1,577 +0,0 @@ -// Copyright (c) Prevail Verifier contributors. -// SPDX-License-Identifier: MIT -#include -#include -#include "ebpf_verifier.hpp" - -#define FAIL_LOAD_ELF(dirname, filename, sectionname) \ - TEST_CASE("Try loading nonexisting program: " dirname "/" filename, "[elf]") { \ - try { \ - read_elf("ebpf-samples/" dirname "/" filename, sectionname, nullptr, &g_ebpf_platform_linux); \ - REQUIRE(false); \ - } catch (const std::runtime_error&) { \ - }\ - } - -// Some intentional failures -FAIL_LOAD_ELF("cilium", "not-found.o", "2/1") -FAIL_LOAD_ELF("cilium", "bpf_lxc.o", "not-found") -FAIL_LOAD_ELF("build", "badrelo.o", ".text") -FAIL_LOAD_ELF("invalid", "badsymsize.o", "xdp_redirect_map") - -#define FAIL_UNMARSHAL(dirname, filename, sectionname) \ - TEST_CASE("Try unmarshalling bad program: " dirname "/" filename, "[unmarshal]") { \ - auto raw_progs = read_elf("ebpf-samples/" dirname "/" filename, sectionname, nullptr, &g_ebpf_platform_linux); \ - REQUIRE(raw_progs.size() == 1); \ - raw_program raw_prog = raw_progs.back(); \ - std::variant prog_or_error = unmarshal(raw_prog); \ - REQUIRE(std::holds_alternative(prog_or_error)); \ - } - -// Some intentional unmarshal failures -FAIL_UNMARSHAL("build", "wronghelper.o", "xdp") -FAIL_UNMARSHAL("invalid", "invalid-lddw.o", ".text") - -#define VERIFY_SECTION(dirname, filename, sectionname, options, pass) \ - do { \ - auto raw_progs = read_elf("ebpf-samples/" dirname "/" filename, sectionname, nullptr, &g_ebpf_platform_linux); \ - REQUIRE(raw_progs.size() == 1); \ - raw_program raw_prog = raw_progs.back(); \ - std::variant prog_or_error = unmarshal(raw_prog); \ - REQUIRE(std::holds_alternative(prog_or_error)); \ - auto& prog = std::get(prog_or_error); \ - crab_results res = ebpf_verify_program(std::cout, prog, raw_prog.info, options, nullptr); \ - if (pass) \ - REQUIRE(res.pass_verify()); \ - else \ - REQUIRE(!res.pass_verify()); \ - } while (0) - -#define TEST_SECTION(project, filename, section) \ - TEST_CASE("./check ebpf-samples/" project "/" filename " " section, "[verify][samples][" project "]") { \ - ebpf_verifier_options_t options = ebpf_verifier_default_options; \ - options.abstract_domain = abstract_domain_kind::TYPE_DOMAIN; \ - VERIFY_SECTION(project, filename, section, &options, true); \ - } - -#define TEST_SECTION_REJECT(project, filename, section) \ - TEST_CASE("./check ebpf-samples/" project "/" filename " " section, "[verify][samples][" project "]") { \ - ebpf_verifier_options_t options = ebpf_verifier_default_options; \ - options.abstract_domain = abstract_domain_kind::TYPE_DOMAIN; \ - VERIFY_SECTION(project, filename, section, &options, false); \ - } - -#define TEST_SECTION_REJECT_IF_STRICT(project, filename, section) \ - TEST_CASE("./check ebpf-samples/" project "/" filename " " section, "[verify][samples][" project "]") { \ - ebpf_verifier_options_t options = ebpf_verifier_default_options; \ - options.abstract_domain = abstract_domain_kind::TYPE_DOMAIN; \ - VERIFY_SECTION(project, filename, section, &options, true); \ - options.strict = true; \ - VERIFY_SECTION(project, filename, section, &options, false); \ - } - -#define TEST_SECTION_FAIL(project, filename, section) \ - TEST_CASE("expect failure ebpf-samples/" project "/" filename " " section, "[!shouldfail][verify][samples][" project "]") { \ - ebpf_verifier_options_t options = ebpf_verifier_default_options; \ - options.abstract_domain = abstract_domain_kind::TYPE_DOMAIN; \ - VERIFY_SECTION(project, filename, section, &options, true); \ - } - -#define TEST_SECTION_REJECT_FAIL(project, filename, section) \ - TEST_CASE("expect failure ebpf-samples/" project "/" filename " " section, "[!shouldfail][verify][samples][" project "]") { \ - ebpf_verifier_options_t options = ebpf_verifier_default_options; \ - options.abstract_domain = abstract_domain_kind::TYPE_DOMAIN; \ - VERIFY_SECTION(project, filename, section, &options, false); \ - } - -TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "1/0xdc06") -TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/1") -TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/3") -TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/4") -TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/5") -TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/6") -TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/7") -TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "2/10") -TEST_SECTION("bpf_cilium_test", "bpf_lxc_jit.o", "from-container") - -TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "1/0x1010") -TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "2/1") -TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "2/2") -TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "2/3") -TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "2/4") -TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "2/5") -TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "2/6") -TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "2/7") -TEST_SECTION("bpf_cilium_test", "bpf_lxc-DUNKNOWN.o", "from-container") - -TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "1/0x1010") -TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "2/1") -TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "2/2") -TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "2/3") -TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "2/4") -TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "2/5") -TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "2/6") -TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "2/7") -TEST_SECTION("bpf_cilium_test", "bpf_lxc-DDROP_ALL.o", "from-container") - -TEST_SECTION("bpf_cilium_test", "bpf_netdev.o", "2/1") -TEST_SECTION("bpf_cilium_test", "bpf_netdev.o", "2/2") -TEST_SECTION("bpf_cilium_test", "bpf_netdev.o", "2/3") -TEST_SECTION("bpf_cilium_test", "bpf_netdev.o", "2/4") -TEST_SECTION("bpf_cilium_test", "bpf_netdev.o", "2/5") -TEST_SECTION("bpf_cilium_test", "bpf_netdev.o", "2/7") -TEST_SECTION("bpf_cilium_test", "bpf_netdev.o", "from-netdev") - -TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "2/1") -TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "2/2") -TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "2/3") -TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "2/4") -TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "2/5") -TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "2/7") -TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "3/2") -TEST_SECTION("bpf_cilium_test", "bpf_overlay.o", "from-overlay") - -TEST_SECTION("bpf_cilium_test", "bpf_lb-DLB_L3.o", "2/1") -TEST_SECTION("bpf_cilium_test", "bpf_lb-DLB_L3.o", "2/2") -TEST_SECTION("bpf_cilium_test", "bpf_lb-DLB_L3.o", "from-netdev") - -TEST_SECTION("bpf_cilium_test", "bpf_lb-DLB_L4.o", "2/1") -TEST_SECTION("bpf_cilium_test", "bpf_lb-DLB_L4.o", "2/2") -TEST_SECTION("bpf_cilium_test", "bpf_lb-DLB_L4.o", "from-netdev") - -TEST_SECTION("bpf_cilium_test", "bpf_lb-DUNKNOWN.o", "2/1") -TEST_SECTION("bpf_cilium_test", "bpf_lb-DUNKNOWN.o", "2/2") -TEST_SECTION("bpf_cilium_test", "bpf_lb-DUNKNOWN.o", "from-netdev") - -TEST_SECTION("cilium", "bpf_lb.o", "2/1") -TEST_SECTION("cilium", "bpf_lb.o", "from-netdev") - -TEST_SECTION("cilium", "bpf_lxc.o", "1/0x1010") -TEST_SECTION("cilium", "bpf_lxc.o", "2/1") -TEST_SECTION("cilium", "bpf_lxc.o", "2/3") -TEST_SECTION("cilium", "bpf_lxc.o", "2/4") -TEST_SECTION("cilium", "bpf_lxc.o", "2/5") -TEST_SECTION("cilium", "bpf_lxc.o", "2/6") -TEST_SECTION("cilium", "bpf_lxc.o", "2/7") -TEST_SECTION("cilium", "bpf_lxc.o", "2/8") -TEST_SECTION("cilium", "bpf_lxc.o", "2/9") -TEST_SECTION("cilium", "bpf_lxc.o", "2/10") -TEST_SECTION("cilium", "bpf_lxc.o", "2/11") -TEST_SECTION("cilium", "bpf_lxc.o", "2/12") -TEST_SECTION("cilium", "bpf_lxc.o", "from-container") - -TEST_SECTION("cilium", "bpf_netdev.o", "2/1") -TEST_SECTION("cilium", "bpf_netdev.o", "2/3") -TEST_SECTION("cilium", "bpf_netdev.o", "2/4") -TEST_SECTION("cilium", "bpf_netdev.o", "2/5") -TEST_SECTION("cilium", "bpf_netdev.o", "2/7") -TEST_SECTION("cilium", "bpf_netdev.o", "from-netdev") - -TEST_SECTION("cilium", "bpf_overlay.o", "2/1") -TEST_SECTION("cilium", "bpf_overlay.o", "2/3") -TEST_SECTION("cilium", "bpf_overlay.o", "2/4") -TEST_SECTION("cilium", "bpf_overlay.o", "2/5") -TEST_SECTION("cilium", "bpf_overlay.o", "2/7") -TEST_SECTION("cilium", "bpf_overlay.o", "from-overlay") - -TEST_SECTION("cilium", "bpf_xdp.o", "from-netdev") - -TEST_SECTION("cilium", "bpf_xdp_dsr_linux_v1_1.o", "from-netdev") -TEST_SECTION("cilium", "bpf_xdp_dsr_linux.o", "2/1") -TEST_SECTION("cilium", "bpf_xdp_dsr_linux.o", "from-netdev") - -TEST_SECTION("cilium", "bpf_xdp_snat_linux.o", "2/1") -TEST_SECTION("cilium", "bpf_xdp_snat_linux.o", "from-netdev") - -TEST_SECTION("linux", "cpustat_kern.o", "tracepoint/power/cpu_frequency") -TEST_SECTION("linux", "cpustat_kern.o", "tracepoint/power/cpu_idle") -TEST_SECTION("linux", "lathist_kern.o", "kprobe/trace_preempt_off") -TEST_SECTION("linux", "lathist_kern.o", "kprobe/trace_preempt_on") -TEST_SECTION("linux", "lwt_len_hist_kern.o", "len_hist") -TEST_SECTION("linux", "map_perf_test_kern.o", "kprobe/sys_getegid") -TEST_SECTION("linux", "map_perf_test_kern.o", "kprobe/sys_geteuid") -TEST_SECTION("linux", "map_perf_test_kern.o", "kprobe/sys_getgid") -TEST_SECTION("linux", "map_perf_test_kern.o", "kprobe/sys_getpgid") -TEST_SECTION("linux", "map_perf_test_kern.o", "kprobe/sys_getppid") -TEST_SECTION("linux", "map_perf_test_kern.o", "kprobe/sys_gettid") -TEST_SECTION("linux", "map_perf_test_kern.o", "kprobe/sys_getuid") -TEST_SECTION("linux", "offwaketime_kern.o", "kprobe/try_to_wake_up") -TEST_SECTION("linux", "offwaketime_kern.o", "tracepoint/sched/sched_switch") -TEST_SECTION("linux", "sampleip_kern.o", "perf_event") -TEST_SECTION("linux", "sock_flags_kern.o", "cgroup/sock1") -TEST_SECTION("linux", "sock_flags_kern.o", "cgroup/sock2") -TEST_SECTION("linux", "sockex1_kern.o", "socket1") -TEST_SECTION("linux", "sockex2_kern.o", "socket2") -TEST_SECTION("linux", "sockex3_kern.o", "socket/3") -TEST_SECTION("linux", "sockex3_kern.o", "socket/4") -TEST_SECTION("linux", "sockex3_kern.o", "socket/1") -TEST_SECTION("linux", "sockex3_kern.o", "socket/2") -TEST_SECTION("linux", "sockex3_kern.o", "socket/0") -TEST_SECTION("linux", "spintest_kern.o", "kprobe/__htab_percpu_map_update_elem") -TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_lock") -TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_lock_bh") -TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_lock_irq") -TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_lock_irqsave") -TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_trylock_bh") -TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_trylock") -TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_unlock") -TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_unlock_bh") -TEST_SECTION("linux", "spintest_kern.o", "kprobe/_raw_spin_unlock_irqrestore") -TEST_SECTION("linux", "spintest_kern.o", "kprobe/htab_map_alloc") -TEST_SECTION("linux", "spintest_kern.o", "kprobe/htab_map_update_elem") -TEST_SECTION("linux", "spintest_kern.o", "kprobe/mutex_spin_on_owner") -TEST_SECTION("linux", "spintest_kern.o", "kprobe/rwsem_spin_on_owner") -TEST_SECTION("linux", "spintest_kern.o", "kprobe/spin_lock") -TEST_SECTION("linux", "spintest_kern.o", "kprobe/spin_unlock") -TEST_SECTION("linux", "spintest_kern.o", "kprobe/spin_unlock_irqrestore") -TEST_SECTION("linux", "syscall_tp_kern.o", "tracepoint/syscalls/sys_enter_open") -TEST_SECTION("linux", "syscall_tp_kern.o", "tracepoint/syscalls/sys_exit_open") -TEST_SECTION("linux", "task_fd_query_kern.o", "kprobe/blk_start_request") -TEST_SECTION("linux", "task_fd_query_kern.o", "kretprobe/blk_account_io_completion") -TEST_SECTION("linux", "tc_l2_redirect_kern.o", "drop_non_tun_vip") -TEST_SECTION("linux", "tc_l2_redirect_kern.o", "l2_to_ip6tun_ingress_redirect") -TEST_SECTION("linux", "tc_l2_redirect_kern.o", "l2_to_iptun_ingress_forward") -TEST_SECTION("linux", "tc_l2_redirect_kern.o", "l2_to_iptun_ingress_redirect") -TEST_SECTION("linux", "tcp_basertt_kern.o", "sockops") -TEST_SECTION("linux", "tcp_bufs_kern.o", "sockops") -TEST_SECTION("linux", "tcp_cong_kern.o", "sockops") -TEST_SECTION("linux", "tcp_iw_kern.o", "sockops") -TEST_SECTION("linux", "tcbpf1_kern.o", "classifier") -TEST_SECTION("linux", "tcbpf1_kern.o", "clone_redirect_recv") -TEST_SECTION("linux", "tcbpf1_kern.o", "clone_redirect_xmit") -TEST_SECTION("linux", "tcbpf1_kern.o", "redirect_recv") -TEST_SECTION("linux", "tcbpf1_kern.o", "redirect_xmit") -TEST_SECTION("linux", "tcp_clamp_kern.o", "sockops") -TEST_SECTION("linux", "tcp_rwnd_kern.o", "sockops") -TEST_SECTION("linux", "tcp_synrto_kern.o", "sockops") -TEST_SECTION("linux", "test_cgrp2_tc_kern.o", "filter") -TEST_SECTION("linux", "test_current_task_under_cgroup_kern.o", "kprobe/sys_sync") -TEST_SECTION("linux", "test_overhead_kprobe_kern.o", "kprobe/__set_task_comm") -TEST_SECTION("linux", "test_overhead_kprobe_kern.o", "kprobe/urandom_read") -TEST_SECTION("linux", "test_overhead_raw_tp_kern.o", "raw_tracepoint/task_rename") -TEST_SECTION("linux", "test_overhead_raw_tp_kern.o", "raw_tracepoint/urandom_read") -TEST_SECTION("linux", "test_overhead_tp_kern.o", "tracepoint/random/urandom_read") -TEST_SECTION("linux", "test_overhead_tp_kern.o", "tracepoint/task/task_rename") -TEST_SECTION("linux", "test_probe_write_user_kern.o", "kprobe/sys_connect") -TEST_SECTION("linux", "trace_event_kern.o", "perf_event") -TEST_SECTION("linux", "trace_output_kern.o", "kprobe/sys_write") -TEST_SECTION("linux", "tracex1_kern.o", "kprobe/__netif_receive_skb_core") -TEST_SECTION("linux", "tracex2_kern.o", "kprobe/kfree_skb") -TEST_SECTION("linux", "tracex2_kern.o", "kprobe/sys_write") -TEST_SECTION("linux", "tracex3_kern.o", "kprobe/blk_account_io_completion") -TEST_SECTION("linux", "tracex3_kern.o", "kprobe/blk_start_request") -TEST_SECTION("linux", "tracex4_kern.o", "kprobe/kmem_cache_free") -TEST_SECTION("linux", "tracex4_kern.o", "kretprobe/kmem_cache_alloc_node") -TEST_SECTION("linux", "tracex5_kern.o", "kprobe/__seccomp_filter") -TEST_SECTION("linux", "tracex5_kern.o", "kprobe/0") -TEST_SECTION("linux", "tracex5_kern.o", "kprobe/1") -TEST_SECTION("linux", "tracex5_kern.o", "kprobe/9") -TEST_SECTION("linux", "tracex6_kern.o", "kprobe/htab_map_get_next_key") -TEST_SECTION("linux", "tracex6_kern.o", "kprobe/htab_map_lookup_elem") -TEST_SECTION("linux", "tracex7_kern.o", "kprobe/open_ctree") -TEST_SECTION("linux", "xdp_adjust_tail_kern.o", "xdp_icmp") -TEST_SECTION("linux", "xdp_fwd_kern.o", "xdp_fwd") -TEST_SECTION("linux", "xdp_fwd_kern.o", "xdp_fwd_direct") -TEST_SECTION("linux", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_cpumap_enqueue") -TEST_SECTION("linux", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_cpumap_kthread") -TEST_SECTION("linux", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_devmap_xmit") -TEST_SECTION("linux", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_exception") -TEST_SECTION("linux", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_redirect") -TEST_SECTION("linux", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_redirect_err") -TEST_SECTION("linux", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_redirect_map") -TEST_SECTION("linux", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_redirect_map_err") -TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "xdp_cpu_map0") -TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "xdp_cpu_map1_touch_data") -TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "xdp_cpu_map2_round_robin") -TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "xdp_cpu_map3_proto_separate") -TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "xdp_cpu_map4_ddos_filter_pktgen") -TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "xdp_cpu_map5_lb_hash_ip_pairs") -TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_cpumap_enqueue") -TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_cpumap_kthread") -TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_exception") -TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_redirect_err") -TEST_SECTION("linux", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_redirect_map_err") -TEST_SECTION("linux", "xdp_redirect_kern.o", "xdp_redirect") -TEST_SECTION("linux", "xdp_redirect_kern.o", "xdp_redirect_dummy") -TEST_SECTION("linux", "xdp_redirect_map_kern.o", "xdp_redirect_dummy") -TEST_SECTION("linux", "xdp_redirect_map_kern.o", "xdp_redirect_map") -TEST_SECTION("linux", "xdp_router_ipv4_kern.o", "xdp_router_ipv4") -TEST_SECTION("linux", "xdp_rxq_info_kern.o", "xdp_prog0") -TEST_SECTION("linux", "xdp_sample_pkts_kern.o", "xdp_sample") -TEST_SECTION("linux", "xdp_tx_iptunnel_kern.o", "xdp_tx_iptunnel") -TEST_SECTION("linux", "xdp1_kern.o", "xdp1") -TEST_SECTION("linux", "xdp2_kern.o", "xdp1") -TEST_SECTION("linux", "xdp2skb_meta_kern.o", "tc_mark") -TEST_SECTION("linux", "xdp2skb_meta_kern.o", "xdp_mark") -TEST_SECTION("linux", "xdpsock_kern.o", "xdp_sock") -// Finally passes; still requires double-check -TEST_SECTION("linux", "map_perf_test_kern.o", "kprobe/sys_connect") - -TEST_SECTION("prototype-kernel", "napi_monitor_kern.o", "tracepoint/irq/softirq_entry") -TEST_SECTION("prototype-kernel", "napi_monitor_kern.o", "tracepoint/irq/softirq_exit") -TEST_SECTION("prototype-kernel", "napi_monitor_kern.o", "tracepoint/irq/softirq_raise") -TEST_SECTION("prototype-kernel", "napi_monitor_kern.o", "tracepoint/napi/napi_poll") -TEST_SECTION("prototype-kernel", "tc_bench01_redirect_kern.o", "ingress_redirect") -TEST_SECTION("prototype-kernel", "xdp_bench01_mem_access_cost_kern.o", "xdp_bench01") -TEST_SECTION("prototype-kernel", "xdp_bench02_drop_pattern_kern.o", "xdp_bench02") -TEST_SECTION("prototype-kernel", "xdp_ddos01_blacklist_kern.o", "xdp_prog") -TEST_SECTION("prototype-kernel", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_redirect") -TEST_SECTION("prototype-kernel", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_redirect_err") -TEST_SECTION("prototype-kernel", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_redirect_map_err") -TEST_SECTION("prototype-kernel", "xdp_monitor_kern.o", "tracepoint/xdp/xdp_redirect_map") -TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "xdp_cpu_map0") -TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "xdp_cpu_map2_round_robin") -TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_cpumap_enqueue") -TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_cpumap_kthread") -TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_exception") -TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_redirect_err") -TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "tracepoint/xdp/xdp_redirect_map_err") -TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "xdp_cpu_map1_touch_data") -TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "xdp_cpu_map3_proto_separate") -TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "xdp_cpu_map4_ddos_filter_pktgen") -TEST_SECTION("prototype-kernel", "xdp_redirect_cpu_kern.o", "xdp_cpu_map5_ip_l3_flow_hash") -TEST_SECTION("prototype-kernel", "xdp_redirect_err_kern.o", "xdp_redirect_dummy") -TEST_SECTION("prototype-kernel", "xdp_redirect_err_kern.o", "xdp_redirect_map") -TEST_SECTION("prototype-kernel", "xdp_redirect_err_kern.o", "xdp_redirect_map_rr") -TEST_SECTION("prototype-kernel", "xdp_tcpdump_kern.o", "xdp_tcpdump_to_perf_ring") -TEST_SECTION("prototype-kernel", "xdp_ttl_kern.o", "xdp_ttl") -TEST_SECTION("prototype-kernel", "xdp_vlan01_kern.o", "tc_vlan_push") -TEST_SECTION("prototype-kernel", "xdp_vlan01_kern.o", "xdp_drop_vlan_4011") -TEST_SECTION("prototype-kernel", "xdp_vlan01_kern.o", "xdp_vlan_change") -TEST_SECTION("prototype-kernel", "xdp_vlan01_kern.o", "xdp_vlan_remove_outer") -TEST_SECTION("prototype-kernel", "xdp_vlan01_kern.o", "xdp_vlan_remove_outer2") - -TEST_SECTION("ovs", "datapath.o", "tail-0") -TEST_SECTION("ovs", "datapath.o", "tail-1") -TEST_SECTION("ovs", "datapath.o", "tail-2") -TEST_SECTION("ovs", "datapath.o", "tail-3") -TEST_SECTION("ovs", "datapath.o", "tail-4") -TEST_SECTION("ovs", "datapath.o", "tail-5") -TEST_SECTION("ovs", "datapath.o", "tail-7") -TEST_SECTION("ovs", "datapath.o", "tail-8") -TEST_SECTION("ovs", "datapath.o", "tail-11") -TEST_SECTION("ovs", "datapath.o", "tail-12") -TEST_SECTION("ovs", "datapath.o", "tail-13") -TEST_SECTION("ovs", "datapath.o", "tail-32") -TEST_SECTION("ovs", "datapath.o", "tail-33") -TEST_SECTION("ovs", "datapath.o", "tail-35") -TEST_SECTION("ovs", "datapath.o", "af_xdp") -TEST_SECTION("ovs", "datapath.o", "downcall") -TEST_SECTION("ovs", "datapath.o", "egress") -TEST_SECTION("ovs", "datapath.o", "ingress") -TEST_SECTION("ovs", "datapath.o", "xdp") - -TEST_SECTION("suricata", "bypass_filter.o", "filter") -TEST_SECTION("suricata", "lb.o", "loadbalancer") -TEST_SECTION("suricata", "filter.o", "filter") -TEST_SECTION("suricata", "vlan_filter.o", "filter") -TEST_SECTION("suricata", "xdp_filter.o", "xdp") - -TEST_SECTION("falco", "probe.o", "raw_tracepoint/filler/sys_accept4_e") -TEST_SECTION("falco", "probe.o", "raw_tracepoint/filler/sys_empty") -TEST_SECTION("falco", "probe.o", "raw_tracepoint/filler/sys_pread64_e") -TEST_SECTION("falco", "probe.o", "raw_tracepoint/filler/sys_preadv64_e") -TEST_SECTION("falco", "probe.o", "raw_tracepoint/filler/sys_pwrite64_e") -TEST_SECTION("falco", "probe.o", "raw_tracepoint/filler/sys_single_x") -TEST_SECTION("falco", "probe.o", "raw_tracepoint/filler/sys_sysdigevent_e") -TEST_SECTION("falco", "probe.o", "raw_tracepoint/filler/terminate_filler") -TEST_SECTION("falco", "probe.o", "raw_tracepoint/page_fault_kernel") -TEST_SECTION("falco", "probe.o", "raw_tracepoint/page_fault_user") -TEST_SECTION("falco", "probe.o", "raw_tracepoint/sched_switch") -TEST_SECTION("falco", "probe.o", "raw_tracepoint/signal_deliver") - -// Test some programs that should pass verification except when the strict flag is set. -TEST_SECTION_REJECT_IF_STRICT("build", "mapoverflow.o", ".text") -TEST_SECTION_REJECT_IF_STRICT("build", "mapunderflow.o", ".text") - -/* - * These programs contain "call -1" instruction and cannot be verified: -TEST_SECTION("raw_tracepoint/filler/sys_access_e") -TEST_SECTION("raw_tracepoint/filler/sys_bpf_x") -TEST_SECTION("raw_tracepoint/filler/sys_brk_munmap_mmap_x") -TEST_SECTION("raw_tracepoint/filler/sys_eventfd_e") -TEST_SECTION("raw_tracepoint/filler/sys_execve_e") -TEST_SECTION("raw_tracepoint/filler/sys_generic") -TEST_SECTION("raw_tracepoint/filler/sys_getrlimit_setrlimit_e") -TEST_SECTION("raw_tracepoint/filler/sys_getrlimit_setrlrimit_x") -TEST_SECTION("raw_tracepoint/filler/sys_mount_e") -TEST_SECTION("raw_tracepoint/filler/sys_nanosleep_e") -TEST_SECTION("raw_tracepoint/filler/sys_pagefault_e") -TEST_SECTION("raw_tracepoint/filler/sys_procexit_e") -TEST_SECTION("raw_tracepoint/filler/sys_single") -TEST_SECTION("raw_tracepoint/filler/sys_unshare_e") -TEST_SECTION("raw_tracepoint/sched_process_exit") -TEST_SECTION("raw_tracepoint/filler/sys_chmod_x") -TEST_SECTION("raw_tracepoint/filler/sys_fchmod_x") -TEST_SECTION("raw_tracepoint/filler/sys_fcntl_e") -TEST_SECTION("raw_tracepoint/filler/sys_flock_e") -TEST_SECTION("raw_tracepoint/filler/sys_poll_x") -TEST_SECTION("raw_tracepoint/filler/sys_prlimit_e") -TEST_SECTION("raw_tracepoint/filler/sys_prlimit_x") -TEST_SECTION("raw_tracepoint/filler/sys_ptrace_e") -TEST_SECTION("raw_tracepoint/filler/sys_quotactl_e") -TEST_SECTION("raw_tracepoint/filler/sys_semop_x") -TEST_SECTION("raw_tracepoint/filler/sys_send_e") -TEST_SECTION("raw_tracepoint/filler/sys_sendfile_x") -TEST_SECTION("raw_tracepoint/filler/sys_setns_e") -TEST_SECTION("raw_tracepoint/filler/sys_shutdown_e") -TEST_SECTION("raw_tracepoint/filler/sys_fchmodat_x") -TEST_SECTION("raw_tracepoint/filler/sys_futex_e") -TEST_SECTION("raw_tracepoint/filler/sys_lseek_e") -TEST_SECTION("raw_tracepoint/filler/sys_mkdirat_x") -TEST_SECTION("raw_tracepoint/filler/sys_poll_e") -TEST_SECTION("raw_tracepoint/filler/sys_ptrace_x") -TEST_SECTION("raw_tracepoint/filler/sys_quotactl_x") -TEST_SECTION("raw_tracepoint/filler/sys_semget_e") -TEST_SECTION("raw_tracepoint/filler/sys_signaldeliver_e") -TEST_SECTION("raw_tracepoint/filler/sys_symlinkat_x") -TEST_SECTION("raw_tracepoint/filler/sys_unlinkat_x") -TEST_SECTION("raw_tracepoint/filler/sys_writev_e") -TEST_SECTION("raw_tracepoint/filler/sys_llseek_e") -TEST_SECTION("raw_tracepoint/filler/sys_ppoll_e") -TEST_SECTION("raw_tracepoint/filler/sys_pwritev_e") -TEST_SECTION("raw_tracepoint/filler/sys_renameat_x") -TEST_SECTION("raw_tracepoint/filler/sys_semctl_e") -TEST_SECTION("raw_tracepoint/filler/sched_switch_e") -TEST_SECTION("raw_tracepoint/filler/sys_getsockopt_x") -TEST_SECTION("raw_tracepoint/filler/sys_linkat_x") -TEST_SECTION("raw_tracepoint/filler/sys_renameat2_x") -TEST_SECTION("raw_tracepoint/filler/sys_sendfile_e") -TEST_SECTION("raw_tracepoint/filler/sys_setsockopt_x") -TEST_SECTION("raw_tracepoint/filler/sys_getresuid_and_gid_x") -TEST_SECTION("raw_tracepoint/filler/sys_mmap_e") -TEST_SECTION("raw_tracepoint/filler/sys_socket_bind_x") -TEST_SECTION("raw_tracepoint/filler/sys_socket_x") -TEST_SECTION("raw_tracepoint/sys_enter") -TEST_SECTION("raw_tracepoint/sys_exit") -TEST_SECTION("raw_tracepoint/filler/sys_pipe_x") -TEST_SECTION("raw_tracepoint/filler/sys_socketpair_x") -TEST_SECTION("raw_tracepoint/filler/sys_creat_x") -TEST_SECTION("raw_tracepoint/filler/sys_open_x") -TEST_SECTION("raw_tracepoint/filler/sys_openat_x") -TEST_SECTION("raw_tracepoint/filler/sys_autofill") -TEST_SECTION("raw_tracepoint/filler/proc_startupdate") -TEST_SECTION("raw_tracepoint/filler/sys_recvmsg_x_2") -TEST_SECTION("raw_tracepoint/filler/sys_sendmsg_e") -TEST_SECTION("raw_tracepoint/filler/sys_connect_x") -TEST_SECTION("raw_tracepoint/filler/sys_sendto_e") -TEST_SECTION("raw_tracepoint/filler/sys_accept_x") -TEST_SECTION("raw_tracepoint/filler/sys_read_x") -TEST_SECTION("raw_tracepoint/filler/sys_recv_x") -TEST_SECTION("raw_tracepoint/filler/sys_recvmsg_x") -TEST_SECTION("raw_tracepoint/filler/sys_send_x") -TEST_SECTION("raw_tracepoint/filler/proc_startupdate_3") -TEST_SECTION("raw_tracepoint/filler/sys_readv_preadv_x") -TEST_SECTION("raw_tracepoint/filler/sys_write_x") -TEST_SECTION("raw_tracepoint/filler/sys_writev_pwritev_x") -TEST_SECTION("raw_tracepoint/filler/sys_sendmsg_x") -TEST_SECTION("raw_tracepoint/filler/proc_startupdate_2") -TEST_SECTION("raw_tracepoint/filler/sys_recvfrom_x") -*/ -TEST_SECTION("build", "byteswap.o", ".text") -TEST_SECTION("build", "stackok.o", ".text") -TEST_SECTION("build", "packet_start_ok.o", "xdp") -TEST_SECTION("build", "packet_access.o", "xdp") -TEST_SECTION("build", "tail_call.o", "xdp_prog") -TEST_SECTION("build", "map_in_map.o", ".text") -TEST_SECTION("build", "map_in_map_legacy.o", ".text") -TEST_SECTION("build", "twomaps.o", ".text"); -TEST_SECTION("build", "twostackvars.o", ".text"); -TEST_SECTION("build", "twotypes.o", ".text"); - -// Test some programs that ought to fail verification. -TEST_SECTION_REJECT("build", "badhelpercall.o", ".text") -TEST_SECTION_REJECT("build", "ctxoffset.o", "sockops") -TEST_SECTION_REJECT("build", "badmapptr.o", "test") -TEST_SECTION_REJECT("build", "exposeptr.o", ".text") -TEST_SECTION_REJECT("build", "exposeptr2.o", ".text") -TEST_SECTION_REJECT("build", "mapvalue-overrun.o", ".text") -TEST_SECTION_REJECT("build", "nullmapref.o", "test") -TEST_SECTION_REJECT("build", "packet_overflow.o", "xdp") -TEST_SECTION_REJECT("build", "packet_reallocate.o", "socket_filter") -TEST_SECTION_REJECT("build", "tail_call_bad.o", "xdp_prog") -TEST_SECTION_REJECT("build", "ringbuf_uninit.o", ".text"); - -// The following eBPF programs currently fail verification. -// If the verifier is later updated to accept them, these should -// be changed to TEST_SECTION(). - -// Unsupported: ebpf-function -TEST_SECTION_FAIL("prototype-kernel", "xdp_ddos01_blacklist_kern.o", ".text") - -// Unsupported: implications are lost in correlated branches -TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/7") - -// Failure: 166:168: Upper bound must be at most packet_size (valid_access(r4.offset, width=2) for read) -// This is the result of merging two branches, one with value 0 and another with value -22, -// then checking that the result is != 0. The minor issue is not handling the int32 comparison precisely enough. -// The bigger issue is that the convexity of the numerical domain means that precise handling would still get -// [-22, -1] which is not sufficient (at most -2 is needed) -TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/10") -TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/21") -TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/24") - -TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/15") - -TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/17") - -// Failure: trying to access r4 where r4.packet_offset=[0, 255] and packet_size=[54, 65534] -// Root cause: r5.value=[0, 65535] 209: w5 >>= 8; clears r5 instead of yielding [0, 255] -TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/18") -TEST_SECTION_FAIL("cilium", "bpf_xdp_snat_linux.o", "2/10") -TEST_SECTION_FAIL("cilium", "bpf_xdp_snat_linux.o", "2/18") - -TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/19") - -// Failure: 230: Upper bound must be at most packet_size (valid_access(r3.offset+32, width=8) for write) -// r3.packet_offset=[0, 82] and packet_size=[34, 65534] -// looks like a combination of misunderstanding the value passed to xdp_adjust_tail() -// which is "r7.value=[0, 82]; w7 -= r9;" where r9.value where "r7.value-r9.value<=48" -TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/20") - -TEST_SECTION_FAIL("cilium", "bpf_xdp_snat_linux.o", "2/7") -TEST_SECTION_FAIL("cilium", "bpf_xdp_snat_linux.o", "2/15") -TEST_SECTION_FAIL("cilium", "bpf_xdp_snat_linux.o", "2/17") -TEST_SECTION_FAIL("cilium", "bpf_xdp_snat_linux.o", "2/19") - -// Failure (&255): assert r5.type == number; w5 &= 255; -// fails since in one branch (77) r5 is a number but in another (92:93) it is a packet -TEST_SECTION_FAIL("cilium", "bpf_xdp_snat_linux.o", "2/24") -// Failure (&255): assert r3.type == number; w3 &= 255; -TEST_SECTION_FAIL("cilium", "bpf_xdp_dsr_linux.o", "2/16") -TEST_SECTION_FAIL("cilium", "bpf_xdp_snat_linux.o", "2/16") - -// False positive, unknown cause -TEST_SECTION_FAIL("linux", "test_map_in_map_kern.o", "kprobe/sys_connect") - -void test_analyze_thread(cfg_t* cfg, program_info* info, bool* res) { - *res = run_ebpf_analysis(std::cout, *cfg, *info, nullptr, nullptr); -} - -// Test multithreading -TEST_CASE("multithreading", "[verify][multithreading]") { - auto raw_progs1 = read_elf("ebpf-samples/bpf_cilium_test/bpf_netdev.o", "2/1", nullptr, &g_ebpf_platform_linux); - REQUIRE(raw_progs1.size() == 1); - raw_program raw_prog1 = raw_progs1.back(); - std::variant prog_or_error1 = unmarshal(raw_prog1); - REQUIRE(std::holds_alternative(prog_or_error1)); - auto& prog1 = std::get(prog_or_error1); - cfg_t cfg1 = prepare_cfg(prog1, raw_prog1.info, true); - - auto raw_progs2 = read_elf("ebpf-samples/bpf_cilium_test/bpf_netdev.o", "2/2", nullptr, &g_ebpf_platform_linux); - REQUIRE(raw_progs2.size() == 1); - raw_program raw_prog2 = raw_progs2.back(); - std::variant prog_or_error2 = unmarshal(raw_prog2); - REQUIRE(std::holds_alternative(prog_or_error2)); - auto& prog2 = std::get(prog_or_error2); - cfg_t cfg2 = prepare_cfg(prog2, raw_prog2.info, true); - - bool res1, res2; - std::thread a(test_analyze_thread, &cfg1, &raw_prog1.info, &res1); - std::thread b(test_analyze_thread, &cfg2, &raw_prog2.info, &res2); - a.join(); - b.join(); - - REQUIRE(res1); - REQUIRE(res2); -} From e389226c88fd055bb666ff19dd377f7b7a1413b1 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 13 Nov 2023 21:00:05 -0500 Subject: [PATCH 141/373] Refactoring and simplifying printing functionality Signed-off-by: Ameer Hamza --- src/crab/common.cpp | 63 +++++++++++++++++++++++---------- src/crab/common.hpp | 2 ++ src/crab/type_domain.cpp | 22 +++++++----- src/crab/type_ostream.cpp | 74 +++++++++++++++++++++++++++++---------- src/crab/type_ostream.hpp | 29 ++++++++++----- 5 files changed, 136 insertions(+), 54 deletions(-) diff --git a/src/crab/common.cpp b/src/crab/common.cpp index 3db09febd..443690a59 100644 --- a/src/crab/common.cpp +++ b/src/crab/common.cpp @@ -19,6 +19,20 @@ namespace std { namespace crab { +inline std::string get_reg_ptr(const region_t& r) noexcept { + switch (r) { + case region_t::T_CTX: + return "ctx_p"; + case region_t::T_STACK: + return "stack_p"; + case region_t::T_SHARED: + return "shared_p"; + default: + return "packet_p"; + } + __builtin_unreachable(); +} + bool mock_interval_t::operator==(const mock_interval_t& other) const { return (to_interval() == other.to_interval()); } @@ -79,10 +93,17 @@ bool mapfd_t::has_type_map_programs() const { void mapfd_t::write(std::ostream& o) const { if (has_type_map_programs()) { - o << "map_fd_programs"; + o << "map_fd_programs "; + } + else { + o << "map_fd "; + } + auto mapfd = m_mapfd.to_interval(); + if (auto mapfd_singleton = mapfd.singleton()) { + o << *mapfd_singleton; } else { - o << "map_fd"; + o << mapfd; } } @@ -111,23 +132,29 @@ std::size_t reg_with_loc_t::hash() const { return seed; } -inline std::string get_reg_ptr(const region_t& r) { - switch (r) { - case region_t::T_CTX: - return "ctx_p"; - case region_t::T_STACK: - return "stack_p"; - case region_t::T_SHARED: - return "shared_p"; - default: - return "packet_p"; - } -} - void ptr_with_off_t::write(std::ostream& o) const { - o << get_reg_ptr(m_r) << "<" << m_offset.to_interval(); - if (m_region_size.lb() >= number_t{0}) o << "," << m_region_size.to_interval(); - o << ">"; + o << get_reg_ptr(m_r); + auto offset = m_offset.to_interval(); + auto region_size = m_region_size.to_interval(); + if (!offset.is_top()) { + o << "<"; + if (auto off_singleton = offset.singleton()) { + o << *off_singleton; + } + else { + o << offset; + } + if (region_size.lb() >= number_t{0}) { + o << ","; + if (auto rs_singleton = region_size.singleton()) { + o << *rs_singleton; + } + else { + o << region_size; + } + } + o << ">"; + } } std::ostream& operator<<(std::ostream& o, const ptr_with_off_t& p) { diff --git a/src/crab/common.hpp b/src/crab/common.hpp index 082ecffa9..0208bcb2e 100644 --- a/src/crab/common.hpp +++ b/src/crab/common.hpp @@ -28,6 +28,8 @@ enum class region_t { T_SHARED }; +inline std::string get_reg_ptr(const region_t& r) noexcept; + enum class nullness_t { MAYBE_NULL, NOT_NULL, _NULL }; class packet_ptr_t { diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 1d8a14364..1cf1f3068 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -533,8 +533,8 @@ void type_domain_t::print_ctx() const { auto ptr = m_region.find_in_ctx(k); auto dist = m_offset.find_in_ctx(k); if (ptr) { - std::cout << "\t\t" << k << ": "; - print_ptr_type(std::cout, *ptr, dist); + std::cout << "\t\t"; + print_non_numeric_memory_cell(std::cout, k, k+3, *ptr, dist); std::cout << ",\n"; } } @@ -552,11 +552,14 @@ void type_domain_t::print_stack() const { auto ptr_or_mapfd_cells = maybe_ptr_or_mapfd_cells.value(); int width = ptr_or_mapfd_cells.second; auto ptr_or_mapfd = ptr_or_mapfd_cells.first; - std::cout << "\t\t[" << k << "-" << k+width-1 << "] : "; - if (dist) - print_ptr_or_mapfd_type(std::cout, ptr_or_mapfd, dist->first); - else - print_ptr_or_mapfd_type(std::cout, ptr_or_mapfd, std::nullopt); + std::cout << "\t\t"; + if (dist) { + print_non_numeric_memory_cell(std::cout, k, k+width-1, ptr_or_mapfd, + std::optional(dist->first)); + } + else { + print_non_numeric_memory_cell(std::cout, k, k+width-1, ptr_or_mapfd); + } std::cout << ",\n"; } } @@ -564,8 +567,9 @@ void type_domain_t::print_stack() const { auto maybe_interval_cells = m_interval.find_in_stack(k); if (maybe_interval_cells) { auto interval_cells = maybe_interval_cells.value(); - std::cout << "\t\t" << "[" << k << "-" << k+interval_cells.second-1 << "] : "; - print_number(std::cout, interval_cells.first.to_interval()); + std::cout << "\t\t"; + print_numeric_memory_cell(std::cout, k, k+interval_cells.second-1, + interval_cells.first.to_interval()); std::cout << ",\n"; } } diff --git a/src/crab/type_ostream.cpp b/src/crab/type_ostream.cpp index 88f1fba50..b1b7a99e1 100644 --- a/src/crab/type_ostream.cpp +++ b/src/crab/type_ostream.cpp @@ -3,46 +3,84 @@ #include "crab/type_ostream.hpp" -void print_ptr_type(std::ostream& o, const crab::ptr_or_mapfd_t& ptr, std::optional d) { +void print_non_numeric_memory_cell(std::ostream& o, int start, int end, + const crab::ptr_or_mapfd_t& ptr, std::optional d) { if (std::holds_alternative(ptr)) { - o << std::get(ptr); + o << "[" << start << "-" << end << "] : " << std::get(ptr); } else if (std::holds_alternative(ptr)) { - o << std::get(ptr) << "<" << (d ? *d : crab::dist_t{}) << ">"; + if (d) { + o << "[" << start << "-" << end << "] : " << + std::get(ptr) << "<" << *d << ">"; + } + else { + o << "[" << start << "-" << end << "] : " << std::get(ptr); + } + } + else { + o << "[" << start << "-" << end << "] : " << std::get(ptr); } } -void print_number(std::ostream& o, crab::interval_t n) { - o << "number"; - if (!n.is_top()) { +void print_numeric_memory_cell(std::ostream& o, int start, int end, crab::interval_t n) { + if (n.is_top()) { + o << "[" << start << "-" << end << "] : number"; + } + else { if (auto n_singleton = n.singleton()) { - o << "<" << *n_singleton << ">"; + o << "[" << start << "-" << end << "] : number<" << *n_singleton << ">"; } else { - o << "<" << n << ">"; + o << "[" << start << "-" << end << "] : number<" << n << ">"; } } } -void print_ptr_or_mapfd_type(std::ostream& o, const crab::ptr_or_mapfd_t& ptr_or_mapfd, std::optional d) { - if (std::holds_alternative(ptr_or_mapfd)) { - o << std::get(ptr_or_mapfd); +void print_memory_cell(std::ostream& o, int start, int end, + const std::optional& p, std::optional d, + std::optional n) { + if (n) print_numeric_memory_cell(o, start, end, n->to_interval()); + else if (p) print_non_numeric_memory_cell(o, start, end, *p, d); +} + +void print_non_numeric_register(std::ostream& o, Reg r, const crab::ptr_or_mapfd_t& ptr, + std::optional d) { + if (std::holds_alternative(ptr)) { + o << r << " : " << std::get(ptr); + } + else if (std::holds_alternative(ptr)) { + if (d) { + o << r << " : " << std::get(ptr) << "<" << *d << ">"; + } + else { + o << r << " : " << std::get(ptr); + } } else { - print_ptr_type(o, ptr_or_mapfd, d); + o << r << " : " << std::get(ptr); } } -void print_register(std::ostream& o, const Reg& r, const std::optional& p, std::optional d, std::optional n) { - o << r << " : "; - if (p) { - print_ptr_or_mapfd_type(o, *p, d); +void print_numeric_register(std::ostream& o, Reg r, crab::interval_t n) { + if (n.is_top()) { + o << r << " : number"; } - else if (n) { - print_number(o, n->to_interval()); + else { + if (auto n_singleton = n.singleton()) { + o << r << " : number<" << *n_singleton << ">"; + } + else { + o << r << " : number<" << n << ">"; + } } } +void print_register(std::ostream& o, Reg r, const std::optional& p, + std::optional d, std::optional n) { + if (n) print_numeric_register(o, r, n->to_interval()); + else if (p) print_non_numeric_register(o, r, *p, d); +} + inline std::string size_(int w) { return std::string("u") + std::to_string(w * 8); } void print_annotated(std::ostream& o, const Call& call, std::optional& p, diff --git a/src/crab/type_ostream.hpp b/src/crab/type_ostream.hpp index fa258e585..a1f34d709 100644 --- a/src/crab/type_ostream.hpp +++ b/src/crab/type_ostream.hpp @@ -9,12 +9,23 @@ #include "crab/common.hpp" #include "crab/offset_domain.hpp" -void print_ptr_or_mapfd_type(std::ostream&, const crab::ptr_or_mapfd_t&, std::optional); -void print_number(std::ostream&, crab::interval_t); -void print_ptr_type(std::ostream&, const crab::ptr_or_mapfd_t& ptr, std::optional); -void print_register(std::ostream& o, const Reg& r, const std::optional& p, std::optional, std::optional); -void print_annotated(std::ostream& o, const Call& call, std::optional& p, std::optional&); -void print_annotated(std::ostream& o, const Bin& b, std::optional& p, std::optional&, std::optional&); -void print_annotated(std::ostream& o, const LoadMapFd& u, std::optional& p); -void print_annotated(std::ostream& o, const Mem& b, std::optional& p, std::optional&, std::optional&); -void print_annotated(std::ostream& o, const Un& b, std::optional&); +void print_numeric_register(std::ostream&, Reg, crab::interval_t); +void print_numeric_memory_cell(std::ostream&, int, int, crab::interval_t); +void print_non_numeric_register(std::ostream&, Reg, const crab::ptr_or_mapfd_t& ptr, + std::optional = std::nullopt); +void print_non_numeric_memory_cell(std::ostream&, int, int, const crab::ptr_or_mapfd_t& ptr, + std::optional = std::nullopt); +void print_register(std::ostream&, Reg, const std::optional&, + std::optional, std::optional); +void print_memory_cell(std::ostream&, int, int, const std::optional&, + std::optional, std::optional); + +// Print select transformers +void print_annotated(std::ostream&, const Call&, std::optional&, + std::optional&); +void print_annotated(std::ostream&, const Bin&, std::optional&, + std::optional&, std::optional&); +void print_annotated(std::ostream&, const LoadMapFd&, std::optional&); +void print_annotated(std::ostream&, const Mem&, std::optional&, + std::optional&, std::optional&); +void print_annotated(std::ostream&, const Un&, std::optional&); From 6f14855db888d8afec438f43f7443b57474d069d Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 13 Nov 2023 22:39:40 -0500 Subject: [PATCH 142/373] Making setup_entry method parametric Making setup_entry method in type domain, and region domain parametric, to allow initialization of r1 as ctx ptr based on the scenario; Some changes also made to crab_verifier, which defines those cases; Signed-off-by: Ameer Hamza --- src/crab/region_domain.cpp | 8 +++++--- src/crab/region_domain.hpp | 2 +- src/crab/type_domain.cpp | 4 ++-- src/crab/type_domain.hpp | 2 +- src/crab_verifier.cpp | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index 32a88f38f..d36bc50e1 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -814,7 +814,7 @@ void region_domain_t::operator()(const ValidStore& u, location_t loc) { // nothing to do here } -region_domain_t&& region_domain_t::setup_entry() { +region_domain_t&& region_domain_t::setup_entry(bool init_r1) { std::shared_ptr ctx = std::make_shared(global_program_info.get().type.context_descriptor); @@ -822,9 +822,11 @@ region_domain_t&& region_domain_t::setup_entry() { register_types_t typ(std::make_shared()); auto loc = std::make_pair(label_t::entry, (unsigned int)0); - auto ctx_ptr_r1 = ptr_with_off_t(region_t::T_CTX, -1, mock_interval_t{number_t{0}}); + if (init_r1) { + auto ctx_ptr_r1 = ptr_with_off_t(region_t::T_CTX, -1, mock_interval_t{number_t{0}}); + typ.insert(register_t{R1_ARG}, loc, ctx_ptr_r1); + } auto stack_ptr_r10 = ptr_with_off_t(region_t::T_STACK, -1, mock_interval_t{number_t{512}}); - typ.insert(register_t{R1_ARG}, loc, ctx_ptr_r1); typ.insert(register_t{R10_STACK_POINTER}, loc, stack_ptr_r10); static region_domain_t inv(std::move(typ), stack_t::top(), ctx); diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index b78ad8c8a..2f5b369c2 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -111,7 +111,7 @@ class region_domain_t final { : m_stack(std::move(_st)), m_registers(std::move(_types)), m_ctx(_ctx), m_shared_ptr_aliases(std::move(_shared_ptr_aliases)) {} // eBPF initialization: R1 points to ctx, R10 to stack, etc. - static region_domain_t&& setup_entry(); + static region_domain_t&& setup_entry(bool); // bottom/top static region_domain_t bottom(); void set_to_top(); diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 1cf1f3068..7b061ebb9 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -428,8 +428,8 @@ void type_domain_t::operator()(const ZeroCtxOffset& u, location_t loc) { m_region(u, loc); } -type_domain_t type_domain_t::setup_entry() { - auto&& reg = crab::region_domain_t::setup_entry(); +type_domain_t type_domain_t::setup_entry(bool init_r1) { + auto&& reg = crab::region_domain_t::setup_entry(init_r1); auto&& off = offset_domain_t::setup_entry(); auto&& interval = interval_prop_domain_t::setup_entry(); type_domain_t typ(std::move(reg), std::move(off), std::move(interval)); diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 482c3054c..61e6152e4 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -29,7 +29,7 @@ class type_domain_t final { type_domain_t& operator=(type_domain_t&& o) = default; type_domain_t& operator=(const type_domain_t& o) = default; // eBPF initialization: R1 points to ctx, R10 to stack, etc. - static type_domain_t setup_entry(); + static type_domain_t setup_entry(bool); // bottom/top static type_domain_t bottom(); void set_to_top(); diff --git a/src/crab_verifier.cpp b/src/crab_verifier.cpp index 4c5d904f6..5b6297867 100644 --- a/src/crab_verifier.cpp +++ b/src/crab_verifier.cpp @@ -159,7 +159,7 @@ static abstract_domain_t make_initial(const ebpf_verifier_options_t* options) { return abstract_domain_t(entry_inv); } case abstract_domain_kind::TYPE_DOMAIN: { - type_domain_t entry_inv = type_domain_t::setup_entry(); + type_domain_t entry_inv = type_domain_t::setup_entry(true); return abstract_domain_t(entry_inv); } default: From 0cc1fe5b0a3299aa8e8ccf2c04ec841b12ce48d6 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 13 Nov 2023 23:10:13 -0500 Subject: [PATCH 143/373] Added implementation to support string_invariants string_invariants are required to run the test cases in the prevail framework, hence such support is added for type domain; This includes support for creating the string invariant, and converting it back to actual types; Signed-off-by: Ameer Hamza --- src/crab/interval_prop_domain.cpp | 9 ++ src/crab/interval_prop_domain.hpp | 2 + src/crab/offset_domain.cpp | 8 + src/crab/offset_domain.hpp | 2 + src/crab/region_domain.cpp | 9 ++ src/crab/region_domain.hpp | 2 + src/crab/type_domain.cpp | 251 +++++++++++++++++++++++++++++- src/crab/type_domain.hpp | 11 +- 8 files changed, 290 insertions(+), 4 deletions(-) diff --git a/src/crab/interval_prop_domain.cpp b/src/crab/interval_prop_domain.cpp index d9dc30ef1..339ad6b1f 100644 --- a/src/crab/interval_prop_domain.cpp +++ b/src/crab/interval_prop_domain.cpp @@ -277,6 +277,15 @@ std::optional interval_prop_domain_t::find_interval_at_loc( return m_registers_interval_values.find(reg); } +void interval_prop_domain_t::insert_in_registers(register_t reg, location_t loc, + interval_t interval) { + m_registers_interval_values.insert(reg, loc, interval); +} + +void interval_prop_domain_t::store_in_stack(uint64_t key, mock_interval_t interval, int width) { + m_stack_slots_interval_values.store(key, interval, width); +} + bool interval_prop_domain_t::operator<=(const interval_prop_domain_t& abs) const { /* WARNING: The operation is not implemented yet.*/ return true; diff --git a/src/crab/interval_prop_domain.hpp b/src/crab/interval_prop_domain.hpp index 0ac2467df..64003cb96 100644 --- a/src/crab/interval_prop_domain.hpp +++ b/src/crab/interval_prop_domain.hpp @@ -168,6 +168,8 @@ class interval_prop_domain_t final { std::optional find_interval_value(register_t) const; std::optional find_interval_at_loc(const reg_with_loc_t reg) const; std::optional find_in_stack(uint64_t) const; + void insert_in_registers(register_t, location_t, interval_t); + void store_in_stack(uint64_t, mock_interval_t, int); void adjust_bb_for_types(location_t); std::vector get_stack_keys() const; bool all_numeric_in_stack(uint64_t, int) const; diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index bc7962700..da7b6aa8d 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -969,6 +969,14 @@ std::optional offset_domain_t::find_offset_info(register_t reg) const { return m_reg_state.find(reg); } +void offset_domain_t::insert_in_registers(register_t reg, location_t loc, dist_t dist) { + m_reg_state.insert(reg, loc, std::move(dist)); +} + +void offset_domain_t::store_in_stack(uint64_t key, dist_t d, int width) { + m_stack_state.store(key, d, width); +} + void offset_domain_t::adjust_bb_for_types(location_t loc) { m_reg_state.adjust_bb_for_registers(loc); } diff --git a/src/crab/offset_domain.hpp b/src/crab/offset_domain.hpp index 2754defb6..f5754b5aa 100644 --- a/src/crab/offset_domain.hpp +++ b/src/crab/offset_domain.hpp @@ -289,6 +289,8 @@ class offset_domain_t final { void update_offset_info(const dist_t&&, const interval_t&&, const location_t&, uint8_t, Bin::Op); dist_t update_offset(const dist_t&, const weight_t&, const interval_t&, Bin::Op); + void insert_in_registers(register_t, location_t, dist_t); + void store_in_stack(uint64_t, dist_t, int); void adjust_bb_for_types(location_t); [[nodiscard]] std::vector& get_errors() { return m_errors; } void reset_errors() { m_errors.clear(); } diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index d36bc50e1..c2f742026 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -316,6 +316,15 @@ std::optional region_domain_t::find_ptr_or_mapfd_type(register_t return m_registers.find(reg); } +void region_domain_t::insert_in_registers(register_t reg, location_t loc, + const ptr_or_mapfd_t& ptr) { + m_registers.insert(reg, loc, ptr); +} + +void region_domain_t::store_in_stack(uint64_t key, ptr_or_mapfd_t value, int width) { + m_stack.store(key, value, width); +} + bool region_domain_t::is_bottom() const { if (m_is_bottom) return true; return (m_stack.is_bottom() || m_registers.is_bottom()); diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index 2f5b369c2..a6e3f681a 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -191,6 +191,8 @@ class region_domain_t final { [[nodiscard]] std::vector get_ctx_keys() const; std::optional find_in_stack(uint64_t key) const; std::optional find_ptr_or_mapfd_at_loc(const crab::reg_with_loc_t&) const; + void insert_in_registers(register_t, location_t, const ptr_or_mapfd_t&); + void store_in_stack(uint64_t, ptr_or_mapfd_t, int); void set_aliases(int, ptr_with_off_t&); [[nodiscard]] std::vector get_stack_keys() const; void set_registers_to_top(); diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 7b061ebb9..decc43a64 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MIT #include "crab/type_domain.hpp" +#include namespace crab { @@ -95,8 +96,53 @@ crab::bound_t type_domain_t::get_loop_count_upper_bound() { return crab::bound_t{crab::number_t{0}}; } -string_invariant type_domain_t::to_set() { - return string_invariant{}; +string_invariant type_domain_t::to_set() const { + if (is_top()) return string_invariant::top(); + std::set result; + for (uint8_t i = 0; i < NUM_REGISTERS; i++) { + std::stringstream elem; + auto maybe_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(register_t{i}); + auto maybe_width_interval = m_interval.find_interval_value(register_t{i}); + auto maybe_offset = m_offset.find_offset_info(register_t{i}); + if (maybe_ptr_or_mapfd.has_value() || maybe_width_interval.has_value()) { + print_register(elem, Reg{i}, maybe_ptr_or_mapfd, maybe_offset, maybe_width_interval); + result.insert(elem.str()); + } + } + std::vector stack_keys_region = m_region.get_stack_keys(); + for (auto const& k : stack_keys_region) { + std::stringstream elem; + auto maybe_ptr_or_mapfd_cells = m_region.find_in_stack(k); + auto dist = m_offset.find_in_stack(k); + if (maybe_ptr_or_mapfd_cells.has_value()) { + auto ptr_or_mapfd_cells = maybe_ptr_or_mapfd_cells.value(); + int width = ptr_or_mapfd_cells.second; + auto ptr_or_mapfd = ptr_or_mapfd_cells.first; + elem << "stack"; + if (dist) { + print_non_numeric_memory_cell(elem, k, k+width-1, ptr_or_mapfd, + std::optional(dist->first)); + } + else { + print_non_numeric_memory_cell(elem, k, k+width-1, ptr_or_mapfd); + } + } + result.insert(elem.str()); + } + + std::vector stack_keys_interval = m_interval.get_stack_keys(); + for (auto const& k : stack_keys_interval) { + std::stringstream elem; + auto maybe_interval_cells = m_interval.find_in_stack(k); + if (maybe_interval_cells.has_value()) { + auto interval_cells = maybe_interval_cells.value(); + elem << "stack"; + print_numeric_memory_cell(elem, k, k+interval_cells.second-1, + interval_cells.first.to_interval()); + } + result.insert(elem.str()); + } + return string_invariant{result}; } void type_domain_t::operator()(const Undefined& u, location_t loc) { @@ -626,6 +672,207 @@ type_domain_t::find_interval_at_loc(const crab::reg_with_loc_t& loc) const { return m_interval.find_interval_at_loc(loc); } +static inline region_t string_to_region(const std::string& s) { + static std::map string_to_region{ + {std::string("ctx"), region_t::T_CTX}, + {std::string("stack"), region_t::T_STACK}, + {std::string("packet"), region_t::T_PACKET}, + {std::string("shared"), region_t::T_SHARED}, + }; + if (string_to_region.count(s)) { + return string_to_region[s]; + } + throw std::runtime_error(std::string("Unsupported region name: ") + s); +} + +void type_domain_t::insert_in_registers_in_interval_domain(register_t r, location_t loc, + interval_t interval) { + m_interval.insert_in_registers(r, loc, interval); +} + +void type_domain_t::store_in_stack_in_interval_domain(uint64_t key, mock_interval_t p, int width) { + m_interval.store_in_stack(key, p, width); +} + +void type_domain_t::insert_in_registers_in_offset_domain(register_t r, location_t loc, dist_t d) { + m_offset.insert_in_registers(r, loc, d); +} + +void type_domain_t::store_in_stack_in_offset_domain(uint64_t key, dist_t d, int width) { + m_offset.store_in_stack(key, d, width); +} + +void type_domain_t::insert_in_registers_in_region_domain(register_t r, location_t loc, + const ptr_or_mapfd_t& p) { + m_region.insert_in_registers(r, loc, p); +} + +void type_domain_t::store_in_stack_in_region_domain(uint64_t key, ptr_or_mapfd_t p, int width) { + m_region.store_in_stack(key, p, width); +} + +type_domain_t type_domain_t::from_predefined_types(const std::set& types, + bool setup_constraints) { + using std::regex; + using std::regex_match; + + #define NUMERIC R"_(\s*\[?([-+]?(?:\d+|oo))(?:,\s*([-+]?(?:\d+|oo))\])?\s*)_" + #define NUMERIC_ENCLOSED "<" NUMERIC ">" + #define NUMERIC_NUMERIC_ENCLOSED "<" NUMERIC ",\\s*" NUMERIC ">" + #define REG R"_(\s*r(\d\d?)\s*)_" + #define STACK_CELL R"_(\s*stack\[(\d+)-(\d+)\]\s*)_" + #define SHARED_PTR "\\s*shared_p(?:" NUMERIC_NUMERIC_ENCLOSED ")?\\s*" + #define CTX_OR_STACK_PTR "\\s*(ctx|stack)_p(?:" NUMERIC_ENCLOSED ")?\\s*" + #define PACKET_PTR "\\s*packet_p(?:<(begin|end|meta)\\+" NUMERIC ">)?\\s*" + #define NUMBER "\\s*number(?:" NUMERIC_ENCLOSED ")?\\s*" + #define MAPFD "\\s*(map_fd|map_fd_programs)" NUMERIC "\\s*" + + auto create_interval = [](std::string lb, std::string ub) { + if (lb == "" && ub == "") { + return crab::mock_interval_t::top(); + } + bound_t lb_num = bound_t::minus_infinity(); + if (lb != "-oo") { + try { + lb_num = bound_t{number_t{static_cast(std::stoll(lb))}}; + } catch (std::out_of_range& e) { + // TODO: Separate handling for such cases + lb_num = bound_t{number_t{static_cast(std::stoull(lb))}}; + } + } + auto ub_num = lb_num; + if (ub != "" && ub != "+oo") { + try { + ub_num = bound_t{number_t{static_cast(std::stoll(ub))}}; + } + catch (std::out_of_range& e) { + // TODO: Separate handling for such cases + ub_num = bound_t{number_t{static_cast(std::stoull(ub))}}; + } + } + else if (ub == "+oo") ub_num = bound_t::plus_infinity(); + return crab::mock_interval_t{lb_num, ub_num}; + }; + + auto create_ptr = [create_interval](std::string region, std::string off_lb, + std::string off_ub, std::string region_sz_lb = "", std::string region_sz_ub = "") { + auto region_type = string_to_region(region); + auto mock_offset = create_interval(off_lb, off_ub); + auto mock_region_size = create_interval(region_sz_lb, region_sz_ub); + return crab::ptr_with_off_t{region_type, -1, mock_offset, nullness_t::MAYBE_NULL, + mock_region_size}; + }; + + auto create_mapfd = [create_interval](std::string mapfd_type, std::string lb_mapfd, + std::string ub_mapfd) { + auto interval = create_interval(lb_mapfd, ub_mapfd); + if (mapfd_type == "map_fd_programs") { + return crab::mapfd_t(interval, EbpfMapValueType::PROGRAM); + } + else { + return crab::mapfd_t(interval, EbpfMapValueType::MAP); + } + }; + + auto create_pkt_offset = [create_interval](std::string offset_type, std::string offset_lb, + std::string offset_ub) { + auto offset = create_interval(offset_lb, offset_ub).to_interval(); + if (offset_type == "begin") { + return dist_t{offset}; + } + else if (offset_type == "end") { + auto packet_end = crab::interval_t{number_t{PACKET_END}}; + return dist_t{packet_end - offset}; + } + else { + auto packet_meta = crab::interval_t{number_t{PACKET_META}}; + return dist_t{packet_meta - offset}; + } + }; + + type_domain_t typ; + if (setup_constraints) { + typ = type_domain_t::setup_entry(false); + } + auto loc = location_t{std::make_pair(label_t::entry, 0)}; + for (const auto& t : types) { + std::cout << t << "\n"; + std::smatch m; + if (regex_match(t, m, regex(REG ":" CTX_OR_STACK_PTR))) { + auto reg = register_t{static_cast(std::stoul(m[1]))}; + auto ptr = create_ptr(m[2], m[3], m[4]); + typ.insert_in_registers_in_region_domain(reg, loc, ptr); + } + else if (regex_match(t, m, regex(REG ":" PACKET_PTR))) { + auto reg = register_t{static_cast(std::stoul(m[1]))}; + auto ptr = packet_ptr_t{}; + auto offset = create_pkt_offset(m[2], m[3], m[4]); + typ.insert_in_registers_in_region_domain(reg, loc, ptr); + typ.insert_in_registers_in_offset_domain(reg, loc, offset); + } + else if (regex_match(t, m, regex(REG ":" SHARED_PTR))) { + auto reg = register_t{static_cast(std::stoul(m[1]))}; + auto ptr = create_ptr("shared", m[2], m[3], m[4], m[5]); + typ.insert_in_registers_in_region_domain(reg, loc, ptr); + } + else if (regex_match(t, m, regex(REG ":" NUMBER))) { + auto reg = register_t{static_cast(std::stoul(m[1]))}; + auto num = create_interval(m[2], m[3]).to_interval(); + typ.insert_in_registers_in_interval_domain(reg, loc, num); + } + else if (regex_match(t, m, regex(REG ":" MAPFD))) { + auto reg = register_t{static_cast(std::stoul(m[1]))}; + auto mapfd = create_mapfd(m[2], m[3], m[4]); + typ.insert_in_registers_in_region_domain(reg, loc, mapfd); + } + else if (regex_match(t, m, regex(STACK_CELL ":" CTX_OR_STACK_PTR))) { + auto stack_cell_start = static_cast(std::stoul(m[1])); + auto stack_cell_end = static_cast(std::stoul(m[2])); + auto ptr = create_ptr(m[3], m[4], m[5]); + typ.store_in_stack_in_region_domain(stack_cell_start, ptr, + stack_cell_end-stack_cell_start); + } + else if (regex_match(t, m, regex(STACK_CELL ":" PACKET_PTR))) { + auto stack_cell_start = static_cast(std::stoul(m[1])); + auto stack_cell_end = std::stoi(m[2]); + auto ptr = packet_ptr_t{}; + auto pkt_offset = create_pkt_offset(m[3], m[4], m[5]); + int width = stack_cell_end - stack_cell_start; + typ.store_in_stack_in_region_domain(stack_cell_start, ptr, width); + typ.store_in_stack_in_offset_domain(stack_cell_start, pkt_offset, width); + } + else if (regex_match(t, m, regex(STACK_CELL ":" SHARED_PTR))) { + auto stack_cell_start = static_cast(std::stoul(m[1])); + auto stack_cell_end = std::stoi(m[2]); + auto ptr = create_ptr("shared", m[3], m[4], m[5], m[6]); + typ.store_in_stack_in_region_domain(stack_cell_start, ptr, + stack_cell_end-stack_cell_start); + } + else if (regex_match(t, m, regex(STACK_CELL ":" NUMBER))) { + auto stack_cell_start = static_cast(std::stoul(m[1])); + auto stack_cell_end = std::stoi(m[2]); + auto num = create_interval(m[3], m[4]); + typ.store_in_stack_in_interval_domain(stack_cell_start, num, + stack_cell_end-stack_cell_start); + } + else if (regex_match(t, m, regex(STACK_CELL ":" MAPFD))) { + auto stack_cell_start = static_cast(std::stoul(m[1])); + auto stack_cell_end = std::stoi(m[2]); + auto mapfd = create_mapfd(m[3], m[4], m[5]); + typ.store_in_stack_in_region_domain(stack_cell_start, mapfd, + stack_cell_end-stack_cell_start); + } + else { + std::cout << "type not recognized: " << t << "\n"; + } + } + return typ; +} + +void type_domain_t::write(std::ostream& os) const { + os << to_set(); +} + std::ostream& operator<<(std::ostream& o, const type_domain_t& typ) { typ.write(o); return o; diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 61e6152e4..af0ab4e2f 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -78,11 +78,11 @@ class type_domain_t final { void operator()(const FuncConstraint& s, location_t loc = boost::none) {}; void operator()(const IncrementLoopCounter&, location_t loc = boost::none); void operator()(const basic_block_t& bb, int print = 0); - void write(std::ostream& os) const {} + void write(std::ostream& os) const; friend std::ostream& operator<<(std::ostream& o, const type_domain_t& dom); void initialize_loop_counter(label_t label); crab::bound_t get_loop_count_upper_bound(); - string_invariant to_set(); + string_invariant to_set() const; void set_require_check(check_require_func_t f) {} [[nodiscard]] std::vector& get_errors() { return m_errors; } void print_ctx() const; @@ -90,6 +90,13 @@ class type_domain_t final { std::optional find_ptr_or_mapfd_at_loc(const crab::reg_with_loc_t&) const; std::optional find_offset_at_loc(const crab::reg_with_loc_t&) const; std::optional find_interval_at_loc(const crab::reg_with_loc_t&) const; + static type_domain_t from_predefined_types(const std::set&, bool); + void insert_in_registers_in_region_domain(register_t, location_t, const ptr_or_mapfd_t&); + void store_in_stack_in_region_domain(uint64_t, ptr_or_mapfd_t, int); + void insert_in_registers_in_interval_domain(register_t, location_t, interval_t); + void store_in_stack_in_interval_domain(uint64_t, mock_interval_t, int); + void insert_in_registers_in_offset_domain(register_t, location_t, dist_t); + void store_in_stack_in_offset_domain(uint64_t, dist_t, int); private: From a9a26b9698e8303bb8e06d028d43e0b1a9636ccd Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 27 Nov 2023 12:13:11 -0500 Subject: [PATCH 144/373] Added support for running tests for type domain Support for running tests for both ebpf domain and type domain inside the single framework; Some refactoring in crab_verifier; Some fixes added in each sub-domain for the tests; Signed-off-by: Ameer Hamza --- src/crab/interval_prop_domain.cpp | 1 + src/crab/offset_domain.cpp | 1 + src/crab/region_domain.cpp | 1 + src/crab/type_domain.cpp | 4 ++- src/crab_verifier.cpp | 18 ++++++------- src/crab_verifier.hpp | 3 ++- src/ebpf_yaml.cpp | 9 ++++--- src/ebpf_yaml.hpp | 3 ++- src/main/run_yaml.cpp | 5 +++- src/test/test_yaml.cpp | 44 +++++++++++++++---------------- 10 files changed, 51 insertions(+), 38 deletions(-) diff --git a/src/crab/interval_prop_domain.cpp b/src/crab/interval_prop_domain.cpp index 339ad6b1f..67c9a4d59 100644 --- a/src/crab/interval_prop_domain.cpp +++ b/src/crab/interval_prop_domain.cpp @@ -20,6 +20,7 @@ bool registers_cp_state_t::is_top() const { } void registers_cp_state_t::set_to_top() { + m_interval_env = std::make_shared(); m_cur_def = live_registers_t{nullptr}; m_is_bottom = false; } diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index da7b6aa8d..eabe3b537 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -164,6 +164,7 @@ std::vector stack_state_t::find_overlapping_cells(uint64_t start, int } void registers_state_t::set_to_top() { + m_offset_env = std::make_shared(); m_cur_def = live_registers_t{nullptr}; m_is_bottom = false; } diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index c2f742026..5fe19a038 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -151,6 +151,7 @@ void register_types_t::set_to_bottom() { } void register_types_t::set_to_top() { + m_region_env = std::make_shared(); m_cur_def = live_registers_t{nullptr}; m_is_bottom = false; } diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index decc43a64..9f31c9adb 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -794,9 +794,11 @@ type_domain_t type_domain_t::from_predefined_types(const std::set& if (setup_constraints) { typ = type_domain_t::setup_entry(false); } + else { + typ.set_to_top(); + } auto loc = location_t{std::make_pair(label_t::entry, 0)}; for (const auto& t : types) { - std::cout << t << "\n"; std::smatch m; if (regex_match(t, m, regex(REG ":" CTX_OR_STACK_PTR))) { auto reg = register_t{static_cast(std::stoul(m[1]))}; diff --git a/src/crab_verifier.cpp b/src/crab_verifier.cpp index 5b6297867..4d1cf0038 100644 --- a/src/crab_verifier.cpp +++ b/src/crab_verifier.cpp @@ -15,9 +15,6 @@ #include "crab/abstract_domain.hpp" #include "crab/ebpf_domain.hpp" #include "crab/type_domain.hpp" -#include "crab/interval_prop_domain.hpp" -#include "crab/region_domain.hpp" -#include "crab/offset_domain.hpp" #include "crab/fwd_analyzer.hpp" #include "crab_utils/lazy_allocator.hpp" @@ -180,7 +177,10 @@ static abstract_domain_t make_initial(abstract_domain_kind abstract_domain, cons return abstract_domain_t(entry_inv); } case abstract_domain_kind::TYPE_DOMAIN: { - // TODO + type_domain_t entry_inv = entry_invariant.is_bottom() + ? type_domain_t::from_predefined_types({"false"}, setup_constraints) + : type_domain_t::from_predefined_types(entry_invariant.value(), setup_constraints); + return abstract_domain_t(entry_inv); } default: // FIXME: supported abstract domains should be checked in check.cpp @@ -238,17 +238,17 @@ static string_invariant_map to_string_invariant_map(crab::invariant_table_t& inv return res; } -std::tuple ebpf_analyze_program_for_test(std::ostream& os, const InstructionSeq& prog, - const string_invariant& entry_invariant, - const program_info& info, - const ebpf_verifier_options_t& options) { +std::tuple +ebpf_analyze_program_for_test(abstract_domain_kind domain, std::ostream& os, const InstructionSeq& prog, + const string_invariant& entry_invariant, const program_info& info, + const ebpf_verifier_options_t& options) { crab::domains::clear_global_state(); crab::variable_t::clear_thread_local_state(); thread_local_options = options; global_program_info = info; assert(!entry_invariant.is_bottom()); - abstract_domain_t entry_inv = make_initial(abstract_domain_kind::EBPF_DOMAIN, entry_invariant, options.setup_constraints); + abstract_domain_t entry_inv = make_initial(domain, entry_invariant, options.setup_constraints); if (entry_inv.is_bottom()) throw std::runtime_error("Entry invariant is inconsistent"); cfg_t cfg = prepare_cfg(prog, info, !options.no_simplify, false); diff --git a/src/crab_verifier.hpp b/src/crab_verifier.hpp index 1e64f8e79..3e34bd794 100644 --- a/src/crab_verifier.hpp +++ b/src/crab_verifier.hpp @@ -72,7 +72,8 @@ crab_results ebpf_verify_program( using string_invariant_map = std::map; -std::tuple ebpf_analyze_program_for_test(std::ostream& os, const InstructionSeq& prog, +std::tuple ebpf_analyze_program_for_test(abstract_domain_kind domain, + std::ostream& os, const InstructionSeq& prog, const string_invariant& entry_invariant, const program_info& info, const ebpf_verifier_options_t& options); diff --git a/src/ebpf_yaml.cpp b/src/ebpf_yaml.cpp index dee8d9e3b..9150721b5 100644 --- a/src/ebpf_yaml.cpp +++ b/src/ebpf_yaml.cpp @@ -232,7 +232,8 @@ static Diff make_diff(const T& actual, const T& expected) { }; } -std::optional run_yaml_test_case(TestCase test_case, bool debug) { +std::optional run_yaml_test_case(abstract_domain_kind domain, TestCase test_case, + bool debug) { if (debug) { test_case.options.print_failures = true; test_case.options.print_invariants = true; @@ -246,6 +247,7 @@ std::optional run_yaml_test_case(TestCase test_case, bool debug) { std::ostringstream ss; const auto& [actual_last_invariant, result] = ebpf_analyze_program_for_test( + domain, ss, test_case.instruction_seq, test_case.assumed_pre_invariant, info, test_case.options); @@ -351,7 +353,8 @@ ConformanceTestResult run_conformance_test_case(const std::vector& memo try { std::ostringstream null_stream; const auto& [actual_last_invariant, result] = - ebpf_analyze_program_for_test(null_stream, prog, pre_invariant, info, options); + ebpf_analyze_program_for_test(abstract_domain_kind::EBPF_DOMAIN, null_stream, prog, + pre_invariant, info, options); for (const std::string& invariant : actual_last_invariant.value()) { if (invariant.rfind("r0.svalue=", 0) == 0) { @@ -407,7 +410,7 @@ void print_failure(const Failure& failure, std::ostream& out) { bool all_suites(const string& path) { bool result = true; for (const TestCase& test_case: read_suite(path)) { - result = result && bool(run_yaml_test_case(test_case)); + result = result && bool(run_yaml_test_case(abstract_domain_kind::EBPF_DOMAIN, test_case)); } return result; } diff --git a/src/ebpf_yaml.hpp b/src/ebpf_yaml.hpp index 183ea3daa..f09194084 100644 --- a/src/ebpf_yaml.hpp +++ b/src/ebpf_yaml.hpp @@ -32,7 +32,8 @@ struct Failure { void print_failure(const Failure& failure, std::ostream& out); -std::optional run_yaml_test_case(TestCase test_case, bool debug = false); +std::optional run_yaml_test_case(abstract_domain_kind, TestCase test_case, + bool debug = false); struct ConformanceTestResult { bool success{}; diff --git a/src/main/run_yaml.cpp b/src/main/run_yaml.cpp index 65a839a57..9ec50794f 100644 --- a/src/main/run_yaml.cpp +++ b/src/main/run_yaml.cpp @@ -24,12 +24,15 @@ int main(int argc, char** argv) { CLI11_PARSE(app, argc, argv); bool res = true; + bool tests_for_type_d = filename.find("types") != filename.npos; + auto domain = tests_for_type_d ? abstract_domain_kind::TYPE_DOMAIN : + abstract_domain_kind::EBPF_DOMAIN; foreach_suite(filename, [&](const TestCase& test_case) { if (!pattern.empty() && test_case.name.find(pattern) == test_case.name.npos) { return; } std::cout << test_case.name << ": " << std::flush; - const auto& maybe_failure = run_yaml_test_case(test_case, verbose); + const auto& maybe_failure = run_yaml_test_case(domain, test_case, verbose); if (!quiet && (verbose || maybe_failure)) { std::cout << "\n"; std::cout << "Pre-invariant:" << test_case.assumed_pre_invariant << "\n"; diff --git a/src/test/test_yaml.cpp b/src/test/test_yaml.cpp index da124a8bf..5d5a2518d 100644 --- a/src/test/test_yaml.cpp +++ b/src/test/test_yaml.cpp @@ -7,10 +7,10 @@ // TODO: move out of this framework -#define YAML_CASE(path) \ +#define YAML_CASE(path, domain) \ TEST_CASE("YAML suite: " path, "[yaml]") { \ foreach_suite(path, [&](const TestCase& test_case){ \ - std::optional failure = run_yaml_test_case(test_case); \ + std::optional failure = run_yaml_test_case(domain, test_case); \ if (failure) { \ std::cout << "test case: " << test_case.name << "\n"; \ print_failure(*failure, std::cout); \ @@ -19,23 +19,23 @@ }); \ } -YAML_CASE("test-data/add.yaml") -YAML_CASE("test-data/assign.yaml") -YAML_CASE("test-data/atomic.yaml") -YAML_CASE("test-data/bitop.yaml") -YAML_CASE("test-data/call.yaml") -YAML_CASE("test-data/callx.yaml") -YAML_CASE("test-data/udivmod.yaml") -YAML_CASE("test-data/sdivmod.yaml") -YAML_CASE("test-data/full64.yaml") -YAML_CASE("test-data/jump.yaml") -YAML_CASE("test-data/loop.yaml") -YAML_CASE("test-data/movsx.yaml") -YAML_CASE("test-data/packet.yaml") -YAML_CASE("test-data/parse.yaml") -YAML_CASE("test-data/sext.yaml") -YAML_CASE("test-data/shift.yaml") -YAML_CASE("test-data/stack.yaml") -YAML_CASE("test-data/subtract.yaml") -YAML_CASE("test-data/unop.yaml") -YAML_CASE("test-data/unsigned.yaml") +YAML_CASE("test-data/add.yaml", abstract_domain_kind::EBPF_DOMAIN) +YAML_CASE("test-data/assign.yaml", abstract_domain_kind::EBPF_DOMAIN) +YAML_CASE("test-data/atomic.yaml", abstract_domain_kind::EBPF_DOMAIN) +YAML_CASE("test-data/bitop.yaml", abstract_domain_kind::EBPF_DOMAIN) +YAML_CASE("test-data/call.yaml", abstract_domain_kind::EBPF_DOMAIN) +YAML_CASE("test-data/callx.yaml", abstract_domain_kind::EBPF_DOMAIN) +YAML_CASE("test-data/udivmod.yaml", abstract_domain_kind::EBPF_DOMAIN) +YAML_CASE("test-data/sdivmod.yaml", abstract_domain_kind::EBPF_DOMAIN) +YAML_CASE("test-data/full64.yaml", abstract_domain_kind::EBPF_DOMAIN) +YAML_CASE("test-data/jump.yaml", abstract_domain_kind::EBPF_DOMAIN) +YAML_CASE("test-data/loop.yaml", abstract_domain_kind::EBPF_DOMAIN) +YAML_CASE("test-data/movsx.yaml", abstract_domain_kind::EBPF_DOMAIN) +YAML_CASE("test-data/packet.yaml", abstract_domain_kind::EBPF_DOMAIN) +YAML_CASE("test-data/parse.yaml", abstract_domain_kind::EBPF_DOMAIN) +YAML_CASE("test-data/sext.yaml", abstract_domain_kind::EBPF_DOMAIN) +YAML_CASE("test-data/shift.yaml", abstract_domain_kind::EBPF_DOMAIN) +YAML_CASE("test-data/stack.yaml", abstract_domain_kind::EBPF_DOMAIN) +YAML_CASE("test-data/subtract.yaml", abstract_domain_kind::EBPF_DOMAIN) +YAML_CASE("test-data/unop.yaml", abstract_domain_kind::EBPF_DOMAIN) +YAML_CASE("test-data/unsigned.yaml", abstract_domain_kind::EBPF_DOMAIN) From 78c536bf743302286f6e5fc7a477921d32fdc841 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 27 Nov 2023 12:23:23 -0500 Subject: [PATCH 145/373] Added some test cases for type domain Signed-off-by: Ameer Hamza --- test-data/add_types.yaml | 120 ++++++++++++++++++++++++++++ test-data/assign_types.yaml | 155 ++++++++++++++++++++++++++++++++++++ test-data/bitop_types.yaml | 94 ++++++++++++++++++++++ test-data/call_types.yaml | 44 ++++++++++ 4 files changed, 413 insertions(+) create mode 100644 test-data/add_types.yaml create mode 100644 test-data/assign_types.yaml create mode 100644 test-data/bitop_types.yaml create mode 100644 test-data/call_types.yaml diff --git a/test-data/add_types.yaml b/test-data/add_types.yaml new file mode 100644 index 000000000..fcc9c4de8 --- /dev/null +++ b/test-data/add_types.yaml @@ -0,0 +1,120 @@ +# Copyright (c) Prevail Verifier contributors. +# SPDX-License-Identifier: MIT +--- +test-case: add immediate to large singleton number + +pre: ["r0 : number<2147483647>"] + +code: + : | + r0 += 2 ; make sure value does not become a negative 64 bit number + +post: ["r0 : number<2147483649>"] + +--- + +test-case: add immediate to large number range + +pre: ["r0 : number<[2147483645, 2147483647]>"] + +code: + : | + r0 += 4 ; make sure value does not become a negative 64 bit number + +post: ["r0 : number<[2147483649, 2147483651]>"] + +--- + +test-case: add immediate to unknown number + +pre: ["r1 : number"] + +code: + : | + r1 += 0 + +post: ["r1 : number"] + +--- + +test-case: add number register to unknown number + +pre: ["r1 : number", "r2 : number"] + +code: + : | + r1 += r2 + +post: ["r1 : number", "r2 : number"] + +--- + +test-case: add immediate to singleton number + +pre: ["r1 : number<5>"] + +code: + : | + r1 += 3 + +post: ["r1 : number<8>"] + +--- + +test-case: add constant register to singleton number + +pre: ["r1 : number<5>", "r2 : number<7>"] + +code: + : | + r1 += r2 + +post: ["r1 : number<12>", "r2 : number<7>"] + +--- + +test-case: add immediate to finite interval number + +pre: ["r1 : number<[5, 10]>"] + +code: + : | + r1 += 5 + +post: ["r1 : number<[10, 15]>"] + +--- + +test-case: add constant register number to finite interval number + +pre: ["r1 : number<[5, 10]>", "r2 : number<5>"] + +code: + : | + r1 += r2 + +post: ["r1 : number<[10, 15]>", "r2 : number<5>"] + +--- + +test-case: add interval number register to constant register pointer + +pre: ["r2 : packet_p", "r7 : number<[3, 5]>"] + +code: + : | + r2 += r7 + +post: ["r2 : packet_p", "r7 : number<[3, 5]>"] + +--- + +test-case: add constant register pointer to interval number + +pre: ["r2 : packet_p", "r7 : number<[3, 5]>"] + +code: + : | + r7 += r2 + +post: ["r2 : packet_p", "r7 : packet_p"] diff --git a/test-data/assign_types.yaml b/test-data/assign_types.yaml new file mode 100644 index 000000000..209fb3c30 --- /dev/null +++ b/test-data/assign_types.yaml @@ -0,0 +1,155 @@ +# Copyright (c) Prevail Verifier contributors. +# SPDX-License-Identifier: MIT +--- +test-case: assign immediate + +pre: [] + +code: + : | + r1 = 0 + +post: ["r1 : number<0>"] + +--- + +test-case: assign register + +pre: [] + +code: + : | + r1 = r2; + +post: [] + +--- + +test-case: re-assign immediate + +pre: ["r1 : number<5>"] + +code: + : | + r1 = 0 + +post: ["r1 : number<0>"] + +--- + +test-case: re-assign register + +pre: ["r1 : number<[-3, 5]>"] + +code: + : | + r1 = 0 + +post: ["r1 : number<0>"] + +--- + +test-case: stack assign immediate + +pre: ["r10 : stack_p<512>"] + +code: + : | + *(u64 *)(r10 - 8) = 0 + +post: ["r10 : stack_p<512>", "stack[504-511] : number<0>"] + +--- +test-case: stack assign number register + +pre: ["r10 : stack_p<512>", "r1 : number<0>"] + +code: + : | + *(u64 *)(r10 - 8) = r1 + +post: ["r10 : stack_p<512>", "r1 : number<0>", "stack[504-511] : number<0>"] + +--- + +test-case: stack assign packet register + +pre: ["r10 : stack_p<512>", "r1 : packet_p"] + +code: + : | + *(u64 *)(r10 - 8) = r1 + +post: ["r10 : stack_p<512>", "r1 : packet_p", "stack[504-511] : packet_p"] + +--- + +test-case: stack extend numeric range + +pre: ["r10 : stack_p<512>", "stack[500-507] : number"] + +code: + : | + *(u64 *)(r10 - 8) = 0 + +post: ["r10 : stack_p<512>", "stack[500-503] : number", "stack[504-511] : number<0>"] + +--- + +test-case: stack narrow numeric range + +pre: ["r10 : stack_p<512>", "r1 : packet_p", "stack[500-507] : number"] + +code: + : | + *(u64 *)(r10 - 8) = r1 + +post: ["r10 : stack_p<512>", "r1 : packet_p", "stack[500-503] : number", "stack[504-511] : packet_p"] + +--- + +test-case: assign register number value + +pre: ["r1 : number<0>"] + +code: + : | + r2 = r1 + +post: ["r1 : number<0>", "r2 : number<0>"] + +--- + +test-case: assign register stack value + +pre: ["r1 : stack_p<0>"] + +code: + : | + r2 = r1 + +post: ["r1 : stack_p<0>", "r2 : stack_p<0>"] + +--- + +test-case: assign register shared value + +pre: ["r1 : shared_p<0,16>"] + +code: + : | + r2 = r1 + +post: ["r1 : shared_p<0,16>", "r2 : shared_p<0,16>"] + +--- + +test-case: 32-bit indirect assignment from context + +pre: ["r1 : ctx_p<0>"] + +code: + : | + r2 = *(u32 *)(r1 + 8) + +post: ["r1 : ctx_p<0>", "r2 : packet_p"] diff --git a/test-data/bitop_types.yaml b/test-data/bitop_types.yaml new file mode 100644 index 000000000..8821bf2d1 --- /dev/null +++ b/test-data/bitop_types.yaml @@ -0,0 +1,94 @@ +# Copyright (c) Prevail Verifier contributors. +# SPDX-License-Identifier: MIT +--- +test-case: Bitwise OR imm + +pre: ["r1 : number<-9223372036854775808>"] + +code: + : | + r1 |= 1 ; 0x8000000000000000 | 1 + +post: ["r1 : number<-9223372036854775807>"] + +--- + +test-case: Bitwise OR reg + +pre: ["r1 : number<-9223372036854775808>", "r2 : number<1>"] + +code: + : | + r1 |= r2 ; 0x8000000000000000 | 1 + +post: ["r1 : number<-9223372036854775807>", "r2 : number<1>"] + +--- + +test-case: Bitwise XOR imm + +pre: ["r1 : number<-9223372036854775807>"] + +code: + : | + r1 ^= 3 ; 0x8000000000000001 ^ 3 + +post: ["r1 : number<-9223372036854775806>"] + +--- + +test-case: Bitwise XOR reg + +pre: ["r1 : number<-9223372036854775807>", "r2 : number<3>"] + +code: + : | + r1 ^= r2 ; 0x8000000000000001 ^ 3 + +post: ["r1 : number<-9223372036854775806>", "r2 : number<3>"] + +--- + +test-case: Bitwise AND imm + +pre: ["r1 : number<-9223372036854775805>"] + +code: + : | + r1 &= 5 ; 0x8000000000000003 & 5 + +post: ["r1 : number<1>"] + +--- + +test-case: Bitwise AND reg + +pre: ["r1 : number<-9223372036854775805>", "r2 : number<-9223372036854775803>"] + +code: + : | + r1 &= r2 ; 0x8000000000000003 & 0x8000000000000005 = 0x8000000000000001 + +post: ["r1 : number<-9223372036854775807>", "r2 : number<-9223372036854775803>"] + +--- + +test-case: Bitwise [-1, 1] AND 0 == 0 + +pre: ["r1 : number<[-1,1]>"] + +code: + : | + r1 &= 0 + +post: ["r1 : number<0>"] +--- +test-case: Bitwise 2 AND 1 == 0 + +pre: ["r1 : number<2>"] + +code: + : | + r1 &= 1 + +post: ["r1 : number<0>"] diff --git a/test-data/call_types.yaml b/test-data/call_types.yaml new file mode 100644 index 000000000..56c57cc8e --- /dev/null +++ b/test-data/call_types.yaml @@ -0,0 +1,44 @@ +# Copyright (c) Prevail Verifier contributors. +# SPDX-License-Identifier: MIT +--- +test-case: bpf_map_lookup_elem + +pre: ["r1 : map_fd 1", "r2 : stack_p<504>", "stack[504-511] : number"] + +code: + : | + call 1; bpf_map_lookup_elem + +post: ["r0 : shared_p<0,4>", "stack[504-511] : number"] + +--- + +test-case: bpf_map_lookup_elem with non-numeric stack key + +pre: ["r1 : map_fd 1", "r2 : stack_p"] + +code: + : | + call 1; bpf_map_lookup_elem + +post: ["r0 : shared_p<0,4>"] + +messages: + - "0: Illegal map update with a non-numerical value [-oo-oo) (within stack(r2:key_size(r1)))" + +--- + +test-case: bpf_map_update_elem with non-numeric stack value + +pre: ["r1 : map_fd 1", "r2 : stack_p<504>", "r3 : stack_p<496>", "r4 : number", + "stack[504-511] : number"] + +code: + : | + call 2; bpf_map_update_elem + +post: ["r0 : number", "stack[504-511] : number"] + +messages: + - "0: Illegal map update with a non-numerical value [496-500) (within stack(r3:value_size(r1)))" + From 0830919ad616042b0e74c26325e8c315f42cb024 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 27 Nov 2023 15:25:26 -0500 Subject: [PATCH 146/373] Correctly computing limits for packet region Signed-off-by: Ameer Hamza --- src/crab/offset_domain.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index eabe3b537..9a03bfd1e 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -405,7 +405,7 @@ std::optional packet_constraint_t::get_limit() const { // TODO: normalize constraint, if required auto dist = m_eq.m_lhs.m_dist; if (dist.is_top()) return {}; - return dist.ub(); + return dist.lb(); } extra_constraints_t extra_constraints_t::operator|(const extra_constraints_t& other) const { @@ -581,9 +581,12 @@ void offset_domain_t::operator()(const Assume &b, location_t loc) { return; } dist_t left_reg_dist = dist_left.value(); + // keep only the upper bound to generate the constraint + auto ub = left_reg_dist.m_dist.ub(); + auto left = weight_t{ub}; dist_t right_reg_dist = dist_right.value(); slack_var_t s = m_slack++; - dist_t f = dist_t(left_reg_dist.m_dist, s); + dist_t f = dist_t(left, s); dist_t b = dist_t(right_reg_dist.m_dist); auto eq = equality_t(f, b); auto ineq = inequality_t(s, rop_t::R_GE, weight_t{number_t{0}}); From bef2f5533433dc89d9f2798214e095864740079f Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 26 Dec 2023 06:01:36 -0500 Subject: [PATCH 147/373] Split interval domain into signed and unsigned Previously, the interval propagation domain handled the intervals in a similar manner for both signed and unsigned operations, with some specific cases handled for unsigned separately, which is unsound; Currently, signed interval domain handles signed operations, and unsigned intervals handles unsigned, while the interval domain manages both using 'reduction'; Some cleanup/refactoring, and better handling of dependencies; Signed-off-by: Ameer Hamza --- src/crab/abstract_domain.cpp | 8 +- src/crab/common.hpp | 3 + src/crab/interval_domain.cpp | 1621 +++++++++++++++++ src/crab/interval_domain.hpp | 139 ++ src/crab/interval_prop_domain.cpp | 1171 ------------ src/crab/offset_domain.cpp | 233 +-- src/crab/offset_domain.hpp | 12 +- src/crab/region_domain.cpp | 213 ++- src/crab/region_domain.hpp | 10 +- src/crab/signed_interval_domain.cpp | 588 ++++++ ..._domain.hpp => signed_interval_domain.hpp} | 114 +- src/crab/type_domain.cpp | 149 +- src/crab/type_domain.hpp | 17 +- src/crab/type_ostream.hpp | 3 - src/crab/unsigned_interval_domain.cpp | 507 ++++++ src/crab/unsigned_interval_domain.hpp | 143 ++ 16 files changed, 3397 insertions(+), 1534 deletions(-) create mode 100644 src/crab/interval_domain.cpp create mode 100644 src/crab/interval_domain.hpp delete mode 100644 src/crab/interval_prop_domain.cpp create mode 100644 src/crab/signed_interval_domain.cpp rename src/crab/{interval_prop_domain.hpp => signed_interval_domain.hpp} (50%) create mode 100644 src/crab/unsigned_interval_domain.cpp create mode 100644 src/crab/unsigned_interval_domain.hpp diff --git a/src/crab/abstract_domain.cpp b/src/crab/abstract_domain.cpp index e6a865122..62e55cb74 100644 --- a/src/crab/abstract_domain.cpp +++ b/src/crab/abstract_domain.cpp @@ -5,7 +5,9 @@ #include "ebpf_domain.hpp" #include "type_domain.hpp" #include "region_domain.hpp" -#include "interval_prop_domain.hpp" +#include "interval_domain.hpp" +#include "signed_interval_domain.hpp" +#include "unsigned_interval_domain.hpp" #include "offset_domain.hpp" template @@ -300,4 +302,6 @@ template abstract_domain_t::abstract_domain_t(crab::ebpf_domain_t); template abstract_domain_t::abstract_domain_t(crab::type_domain_t); template abstract_domain_t::abstract_domain_t(crab::region_domain_t); template abstract_domain_t::abstract_domain_t(crab::offset_domain_t); -template abstract_domain_t::abstract_domain_t(crab::interval_prop_domain_t); +template abstract_domain_t::abstract_domain_t(crab::interval_domain_t); +template abstract_domain_t::abstract_domain_t(crab::signed_interval_domain_t); +template abstract_domain_t::abstract_domain_t(crab::unsigned_interval_domain_t); diff --git a/src/crab/common.hpp b/src/crab/common.hpp index 0208bcb2e..1dfbf1148 100644 --- a/src/crab/common.hpp +++ b/src/crab/common.hpp @@ -7,12 +7,15 @@ #include #include #include +#include #include "string_constraints.hpp" #include "asm_syntax.hpp" +#include "array_domain.hpp" namespace crab { +using check_require_func_t = std::function; constexpr uint8_t NUM_REGISTERS = 11; constexpr int STACK_BEGIN = 0; diff --git a/src/crab/interval_domain.cpp b/src/crab/interval_domain.cpp new file mode 100644 index 000000000..8805fcd43 --- /dev/null +++ b/src/crab/interval_domain.cpp @@ -0,0 +1,1621 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT + +#include "crab/interval_domain.hpp" +#include "boost/endian/conversion.hpp" + +namespace crab { + +bool interval_domain_t::is_bottom() const { + if (m_is_bottom) return true; + return (m_signed.is_bottom() || m_unsigned.is_bottom()); +} + +bool interval_domain_t::is_top() const { + if (m_is_bottom) return false; + return (m_signed.is_top() && m_unsigned.is_top()); +} + +interval_domain_t interval_domain_t::bottom() { + interval_domain_t interval; + interval.set_to_bottom(); + return interval; +} + +void interval_domain_t::set_to_bottom() { + m_is_bottom = true; + m_signed.set_to_bottom(); + m_unsigned.set_to_bottom(); +} + +void interval_domain_t::set_to_top() { + m_is_bottom = false; + m_signed.set_to_top(); + m_unsigned.set_to_top(); +} + +void interval_domain_t::set_registers_to_bottom() { + m_signed.set_registers_to_bottom(); + m_unsigned.set_registers_to_bottom(); +} + +void interval_domain_t::set_registers_to_top() { + m_signed.set_registers_to_top(); + m_unsigned.set_registers_to_top(); +} + +std::optional interval_domain_t::find_in_stack_signed(uint64_t key) const { + return m_signed.find_in_stack(key); +} + +std::optional interval_domain_t::find_in_stack_unsigned(uint64_t key) const { + return m_unsigned.find_in_stack(key); +} + +void interval_domain_t::adjust_bb_for_types(location_t loc) { + m_signed.adjust_bb_for_types(loc); + m_unsigned.adjust_bb_for_types(loc); +} + +std::vector interval_domain_t::get_stack_keys() const { + // likewise for both domains, so just use signed + return m_signed.get_stack_keys(); +} + +bool interval_domain_t::all_numeric_in_stack(uint64_t start_loc, int width) const { + // likewise for both domains, so just use signed + return m_signed.all_numeric_in_stack(start_loc, width); +} + +std::vector interval_domain_t::find_overlapping_cells_in_stack(uint64_t start_loc, + int width) const { + // likewise for both domains, so just use signed + return m_signed.find_overlapping_cells_in_stack(start_loc, width); +} + +void interval_domain_t::remove_overlap_in_stack(const std::vector& overlap, + uint64_t start_loc, int width) { + m_signed.remove_overlap_in_stack(overlap, start_loc, width); + m_unsigned.remove_overlap_in_stack(overlap, start_loc, width); +} + +void interval_domain_t::fill_values_in_stack(const std::vector& overlap, + uint64_t start_loc, int width) { + m_signed.fill_values_in_stack(overlap, start_loc, width); + m_unsigned.fill_values_in_stack(overlap, start_loc, width); +} + +std::optional interval_domain_t::find_interval_value(register_t reg) const { + // in this case, it does not matter which domain we look into, hence we look into the signed one + return m_signed.find_interval_value(reg); +} + +std::optional interval_domain_t::find_signed_interval_value(register_t reg) const { + return m_signed.find_interval_value(reg); +} + +std::optional interval_domain_t::find_unsigned_interval_value(register_t reg) const { + return m_unsigned.find_interval_value(reg); +} + +std::optional interval_domain_t::find_signed_interval_at_loc( + const reg_with_loc_t reg) const { + return m_signed.find_interval_at_loc(reg); +} + +std::optional interval_domain_t::find_unsigned_interval_at_loc( + const reg_with_loc_t reg) const { + return m_unsigned.find_interval_at_loc(reg); +} + +void interval_domain_t::insert_in_registers(register_t reg, location_t loc, interval_t interval) { + insert_in_registers_unsigned(reg, loc, interval); + insert_in_registers_signed(reg, loc, interval); +} + +void interval_domain_t::insert_in_registers_signed(register_t reg, location_t loc, + interval_t interval) { + m_signed.insert_in_registers(reg, loc, interval); +} + +void interval_domain_t::insert_in_registers_unsigned(register_t reg, location_t loc, + interval_t interval) { + m_unsigned.insert_in_registers(reg, loc, interval); +} + +void interval_domain_t::store_in_stack(uint64_t key, mock_interval_t interval, int width) { + store_in_stack_signed(key, interval, width); + store_in_stack_unsigned(key, interval, width); +} + +void interval_domain_t::store_in_stack_signed(uint64_t key, mock_interval_t interval, int width) { + m_signed.store_in_stack(key, interval, width); +} + +void interval_domain_t::store_in_stack_unsigned(uint64_t key, mock_interval_t interval, int width) { + m_unsigned.store_in_stack(key, interval, width); +} + +bool interval_domain_t::operator<=(const interval_domain_t& abs) const { + /* WARNING: The operation is not implemented yet.*/ + return true; +} + +void interval_domain_t::operator|=(const interval_domain_t& abs) { + interval_domain_t tmp{abs}; + operator|=(std::move(tmp)); +} + +void interval_domain_t::operator|=(interval_domain_t&& abs) { + if (is_bottom()) { + *this = abs; + return; + } + *this = *this | std::move(abs); +} + +interval_domain_t interval_domain_t::operator|(const interval_domain_t& other) const { + if (is_bottom() || other.is_top()) { + return other; + } + else if (other.is_bottom() || is_top()) { + return *this; + } + return interval_domain_t(m_signed | other.m_signed, m_unsigned | other.m_unsigned); +} + +interval_domain_t interval_domain_t::operator|(interval_domain_t&& other) const { + if (is_bottom() || other.is_top()) { + return std::move(other); + } + else if (other.is_bottom() || is_top()) { + return *this; + } + return interval_domain_t( + m_signed | std::move(other.m_signed), m_unsigned | std::move(other.m_unsigned)); +} + +interval_domain_t interval_domain_t::operator&(const interval_domain_t& abs) const { + /* WARNING: The operation is not implemented yet.*/ + return abs; +} + +interval_domain_t interval_domain_t::widen(const interval_domain_t& abs, bool to_constants) { + /* WARNING: The operation is not implemented yet.*/ + return abs; +} + +interval_domain_t interval_domain_t::narrow(const interval_domain_t& other) const { + /* WARNING: The operation is not implemented yet.*/ + return other; +} + +crab::bound_t interval_domain_t::get_loop_count_upper_bound() { + /* WARNING: The operation is not implemented yet.*/ + return crab::bound_t{crab::number_t{0}}; +} + +void interval_domain_t::initialize_loop_counter(const label_t& label) { + /* WARNING: The operation is not implemented yet.*/ +} + +string_invariant interval_domain_t::to_set() { + return string_invariant{}; +} + +interval_domain_t interval_domain_t::setup_entry() { + auto&& _signed = signed_interval_domain_t::setup_entry(); + auto&& _unsigned = unsigned_interval_domain_t::setup_entry(); + interval_domain_t interval(std::move(_signed), std::move(_unsigned)); + return interval; +} + +void interval_domain_t::operator()(const Un& u, location_t loc) { + m_signed(u, loc); + m_unsigned(u, loc); +} + +void interval_domain_t::operator()(const LoadMapFd& u, location_t loc) { + operator-=(register_t{u.dst.v}); +} + +void interval_domain_t::scratch_caller_saved_registers() { + for (uint8_t i = R1_ARG; i <= R5_ARG; i++) { + operator-=(register_t{i}); + } +} + +void interval_domain_t::do_call(const Call& u, const stack_cells_t& store_in_stack, + location_t loc) { + for (const auto& kv : store_in_stack) { + auto offset = kv.first; + auto width = kv.second; + auto overlapping_cells = find_overlapping_cells_in_stack(offset, width); + if (overlapping_cells.empty()) { + m_signed.store_in_stack(offset, interval_t::top(), width); + m_unsigned.store_in_stack(offset, interval_t::top(), width); + } + else { + fill_values_in_stack(overlapping_cells, offset, width); + } + //remove_overlap_in_stack(overlapping_cells, offset, width); + //m_signed.store_in_stack(offset, interval_t::top(), width); + //m_unsigned.store_in_stack(offset, interval_t::top(), width); + } + auto r0 = register_t{R0_RETURN_VALUE}; + if (u.is_map_lookup) { + operator-=(r0); + } + else { + insert_in_registers(r0, loc, interval_t::top()); + } + scratch_caller_saved_registers(); +} + +void interval_domain_t::operator()(const Packet& u, location_t loc) { + auto r0 = register_t{R0_RETURN_VALUE}; + insert_in_registers(r0, loc, interval_t::top()); + scratch_caller_saved_registers(); +} + +// Given left and right values, get the left and right intervals +static void get_unsigned_intervals(bool is64, const interval_t& dst_signed, + const interval_t& dst_unsigned, const interval_t& src_unsigned, + interval_t& left_interval, interval_t& right_interval) { + + // Get intervals as 32-bit or 64-bit as appropriate. + left_interval = dst_unsigned; + right_interval = src_unsigned; + if (!is64) { + for (interval_t* interval : {&left_interval, &right_interval}) { + if (!(*interval <= interval_t::unsigned_int(false))) { + *interval = interval->truncate_to_uint(false); + } + } + } + + if (left_interval.is_top()) { + left_interval = dst_signed; + if (left_interval.is_top()) { + left_interval = interval_t::unsigned_int(is64); + } + else { + // make left interval as union of two intervals: + // [0, left_interval.ub()] truncated to uint + // [left_interval.lb(), -1] truncated to uint, as negative_int <=> unsigned_high + left_interval = interval_t{number_t{0}, left_interval.ub()}.truncate_to_uint(true) | + interval_t{left_interval.lb(), number_t{-1}}.truncate_to_uint(true); + } + } + + for (interval_t* interval : {&left_interval, &right_interval}) { + if (!(*interval <= interval_t::unsigned_int(true))) { + *interval = interval->truncate_to_uint(true); + } + } +} + +void interval_domain_t::assume_unsigned_lt(bool is64, bool strict, + interval_t&& left_interval, interval_t&& right_interval, + const interval_t& left_signed, const interval_t& left_unsigned, + const interval_t& right_signed, const interval_t& right_unsigned, + register_t left, Value right, location_t loc) { + + auto positive = interval_t{number_t{0}, bound_t::plus_infinity()}; + if (right_interval <= interval_t::nonnegative_int(is64)) { + // Both left_interval and right_interval fit in [0, INT_MAX], + // and can be treated as both signed and unsigned values + update_lt(is64, strict, std::move(left_interval), std::move(right_interval), + left_signed, left_unsigned, right_signed, right_unsigned, + left, right, loc, std::move(positive), std::move(positive), true, true, false, false); + } + else if (left_interval <= interval_t::unsigned_int(is64) && + right_interval <= interval_t::unsigned_int(is64)) { + update_lt(is64, strict, std::move(left_interval), std::move(right_interval), + left_signed, left_unsigned, right_signed, right_unsigned, + left, right, loc, interval_t::top(), std::move(positive), true, true, false, false); + } + else if (left_interval <= interval_t::unsigned_int(is64)) { + // interval can only be represented as uvalue + update_lt(is64, strict, std::move(left_interval), std::move(right_interval), + left_signed, left_unsigned, right_signed, right_unsigned, + left, right, loc, interval_t::top(), interval_t::top(), + false, true, false, false); + insert_in_registers_signed(left, loc, interval_t::top()); + if (std::holds_alternative(right)) { + insert_in_registers_signed(std::get(right).v, loc, interval_t::top()); + } + } + // possibly redundant case, since when left interval is negative, it is converted to + // unsigned high representation, while right interval likely is not negative + /* + else if (left_signed <= interval_t::negative_int(is64) && + right_signed <= interval_t::negative_int(is64)) { + // right_signed and left_signed fit in [INT_MIN, -1], and can be treated as + // both signed and unsigned, since [INT_MIN, -1] <=> [INT_MAX+1, UINT_MAX] + update_lt(is64, strict, std::move(left_interval), std::move(right_interval), + left_signed, left_unsigned, right_signed, right_unsigned, + left, right, loc, interval_t::negative_int(is64), + interval_t::unsigned_high(is64), true, true); + } + */ + else { + // interval can only be represented as uvalue + update_lt(is64, strict, std::move(left_interval), std::move(right_interval), + left_signed, left_unsigned, right_signed, right_unsigned, + left, right, loc, interval_t::top(), std::move(positive), false, true, false, false); + insert_in_registers_signed(left, loc, interval_t::top()); + if (std::holds_alternative(right)) { + insert_in_registers_signed(std::get(right).v, loc, interval_t::top()); + } + } +} + +void interval_domain_t::assume_unsigned_gt(bool is64, bool strict, + interval_t&& left_interval, interval_t&& right_interval, + const interval_t& left_signed, const interval_t& left_unsigned, + const interval_t& right_signed, const interval_t& right_unsigned, + register_t left, Value right, location_t loc) { + + auto positive = interval_t{number_t{0}, bound_t::plus_infinity()}; + if (left_interval <= interval_t::unsigned_int(is64) && + right_interval <= interval_t::unsigned_int(is64)) { + update_gt(is64, strict, std::move(left_interval), std::move(right_interval), + left_signed, left_unsigned, right_signed, right_unsigned, + left, right, loc, interval_t::top(), std::move(positive), true, true, false, false); + } + // possibly redundant analysis, see unsigned_lt + /* + else if (right_signed <= interval_t::negative_int(is64) + && left_signed <= interval_t::negative_int(is64)) { + // Both left_signed and right_signed fit in [INT_MIN, -1], and can be treated as both + // signed and unsigned values since [INT_MIN, -1] <=> [INT_MAX+1, UINT_MAX] + update_gt(is64, strict, std::move(left_interval), std::move(right_interval), + left_signed, left_unsigned, right_signed, right_unsigned, + left, right, loc, interval_t::negative_int(is64), + interval_t::unsigned_high(is64), true, true); + } + */ + else { + update_gt(is64, strict, std::move(left_interval), std::move(right_interval), + left_signed, left_unsigned, right_signed, right_unsigned, + left, right, loc, interval_t::top(), std::move(positive), false, true, false, false); + insert_in_registers_signed(left, loc, interval_t::top()); + if (std::holds_alternative(right)) { + insert_in_registers(std::get(right).v, loc, interval_t::top()); + } + } +} + +void interval_domain_t::assume_unsigned_cst(Condition::Op op, bool is64, + const interval_t& left_signed, const interval_t& left_unsigned, + const interval_t& right_signed, const interval_t& right_unsigned, + register_t left, Value right, location_t loc) { + + auto left_interval = interval_t::bottom(); + auto right_interval = interval_t::bottom(); + get_unsigned_intervals(is64, left_signed, left_unsigned, right_unsigned, + left_interval, right_interval); + + // Handle uvalue != right. + if (op == Condition::Op::NE) { + if (auto rn = right_interval.singleton()) { + if (rn == left_interval.truncate_to_uint(is64).lb().number()) { + // "NE lower bound" is equivalent to "GT lower bound". + op = Condition::Op::GT; + right_interval = interval_t{left_interval.lb()}; + } else if (rn == left_interval.ub().number()) { + // "NE upper bound" is equivalent to "LT upper bound". + op = Condition::Op::LT; + right_interval = interval_t{left_interval.ub()}; + } else { + return; + } + } else { + return; + } + } + const bool is_lt = op == Condition::Op::LT || op == Condition::Op::LE; + bool strict = op == Condition::Op::LT || op == Condition::Op::GT; + + auto llb = left_interval.lb(); + auto lub = left_interval.ub(); + auto rlb = right_interval.lb(); + auto rub = right_interval.ub(); + if (!is_lt && (strict ? (lub <= rlb) : (lub < rlb))) { + // Left unsigned interval is lower than right unsigned interval. + set_registers_to_bottom(); + return; + } else if (is_lt && (strict ? (llb >= rub) : (llb > rub))) { + // Left unsigned interval is higher than right unsigned interval. + set_registers_to_bottom(); + return; + } + if (is_lt && (strict ? (lub < rlb) : (lub <= rlb))) { + // Left unsigned interval is lower than right unsigned interval. + // TODO: verify if setting to top is the correct equivalent of returning linear cst true + set_registers_to_top(); + return; + } else if (!is_lt && (strict ? (llb > rub) : (llb >= rub))) { + // Left unsigned interval is higher than right unsigned interval. + set_registers_to_top(); + return; + } + + if (is_lt) + assume_unsigned_lt(is64, strict, std::move(left_interval), std::move(right_interval), + left_signed, left_unsigned, right_signed, right_unsigned, + left, right, loc); + else + assume_unsigned_gt(is64, strict, std::move(left_interval), std::move(right_interval), + left_signed, left_unsigned, right_signed, right_unsigned, + left, right, loc); +} + +// Given left and right values, get the left and right intervals +static void get_signed_intervals(bool is64, const interval_t& dst_signed, + const interval_t& dst_unsigned, const interval_t& src_signed, + interval_t& left_interval, interval_t& right_interval) { + + // Get intervals as 32-bit or 64-bit as appropriate. + left_interval = dst_signed; + right_interval = src_signed; + if (!is64) { + for (interval_t* interval : {&left_interval, &right_interval}) { + if (!(*interval <= interval_t::signed_int(false))) { + *interval = interval->truncate_to_sint(false); + } + } + } + + if (left_interval.is_top()) { + left_interval = dst_unsigned; + if (left_interval.is_top()) { + left_interval = interval_t::signed_int(is64); + } + else { + auto low = (left_interval & interval_t::unsigned_high(is64)).truncate_to_sint(true); + auto high = (left_interval & interval_t::nonnegative_int(is64)).truncate_to_sint(true); + left_interval = low | high; + } + } + + for (interval_t* interval : {&left_interval, &right_interval}) { + if (!(*interval <= interval_t::signed_int(true))) { + *interval = interval->truncate_to_sint(true); + } + } +} + +void interval_domain_t::update_lt(bool is64, bool strict, interval_t&& left_interval, + interval_t&& right_interval, const interval_t& left_signed, const interval_t& left_unsigned, + const interval_t& right_signed, const interval_t& right_unsigned, + register_t left, Value right, location_t loc, + interval_t&& restrict_signed, interval_t&& restrict_unsigned, + bool update_signed, bool update_unsigned, bool mk_equal_unsigned, bool is_signed) { + + auto rlb = right_interval.lb(); + auto rub = right_interval.ub(); + auto llb = left_interval.lb(); + auto lub = left_interval.ub(); + auto llbs = left_signed.lb(); + auto lubs = left_signed.ub(); + auto rlbs = right_signed.lb(); + auto rubs = right_signed.ub(); + auto llbu = left_unsigned.lb(); + auto lubu = left_unsigned.ub(); + auto rlbu = right_unsigned.lb(); + auto rubu = right_unsigned.ub(); + + bool holds_reg = std::holds_alternative(right); + + if (strict ? llb < rlb : llb <= rlb && lub >= rlb) { + if (update_signed) { + auto to_insert_signed = interval_t{llbs, strict ? rlbs - number_t{1} : rlbs}; + to_insert_signed = to_insert_signed & restrict_signed; + insert_in_registers_signed(left, loc, to_insert_signed); + if (mk_equal_unsigned && !is_signed) { + insert_in_registers_unsigned(left, loc, to_insert_signed); + } + } + + if (update_unsigned && !is_signed) { + auto to_insert_unsigned = interval_t{llbu, strict ? rlbu - number_t{1} : rlbu}; + to_insert_unsigned = to_insert_unsigned & restrict_unsigned; + insert_in_registers_unsigned(left, loc, to_insert_unsigned); + } + } + else if (left_interval <= right_interval && strict ? lub < rub : lub <= rub && holds_reg) { + auto right_reg = std::get(right).v; + if (update_signed) { + auto to_insert_signed = interval_t{strict ? lubs + number_t{1} : lubs, rubs}; + to_insert_signed = to_insert_signed & restrict_signed; + insert_in_registers_signed(right_reg, loc, to_insert_signed); + if (mk_equal_unsigned && !is_signed) { + insert_in_registers_unsigned(right_reg, loc, to_insert_signed); + } + } + + if (update_unsigned && !is_signed) { + auto to_insert_unsigned = interval_t{strict ? lubu + number_t{1} : lubu, rubu}; + to_insert_unsigned = to_insert_unsigned & restrict_unsigned; + insert_in_registers_unsigned(right_reg, loc, to_insert_unsigned); + } + } + else if (lub >= rub && strict ? llb < rub : llb <= rub) { + if (update_signed) { + auto to_insert_left_signed = interval_t{llbs, strict ? rubs - number_t{1} : rubs}; + to_insert_left_signed = to_insert_left_signed & restrict_signed; + insert_in_registers_signed(left, loc, to_insert_left_signed); + if (mk_equal_unsigned && !is_signed) { + insert_in_registers_unsigned(left, loc, to_insert_left_signed); + } + } + + if (update_unsigned && !is_signed) { + auto to_insert_left_unsigned = interval_t{llbu, strict ? rubu - number_t{1} : rubu}; + to_insert_left_unsigned = to_insert_left_unsigned & restrict_unsigned; + insert_in_registers_unsigned(left, loc, to_insert_left_unsigned); + } + + // this is only one way to resolve this scenario, i.e. set right to singleton value (rub) + // and set left to the rest of the interval < (or <=) of right + // a more sound analysis is needed + if (holds_reg) { + auto right_reg = std::get(right).v; + if (update_signed) { + auto to_insert_right_signed = interval_t{rubs} & restrict_signed; + insert_in_registers_signed(right_reg, loc, to_insert_right_signed); + if (mk_equal_unsigned && !is_signed) { + insert_in_registers_unsigned(right_reg, loc, to_insert_right_signed); + } + } + if (update_unsigned && !is_signed) { + insert_in_registers_unsigned(right_reg, loc, interval_t{rubu} & restrict_unsigned); + } + } + } + else { + // TODO: verify if any legitimate case can fall into here + set_registers_to_bottom(); + } + if (is_signed) { + insert_in_registers_unsigned(left, loc, left_unsigned); + if (std::holds_alternative(right)) { + insert_in_registers_unsigned(std::get(right).v, loc, right_unsigned); + } + } +} + +void interval_domain_t::assume_signed_lt(bool is64, bool strict, + interval_t&& left_interval, interval_t&& right_interval, + const interval_t& left_signed, const interval_t& left_unsigned, + const interval_t& right_signed, const interval_t& right_unsigned, + register_t left, Value right, location_t loc) { + + auto positive = interval_t{number_t{0}, bound_t::plus_infinity()}; + if (right_interval <= interval_t::negative_int(is64)) { + // right_interval fits in [INT_MIN, -1], and can be treated as both signed and unsigned + // since [INT_MIN, -1] <=> [INT_MAX+1, UINT_MAX] + // likewise for left_interval, as it is not > right_interval, and truncated to signed int + update_lt(is64, strict, std::move(left_interval), std::move(right_interval), + left_signed, left_unsigned, right_signed, right_unsigned, + left, right, loc, interval_t::top(), interval_t::unsigned_high(is64), true, true, false, true); + } + else if (left_interval <= interval_t::nonnegative_int(is64) && + right_interval <= interval_t::nonnegative_int(is64)) { + // Both left_interval and right_interval fit in [0, INT_MAX], + // and can be treated as both signed and unsigned values + update_lt(is64, strict, std::move(left_interval), std::move(right_interval), + left_signed, left_unsigned, right_signed, right_unsigned, + left, right, loc, std::move(positive), std::move(positive), true, false, true, true); + } + else { + // left_interval and right_interval can be treated as signed values only + update_lt(is64, strict, std::move(left_interval), std::move(right_interval), + left_signed, left_unsigned, right_signed, right_unsigned, + left, right, loc, interval_t::top(), interval_t::top(), true, false, false, false); + insert_in_registers_unsigned(left, loc, left_unsigned); + if (std::holds_alternative(right)) { + auto right_reg = std::get(right).v; + insert_in_registers_unsigned(right_reg, loc, right_unsigned); + } + } +} + +void interval_domain_t::update_gt(bool is64, bool strict, interval_t&& left_interval, + interval_t&& right_interval, const interval_t& left_signed, const interval_t& left_unsigned, + const interval_t& right_signed, const interval_t& right_unsigned, + register_t left, Value right, location_t loc, interval_t&& restrict_signed, + interval_t&& restrict_unsigned, bool update_signed, bool update_unsigned, + bool mk_equal_unsigned, bool is_signed) { + + auto rlb = right_interval.lb(); + auto rub = right_interval.ub(); + auto llb = left_interval.lb(); + auto lub = left_interval.ub(); + auto llbs = left_signed.lb(); + auto lubs = left_signed.ub(); + auto rlbs = right_signed.lb(); + auto rubs = right_signed.ub(); + auto llbu = left_unsigned.lb(); + auto lubu = left_unsigned.ub(); + auto rlbu = right_unsigned.lb(); + auto rubu = right_unsigned.ub(); + + bool holds_reg = std::holds_alternative(right); + + if (strict ? lub > rub : lub >= rub && llb <= rub) { + if (update_signed) { + auto to_insert_signed = interval_t{strict ? rubs + number_t{1} : rubs, lubs}; + to_insert_signed = to_insert_signed & restrict_signed; + insert_in_registers_signed(left, loc, to_insert_signed); + if (mk_equal_unsigned && !is_signed) { + insert_in_registers_unsigned(left, loc, to_insert_signed); + } + } + + if (update_unsigned && !is_signed) { + auto to_insert_unsigned = interval_t{strict ? rubu + number_t{1} : rubu, lubu}; + to_insert_unsigned = to_insert_unsigned & restrict_unsigned; + insert_in_registers_unsigned(left, loc, to_insert_unsigned); + } + } + else if (left_interval <= right_interval && strict ? llb > rlb : llb >= rlb && holds_reg) { + auto right_reg = std::get(right).v; + if (update_signed) { + auto to_insert_signed = interval_t{rlbs, strict ? llbs - number_t{1} : llbs}; + to_insert_signed = to_insert_signed & restrict_signed; + insert_in_registers_signed(right_reg, loc, to_insert_signed); + if (mk_equal_unsigned && !is_signed) { + insert_in_registers_unsigned(right_reg, loc, to_insert_signed); + } + } + + if (update_unsigned && !is_signed) { + auto to_insert_unsigned = interval_t{rlbu, strict ? llbu - number_t{1} : llbu}; + to_insert_unsigned = to_insert_unsigned & restrict_unsigned; + insert_in_registers_unsigned(right_reg, loc, to_insert_unsigned); + } + } + else if (llb <= rlb && strict ? lub > rlb : lub >= rlb) { + if (update_signed) { + auto to_insert_signed_left = interval_t{strict ? rlbs + number_t{1} : rlbs, lubs}; + to_insert_signed_left = to_insert_signed_left & restrict_signed; + insert_in_registers_signed(left, loc, to_insert_signed_left); + if (mk_equal_unsigned && !is_signed) { + insert_in_registers_unsigned(left, loc, to_insert_signed_left); + } + } + + if (update_unsigned && !is_signed) { + auto to_insert_unsigned_left = interval_t{strict ? rlbu + number_t{1} : rlbu, lubu}; + to_insert_unsigned_left = to_insert_unsigned_left & restrict_unsigned; + insert_in_registers_unsigned(left, loc, to_insert_unsigned_left); + } + + // this is only one way to resolve this scenario, i.e. set right to singleton value (rlb) + // and set left to the rest of the interval > (or >=) of right + // a more sound analysis is needed + if (holds_reg) { + auto right_reg = std::get(right).v; + if (update_signed) { + auto to_insert_signed_right = interval_t{rlbs} & restrict_signed; + insert_in_registers_signed(right_reg, loc, to_insert_signed_right); + if (mk_equal_unsigned && !is_signed) { + insert_in_registers_unsigned(right_reg, loc, to_insert_signed_right); + } + } + if (update_unsigned && !is_signed) { + insert_in_registers_unsigned(right_reg, loc, interval_t{rlbu} & restrict_unsigned); + } + } + } + else { + // TODO: verify if any legitimate case can fall into here + set_registers_to_bottom(); + } + if (is_signed) { + insert_in_registers_unsigned(left, loc, left_unsigned); + if (std::holds_alternative(right)) { + insert_in_registers_unsigned(std::get(right).v, loc, right_unsigned); + } + } +} + +void interval_domain_t::assume_signed_gt(bool is64, bool strict, + interval_t&& left_interval, interval_t&& right_interval, + const interval_t& left_signed, const interval_t& left_unsigned, + const interval_t& right_signed, const interval_t& right_unsigned, + register_t left, Value right, location_t loc) { + + auto positive = interval_t{number_t{0}, bound_t::plus_infinity()}; + if (right_interval <= interval_t::nonnegative_int(is64)) { + // right_interval fits in [0, INT_MAX], and can be treated as both signed and unsigned + // likewise fits in [0, UINT_MAX], as it is not < right_interval, + // and truncated to signed int + update_gt(is64, strict, std::move(left_interval), std::move(right_interval), + left_signed, left_unsigned, right_signed, right_unsigned, + left, right, loc, std::move(positive), std::move(positive), true, false, true, true); + } + else if (right_interval <= interval_t::negative_int(is64) + && left_interval <= interval_t::negative_int(is64)) { + // Both left_interval and right_interval fit in [INT_MIN, -1], and can be treated as both + // signed and unsigned values since [INT_MIN, -1] <=> [INT_MAX+1, UINT_MAX] + update_gt(is64, strict, std::move(left_interval), std::move(right_interval), + left_signed, left_unsigned, right_signed, right_unsigned, + left, right, loc, interval_t::top(), interval_t::unsigned_high(is64), + true, true, false, true); + } + else { + // left_interval and right_interval can be treated as signed values only + update_gt(is64, strict, std::move(left_interval), std::move(right_interval), + left_signed, left_unsigned, right_signed, right_unsigned, + left, right, loc, interval_t::top(), interval_t::top(), true, false, false, true); + insert_in_registers_unsigned(left, loc, left_unsigned); + if (std::holds_alternative(right)) { + insert_in_registers_unsigned(std::get(right).v, loc, right_unsigned); + } + } +} + +void interval_domain_t::assume_signed_cst(Condition::Op op, bool is64, + const interval_t& left_signed, const interval_t& left_unsigned, + const interval_t& right_signed, const interval_t& right_unsigned, + register_t left, Value right, location_t loc) { + + auto left_interval = interval_t::bottom(); + auto right_interval = interval_t::bottom(); + get_signed_intervals(is64, left_signed, left_unsigned, right_signed, + left_interval, right_interval); + + const bool is_lt = op == Condition::Op::SLT || op == Condition::Op::SLE; + bool strict = op == Condition::Op::SLT || op == Condition::Op::SGT; + + auto llb = left_interval.lb(); + auto lub = left_interval.ub(); + auto rlb = right_interval.lb(); + auto rub = right_interval.ub(); + if (!is_lt && (strict ? (lub <= rlb) : (lub < rlb))) { + // Left unsigned interval is lower than right unsigned interval. + set_registers_to_bottom(); + return; + } else if (is_lt && (strict ? (llb >= rub) : (llb > rub))) { + // Left unsigned interval is higher than right unsigned interval. + set_registers_to_bottom(); + return; + } + if (is_lt && (strict ? (lub < rlb) : (lub <= rlb))) { + // Left unsigned interval is lower than right unsigned interval. + // TODO: verify if setting to top is the correct equivalent of returning linear cst true + set_registers_to_top(); + return; + } else if (!is_lt && (strict ? (llb > rub) : (llb >= rub))) { + // Left unsigned interval is higher than right unsigned interval. + set_registers_to_top(); + return; + } + + if (is_lt) + assume_signed_lt(is64, strict, std::move(left_interval), std::move(right_interval), + left_signed, left_unsigned, right_signed, right_unsigned, + left, right, loc); + else + assume_signed_gt(is64, strict, std::move(left_interval), std::move(right_interval), + left_signed, left_unsigned, right_signed, right_unsigned, + left, right, loc); +} + +void interval_domain_t::assume_cst(Condition::Op op, bool is64, register_t left, + Value right, location_t loc) { + using Op = Condition::Op; + + auto left_signed = find_signed_interval_value(left)->to_interval(); + auto left_unsigned = find_unsigned_interval_value(left)->to_interval(); + auto right_signed = interval_t::bottom(); + auto right_unsigned = interval_t::bottom(); + if (std::holds_alternative(right)) { + auto right_reg = register_t{std::get(right).v}; + right_signed = find_signed_interval_value(right_reg)->to_interval(); + right_unsigned = find_unsigned_interval_value(right_reg)->to_interval(); + } else if (std::holds_alternative(right)) { + auto right_imm = std::get(right).v; + right_signed = interval_t{number_t{right_imm}}; + right_unsigned = interval_t(number_t{(uint64_t)right_imm}); + } + + switch (op) { + case Op::EQ: { + auto interval_signed = left_signed & right_signed; + auto interval_unsigned = left_unsigned & right_unsigned; + insert_in_registers_signed(left, loc, interval_signed); + insert_in_registers_unsigned(left, loc, interval_unsigned); + if (std::holds_alternative(right)) { + auto right_reg = std::get(right).v; + insert_in_registers_signed(right_reg, loc, interval_signed); + insert_in_registers_unsigned(right_reg, loc, interval_unsigned); + } + break; + } + case Op::SGE: + case Op::SLE: + case Op::SGT: + case Op::SLT: { + assume_signed_cst(op, is64, left_signed, left_unsigned, right_signed, + right_unsigned, left, right, loc); + break; + } + case Op::SET: + case Op::NSET: { + // TODO: implement SET and NSET + break; + } + case Op::NE: + case Op::GE: + case Op::LE: + case Op::GT: + case Op::LT: { + assume_unsigned_cst(op, is64, left_signed, left_unsigned, right_signed, + right_unsigned, left, right, loc); + break; + } + } +} + +void interval_domain_t::operator()(const Assume& s, location_t loc) { + // nothing to do here +} + +void interval_domain_t::operator-=(register_t reg) { + m_signed.operator-=(reg); + m_unsigned.operator-=(reg); +} + +bool interval_domain_t::load_from_stack(register_t reg, interval_t load_at, int width, + location_t loc) { + uint64_t start_offset = 0; + if (auto load_at_singleton = load_at.singleton()) { + start_offset = (uint64_t)(*load_at_singleton); + bool loaded_signed = m_signed.load_from_stack(reg, start_offset, loc); + bool loaded_unsigned = m_unsigned.load_from_stack(reg, start_offset, loc); + if (loaded_signed && loaded_unsigned) return true; + } + else { + auto load_at_lb = load_at.lb(); + auto load_at_ub = load_at.ub(); + if (auto finite_size = load_at.finite_size()) { + if (auto load_at_lb = load_at.lb().number()) { + start_offset = (uint64_t)(*load_at_lb); + width = (int)(*finite_size + number_t{width}); + } + } + } + if (all_numeric_in_stack(start_offset, width)) { + insert_in_registers(reg, loc, interval_t::top()); + return true; + } + return false; +} + +void interval_domain_t::do_load(const Mem& b, const register_t& target_register, + std::optional basereg_type, bool load_in_region, location_t loc) { + + if (!basereg_type) { + operator-=(target_register); + return; + } + + // we check if we already loaded a pointer from ctx or stack in region domain, + // we then do not store a number + if (load_in_region) { + operator-=(target_register); + return; + } + int width = b.access.width; + int offset = b.access.offset; + auto basereg_ptr_or_mapfd_type = basereg_type.value(); + + if (is_ctx_ptr(basereg_type)) { + insert_in_registers(target_register, loc, interval_t::top()); + return; + } + if (is_packet_ptr(basereg_type) || is_shared_ptr(basereg_type)) { + if (width == 1) { + interval_t to_insert = interval_t(number_t{0}, number_t{UINT8_MAX}); + insert_in_registers(target_register, loc, to_insert); + } + else if (width == 2) { + interval_t to_insert = interval_t(number_t{0}, number_t{UINT16_MAX}); + insert_in_registers(target_register, loc, to_insert); + } + else { + insert_in_registers(target_register, loc, interval_t::top()); + } + return; + } + + if (is_stack_ptr(basereg_type)) { + auto ptr_with_off = std::get(basereg_ptr_or_mapfd_type); + auto p_offset = ptr_with_off.get_offset(); + auto load_at = p_offset.to_interval() + interval_t(number_t{offset}); + if (load_from_stack(target_register, load_at, width, loc)) return; + } + operator-=(target_register); +} + +void interval_domain_t::store_in_stack(const Mem& b, uint64_t store_at, int width) { + m_signed.store_in_stack(b, store_at, width); + m_unsigned.store_in_stack(b, store_at, width); +} + +void interval_domain_t::do_mem_store(const Mem& b, std::optional basereg_type) { + int offset = b.access.offset; + int width = b.access.width; + + if (!is_stack_ptr(basereg_type)) { + // we only store for stack pointers + return; + } + + auto basereg_ptr_with_off_type = std::get(*basereg_type); + auto offset_singleton = basereg_ptr_with_off_type.get_offset().to_interval().singleton(); + if (!offset_singleton) { + m_errors.push_back("doing a store with unknown offset"); + return; + } + auto store_at = (uint64_t)(*offset_singleton + offset); + auto overlapping_cells = find_overlapping_cells_in_stack(store_at, width); + remove_overlap_in_stack(overlapping_cells, store_at, width); + store_in_stack(b, store_at, width); +} + +void interval_domain_t::check_valid_access(const ValidAccess& s, interval_t&& interval, + int width, bool check_stack_all_numeric) { + // access can be checked only in the signed domain + m_signed.check_valid_access(s, std::move(interval), width, check_stack_all_numeric); +} + +static void overflow_bounds(interval_t& interval, number_t span, int finite_width, + bool issigned) { + if (interval.ub() - interval.lb() >= span) { + // Interval covers the full space. + interval = interval_t::top(); + return; + } + if (interval.is_bottom()) { + interval = interval_t::top(); + return; + } + number_t lb_value = interval.lb().number().value(); + number_t ub_value = interval.ub().number().value(); + + // Compute the interval, taking overflow into account. + // For a signed result, we need to ensure the signed and unsigned results match + // so for a 32-bit operation, 0x80000000 should be a positive 64-bit number not + // a sign extended negative one. + number_t lb = lb_value.truncate_to_unsigned_finite_width(finite_width); + number_t ub = ub_value.truncate_to_unsigned_finite_width(finite_width); + if (issigned) { + lb = lb.truncate_to_sint64(); + ub = ub.truncate_to_sint64(); + } + if (lb > ub) { + // Range wraps in the middle, so we cannot represent as an unsigned interval. + interval = interval_t::top(); + return; + } + interval = crab::interval_t{lb, ub}; +} + +static void overflow_unsigned(interval_t& interval, int finite_width) { + auto span{finite_width == 64 ? crab::z_number{std::numeric_limits::max()} + : finite_width == 32 ? crab::z_number{std::numeric_limits::max()} + : throw std::exception()}; + overflow_bounds(interval, span, finite_width, false); +} + +static void overflow_signed(interval_t& interval, int finite_width) { + auto span{finite_width == 64 ? crab::z_number{std::numeric_limits::max()} + : finite_width == 32 ? crab::z_number{std::numeric_limits::max()} + : throw std::exception()}; + overflow_bounds(interval, span, finite_width, true); +} + +static void apply_unsigned(interval_t& dst_signed, interval_t& dst_unsigned, + const interval_t& src, int finite_width, const interval_t& updated_interval, Bin::Op op) { + switch (op) { + case Bin::Op::UDIV: { + // this hack is required because UDiv call returns [+oo, +oo], at least in case when + // updated_interval is top, + // which causes the error: CRAB ERROR: Bound: undefined operation -oo + +oo + // SplitDBM (possibly) uses a normalize() to avoid this issue, + // but we don't have that here + // TODO: fix this + if (updated_interval.is_top()) { + dst_unsigned = interval_t::top(); + dst_signed = interval_t::top(); + return; + } + dst_unsigned = updated_interval.UDiv(src); + break; + } + case Bin::Op::UMOD: { + dst_unsigned = updated_interval.URem(src); + break; + } + case Bin::Op::AND: { + dst_unsigned = updated_interval.And(src); + break; + } + case Bin::Op::OR: { + dst_unsigned = updated_interval.Or(src); + break; + } + case Bin::Op::XOR: { + dst_unsigned = updated_interval.Xor(src); + break; + } + case Bin::Op::LSH: { + dst_unsigned = updated_interval.Shl(src); + break; + } + case Bin::Op::RSH: { + dst_unsigned = updated_interval.LShr(src); + break; + } + case Bin::Op::ARSH: { + dst_unsigned = updated_interval.AShr(src); + break; + } + default: { + break; + } + } + if (finite_width) { + dst_signed = dst_unsigned; + overflow_unsigned(dst_unsigned, finite_width); + overflow_signed(dst_signed, finite_width); + } +} + +static void apply_signed(interval_t& dst_signed, interval_t& dst_unsigned, const interval_t& src, + int finite_width, const interval_t& updated_interval, Bin::Op op) { + switch (op) { + case Bin::Op::ADD: { + dst_signed = updated_interval + src; + break; + } + case Bin::Op::SUB: { + dst_signed = updated_interval - src; + break; + } + case Bin::Op::MUL: { + dst_signed = updated_interval * src; + break; + } + default: { + break; + } + } + if (finite_width) { + dst_unsigned = dst_signed; + overflow_signed(dst_signed, finite_width); + overflow_unsigned(dst_unsigned, finite_width); + } +} + +static void shl(register_t reg, int imm, int finite_width, + interval_t& dst_signed, interval_t& dst_unsigned, location_t loc) { + // The BPF ISA requires masking the imm. + imm &= finite_width - 1; + + if (dst_unsigned.finite_size()) { + number_t lb = dst_unsigned.lb().number().value(); + number_t ub = dst_unsigned.ub().number().value(); + uint64_t lb_n = lb.cast_to_uint64(); + uint64_t ub_n = ub.cast_to_uint64(); + uint64_t uint_max = (finite_width == 64) ? UINT64_MAX : UINT32_MAX; + if ((lb_n >> (finite_width - imm)) != (ub_n >> (finite_width - imm))) { + // The bits that will be shifted out to the left are different, + // which means all combinations of remaining bits are possible. + lb_n = 0; + ub_n = (uint_max << imm) & uint_max; + } else { + // The bits that will be shifted out to the left are identical + // for all values in the interval, so we can safely shift left + // to get a new interval. + lb_n = (lb_n << imm) & uint_max; + ub_n = (ub_n << imm) & uint_max; + } + dst_unsigned = interval_t{number_t{lb_n}, number_t{ub_n}}; + dst_signed = interval_t::top(); + if ((int64_t)ub_n >= (int64_t)lb_n) { + dst_signed = dst_unsigned; + } + return; + } + apply_unsigned(dst_signed, dst_unsigned, interval_t{number_t{imm}}, 64, + dst_unsigned, Bin::Op::LSH); +} + +static void lshr(register_t reg, int imm, int finite_width, + interval_t& dst_signed, interval_t& dst_unsigned, location_t loc) { + // The BPF ISA requires masking the imm. + imm &= finite_width - 1; + + number_t lb_n{0}; + number_t ub_n{UINT64_MAX >> imm}; + if (dst_unsigned.finite_size()) { + number_t lb = dst_unsigned.lb().number().value(); + number_t ub = dst_unsigned.ub().number().value(); + if (finite_width == 64) { + lb_n = lb.cast_to_uint64() >> imm; + ub_n = ub.cast_to_uint64() >> imm; + } else { + number_t lb_w = lb.cast_to_signed_finite_width(finite_width); + number_t ub_w = ub.cast_to_signed_finite_width(finite_width); + lb_n = lb_w.cast_to_uint32() >> imm; + ub_n = ub_w.cast_to_uint32() >> imm; + + // The interval must be valid since a signed range crossing 0 + // was earlier converted to a full unsigned range. + assert(lb_n <= ub_n); + } + } + dst_unsigned = interval_t{lb_n, ub_n}; + if ((int64_t)ub_n >= (int64_t)lb_n) { + dst_signed = dst_unsigned; + } else { + dst_signed = interval_t::top(); + } + return; +} + +static void ashr(register_t reg, interval_t src, int finite_width, + interval_t& dst_signed, interval_t& dst_unsigned, location_t loc) { + interval_t left_interval = interval_t::bottom(); + interval_t right_interval = interval_t::bottom(); + get_signed_intervals(finite_width == 64, dst_signed, dst_unsigned, + src, left_interval, right_interval); + if (auto sn = right_interval.singleton()) { + // The BPF ISA requires masking the imm. + int64_t imm = sn->cast_to_sint64() & (finite_width - 1); + + int64_t lb_n = INT64_MIN >> imm; + int64_t ub_n = INT64_MAX >> imm; + if (left_interval.finite_size()) { + number_t lb = left_interval.lb().number().value(); + number_t ub = left_interval.ub().number().value(); + if (finite_width == 64) { + lb_n = lb.cast_to_sint64() >> imm; + ub_n = ub.cast_to_sint64() >> imm; + } else { + number_t lb_w = lb.cast_to_signed_finite_width(finite_width) >> (int)imm; + number_t ub_w = ub.cast_to_signed_finite_width(finite_width) >> (int)imm; + if (lb_w.cast_to_uint32() <= ub_w.cast_to_uint32()) { + lb_n = lb_w.cast_to_uint32(); + ub_n = ub_w.cast_to_uint32(); + } + } + } + dst_signed = interval_t{number_t{lb_n}, number_t{ub_n}}; + dst_unsigned = interval_t::top(); + if ((uint64_t)ub_n >= (uint64_t)lb_n) { + dst_unsigned = dst_signed; + } + return; + } + dst_signed = interval_t::top(); + dst_unsigned = interval_t::top(); +} + +void interval_domain_t::do_bin(const Bin& bin, + const std::optional& src_signed_interval_opt, + const std::optional& src_unsigned_interval_opt, + const std::optional& src_ptr_or_mapfd_opt, + const std::optional& dst_signed_interval_opt, + const std::optional& dst_unsigned_interval_opt, + const std::optional& dst_ptr_or_mapfd_opt, + const interval_t& subtracted, location_t loc) { + + using Op = Bin::Op; + + auto dst_register = register_t{bin.dst.v}; + auto finite_width = (bin.is64 ? 64 : 32); + + interval_t dst_signed = subtracted; + interval_t dst_unsigned = subtracted; + if (subtracted != interval_t::bottom()) { + if (!(dst_signed <= interval_t::signed_int(bin.is64))) { + dst_signed = dst_signed.truncate_to_sint(bin.is64); + } + insert_in_registers_signed(dst_register, loc, dst_signed); + if (!(dst_unsigned <= interval_t::unsigned_int(bin.is64))) { + dst_unsigned = dst_unsigned.truncate_to_uint(bin.is64); + } + insert_in_registers_unsigned(dst_register, loc, dst_unsigned); + return; + } + if (dst_signed_interval_opt) dst_signed = std::move(*dst_signed_interval_opt); + if (dst_unsigned_interval_opt) dst_unsigned = std::move(*dst_unsigned_interval_opt); + + if (std::holds_alternative(bin.v)) { + int64_t imm; + if (bin.is64) { + // Use the full signed value. + imm = static_cast(std::get(bin.v).v); + } else { + // Use only the low 32 bits of the value. + imm = static_cast(std::get(bin.v).v); + if (dst_signed_interval_opt && dst_unsigned_interval_opt) { + apply_unsigned(dst_signed, dst_unsigned, interval_t{number_t{UINT32_MAX}}, + 64, dst_unsigned, Op::AND); + } + } + auto imm_number = number_t{imm}; + auto imm_interval = interval_t{imm_number}; + auto imm_unsigned_interval = interval_t{number_t{imm_number.cast_to_uint64()}}; + auto imm_int_interval = interval_t{number_t{(int)imm}}; + switch (bin.op) { + case Op::MOV: { + // ra = imm + dst_signed = imm_interval; + overflow_unsigned(imm_interval, (bin.is64 ? 64 : 32)); + dst_unsigned = imm_interval; + break; + } + case Op::ADD: { + // ra += imm + if (imm == 0) { + return; + } + if (dst_signed_interval_opt && dst_unsigned_interval_opt) { + auto interval = dst_signed.is_top() ? dst_unsigned : dst_signed; + apply_signed(dst_signed, dst_unsigned, imm_int_interval, finite_width, + interval, Op::ADD); + } + else { + operator-=(dst_register); + } + break; + } + case Op::SUB: { + // ra -= imm + if (imm == 0) { + return; + } + if (dst_signed_interval_opt && dst_unsigned_interval_opt) { + auto interval = dst_signed.is_top() ? dst_unsigned : dst_signed; + apply_signed(dst_signed, dst_unsigned, imm_int_interval, finite_width, + interval, Op::SUB); + } + else { + operator-=(dst_register); + } + break; + } + case Op::MUL: { + // ra *= imm + if (dst_signed_interval_opt && dst_unsigned_interval_opt) { + apply_signed(dst_signed, dst_unsigned, imm_interval, finite_width, dst_signed, + Op::MUL); + } + else { + operator-=(dst_register); + } + break; + } + case Op::UDIV: { + // ra /= imm + if (dst_unsigned_interval_opt && dst_signed_interval_opt) { + apply_unsigned(dst_signed, dst_unsigned, imm_interval, finite_width, + dst_unsigned, Op::UDIV); + } + else { + operator-=(dst_register); + } + break; + } + case Op::UMOD: { + // ra %= imm + if (dst_unsigned_interval_opt && dst_signed_interval_opt) { + apply_unsigned(dst_signed, dst_unsigned, imm_interval, finite_width, + dst_unsigned, Op::UMOD); + } + else { + operator-=(dst_register); + } + break; + } + case Op::AND: { + // ra &= imm + // might or might not be needed, but some case occurred where left was negative + dst_unsigned = dst_unsigned.truncate_to_uint(finite_width); + if (dst_signed_interval_opt && dst_unsigned_interval_opt) { + apply_unsigned(dst_signed, dst_unsigned, imm_unsigned_interval, finite_width, + dst_unsigned, Op::AND); + if ((int32_t)imm > 0) { + // AND with immediate is only a 32-bit operation so svalue and uvalue + // are the same. + dst_signed = dst_signed & interval_t{number_t{0}, number_t{imm}}; + dst_unsigned = dst_unsigned & interval_t{number_t{0}, number_t{imm}}; + } + } + else { + operator-=(dst_register); + } + break; + } + case Op::OR: { + // ra |= imm + if (dst_signed_interval_opt && dst_unsigned_interval_opt) { + apply_unsigned(dst_signed, dst_unsigned, imm_unsigned_interval, finite_width, + dst_unsigned, Op::OR); + } + else { + operator-=(dst_register); + } + break; + } + case Op::XOR: { + // ra ^= imm + if (dst_signed_interval_opt && dst_unsigned_interval_opt) { + apply_unsigned(dst_signed, dst_unsigned, imm_unsigned_interval, finite_width, + dst_unsigned, Op::XOR); + } + else { + operator-=(dst_register); + } + break; + } + case Op::LSH: { + // ra <<= imm + if (dst_signed_interval_opt && dst_unsigned_interval_opt) { + shl(dst_register, (int32_t)imm, finite_width, dst_signed, dst_unsigned, loc); + } + else { + operator-=(dst_register); + } + break; + } + case Op::RSH: { + // ra >>= imm + if (dst_signed_interval_opt && dst_unsigned_interval_opt) { + lshr(dst_register, (int32_t)imm, finite_width, dst_signed, dst_unsigned, loc); + } + else { + operator-=(dst_register); + } + break; + } + case Op::ARSH: { + // ra >>>= imm + if (dst_signed_interval_opt && dst_unsigned_interval_opt) { + ashr(dst_register, interval_t{number_t{(int32_t)imm}}, finite_width, + dst_signed, dst_unsigned, loc); + } + else { + operator-=(dst_register); + } + break; + } + default: { + break; + } + } + } + else { + if (!src_signed_interval_opt || !src_unsigned_interval_opt) { + operator-=(dst_register); + return; + } + interval_t src_signed = *src_signed_interval_opt; + interval_t src_unsigned = *src_unsigned_interval_opt; + switch (bin.op) { + case Op::MOV: { + // ra = rb + dst_signed = src_signed; + dst_unsigned = src_unsigned; + break; + } + case Op::ADD: { + // ra += rb + if (dst_signed_interval_opt && dst_unsigned_interval_opt) { + auto interval = dst_signed.is_top() ? dst_unsigned : dst_signed; + apply_signed(dst_signed, dst_unsigned, src_signed, finite_width, interval, + Op::ADD); + } + else { + operator-=(dst_register); + } + break; + } + case Op::SUB: { + // ra -= rb + if (dst_signed_interval_opt && dst_unsigned_interval_opt) { + auto interval = dst_signed.is_top() ? dst_unsigned : dst_signed; + apply_signed(dst_signed, dst_unsigned, src_signed, finite_width, interval, + Op::SUB); + } + else { + operator-=(dst_register); + } + break; + } + case Op::MUL: { + // ra *= rb + if (dst_signed_interval_opt && dst_unsigned_interval_opt) { + apply_signed(dst_signed, dst_unsigned, src_signed, finite_width, dst_signed, + Op::MUL); + } + else { + operator-=(dst_register); + } + break; + } + case Op::UDIV: { + // ra /= rb + if (dst_unsigned_interval_opt && dst_signed_interval_opt) { + apply_unsigned(dst_signed, dst_unsigned, src_unsigned, finite_width, + dst_unsigned, Op::UDIV); + } + else { + operator-=(dst_register); + } + break; + } + case Op::UMOD: { + // ra %= rb + if (dst_unsigned_interval_opt && dst_signed_interval_opt) { + apply_unsigned(dst_signed, dst_unsigned, src_unsigned, finite_width, + dst_unsigned, Op::UMOD); + } + else { + operator-=(dst_register); + } + break; + } + case Op::AND: { + // ra &= rb + // might or might not be needed + dst_unsigned = dst_unsigned.truncate_to_uint(finite_width); + src_unsigned = src_unsigned.truncate_to_uint(finite_width); + if (dst_signed_interval_opt && dst_unsigned_interval_opt) { + apply_unsigned(dst_signed, dst_unsigned, src_unsigned, finite_width, + dst_unsigned, Op::AND); + } + else { + operator-=(dst_register); + } + break; + } + case Op::OR: { + // ra |= rb + if (dst_signed_interval_opt && dst_unsigned_interval_opt) { + apply_unsigned(dst_signed, dst_unsigned, src_unsigned, finite_width, + dst_unsigned, Op::OR); + } + else { + operator-=(dst_register); + } + break; + } + case Op::XOR: { + // ra ^= rb + if (dst_signed_interval_opt && dst_unsigned_interval_opt) { + apply_unsigned(dst_signed, dst_unsigned, src_unsigned, finite_width, + dst_unsigned, Op::XOR); + } + else { + operator-=(dst_register); + } + break; + } + case Op::LSH: { + // ra <<= rb + if (dst_signed_interval_opt && dst_unsigned_interval_opt) { + if (std::optional sn = src_unsigned.singleton()) { + uint64_t imm = sn->cast_to_uint64() & (bin.is64 ? 63 : 31); + if (imm <= INT32_MAX) { + if (!bin.is64) { + // Use only the low 32 bits of the value. + dst_signed = dst_signed & interval_t{number_t{UINT32_MAX}}; + dst_unsigned = dst_unsigned & interval_t{number_t{UINT32_MAX}}; + } + shl(dst_register, (int32_t)imm, finite_width, dst_signed, dst_unsigned, loc); + break; + } + } + apply_unsigned(dst_signed, dst_unsigned, src_unsigned, 64, + dst_unsigned, Op::LSH); + } + else { + operator-=(dst_register); + } + break; + } + case Op::RSH: { + // ra >>= rb + if (dst_signed_interval_opt && dst_unsigned_interval_opt) { + if (std::optional sn = src_unsigned.singleton()) { + uint64_t imm = sn->cast_to_uint64() & (bin.is64 ? 63 : 31); + if (imm <= INT32_MAX) { + if (!bin.is64) { + // Use only the low 32 bits of the value. + dst_signed = dst_signed & interval_t{number_t{UINT32_MAX}}; + dst_unsigned = dst_unsigned & interval_t{number_t{UINT32_MAX}}; + } + lshr(dst_register, (int32_t)imm, finite_width, dst_signed, dst_unsigned, loc); + break; + } + } + dst_signed = interval_t::top(); + dst_unsigned = interval_t::top(); + } + else { + operator-=(dst_register); + } + break; + } + case Op::ARSH: { + // ra >>>= rb + if (dst_signed_interval_opt && dst_unsigned_interval_opt) { + ashr(dst_register, src_signed, finite_width, dst_signed, dst_unsigned, loc); + } + else { + operator-=(dst_register); + } + break; + } + default: { + break; + } + } + } + if (!dst_signed.is_bottom() && !dst_unsigned.is_bottom()) { + if (!bin.is64) { + apply_unsigned(dst_signed, dst_unsigned, interval_t{number_t{UINT32_MAX}}, + finite_width, dst_unsigned, Op::AND); + } + insert_in_registers_signed(dst_register, loc, dst_signed); + insert_in_registers_unsigned(dst_register, loc, dst_unsigned); + } + else { + operator-=(dst_register); + } +} + +void interval_domain_t::operator()(const Undefined& u, location_t loc) { + // nothing to do here +} + +void interval_domain_t::operator()(const Bin& b, location_t loc) { + // nothing to do here +} + +void interval_domain_t::operator()(const Call&, location_t loc) { + // nothing to do here +} + +void interval_domain_t::operator()(const Exit&, location_t loc) { + // nothing to do here +} + +void interval_domain_t::operator()(const Jmp&, location_t loc) { + // nothing to do here +} + +void interval_domain_t::operator()(const Mem&, location_t loc) { + // nothing to do here +} + +void interval_domain_t::operator()(const Assert&, location_t loc) { + // nothing to do here +} + +void interval_domain_t::operator()(const basic_block_t& bb, int print) { + // nothing to do here +} +void interval_domain_t::set_require_check(check_require_func_t f) {} + +} // namespace crab diff --git a/src/crab/interval_domain.hpp b/src/crab/interval_domain.hpp new file mode 100644 index 000000000..8431f3b3c --- /dev/null +++ b/src/crab/interval_domain.hpp @@ -0,0 +1,139 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT + +#pragma once + +#include "crab/common.hpp" +#include "crab/signed_interval_domain.hpp" +#include "crab/unsigned_interval_domain.hpp" + +namespace crab { + +class interval_domain_t final { + signed_interval_domain_t m_signed; + unsigned_interval_domain_t m_unsigned; + std::vector m_errors; + bool m_is_bottom = false; + + public: + + interval_domain_t() = default; + interval_domain_t(interval_domain_t&& o) = default; + interval_domain_t(const interval_domain_t& o) = default; + explicit interval_domain_t(signed_interval_domain_t&& signed_domain, + unsigned_interval_domain_t&& unsigned_domain, bool is_bottom = false) : + m_signed(std::move(signed_domain)), m_unsigned(std::move(unsigned_domain)), + m_is_bottom(is_bottom) {} + interval_domain_t& operator=(interval_domain_t&& o) = default; + interval_domain_t& operator=(const interval_domain_t& o) = default; + // eBPF initialization: R1 points to ctx, R10 to stack, etc. + static interval_domain_t setup_entry(); + // bottom/top + static interval_domain_t bottom(); + void set_to_top(); + void set_to_bottom(); + void set_registers_to_bottom(); + void set_registers_to_top(); + bool is_bottom() const; + bool is_top() const; + // inclusion + bool operator<=(const interval_domain_t& other) const; + // join + void operator|=(const interval_domain_t& abs); + void operator|=(interval_domain_t&& abs); + interval_domain_t operator|(const interval_domain_t& other) const; + interval_domain_t operator|(interval_domain_t&& abs) const; + // meet + interval_domain_t operator&(const interval_domain_t& other) const; + // widening + interval_domain_t widen(const interval_domain_t& other, bool); + // narrowing + interval_domain_t narrow(const interval_domain_t& other) const; + void operator-=(register_t reg); + + //// abstract transformers + void operator()(const Undefined&, location_t loc = boost::none); + void operator()(const Bin&, location_t loc = boost::none); + void operator()(const Un&, location_t loc = boost::none); + void operator()(const LoadMapFd&, location_t loc = boost::none); + void operator()(const Call&, location_t loc = boost::none); + void operator()(const Exit&, location_t loc = boost::none); + void operator()(const Jmp&, location_t loc = boost::none); + void operator()(const Mem&, location_t loc = boost::none); + void operator()(const Packet&, location_t loc = boost::none); + void operator()(const Assume&, location_t loc = boost::none); + void operator()(const Assert&, location_t loc = boost::none); + void operator()(const basic_block_t& bb, int print = 0); + void write(std::ostream& os) const {} + crab::bound_t get_loop_count_upper_bound(); + void initialize_loop_counter(const label_t&); + string_invariant to_set(); + void set_require_check(check_require_func_t f); + + void do_load(const Mem&, const register_t&, std::optional, bool, location_t); + void do_mem_store(const Mem&, std::optional); + void do_call(const Call&, const stack_cells_t&, location_t); + void do_bin(const Bin&, const std::optional&, + const std::optional&, const std::optional&, + const std::optional&, const std::optional&, + const std::optional&, const interval_t&, location_t); + void check_valid_access(const ValidAccess&, interval_t&&, int = -1, bool = false); + void assume_cst(Condition::Op, bool, register_t, Value, location_t); + void assume_signed_cst(Condition::Op, bool, const interval_t&, const interval_t&, + const interval_t&, const interval_t&, register_t, Value, location_t); + void assume_signed_lt(bool, bool, interval_t&&, interval_t&&, const interval_t&, + const interval_t&, const interval_t&, const interval_t&, register_t, Value, location_t); + void assume_signed_gt(bool, bool, interval_t&&, interval_t&&, const interval_t&, + const interval_t&, const interval_t&, const interval_t&, register_t, Value, location_t); + void assume_unsigned_cst(Condition::Op, bool, const interval_t&, const interval_t&, + const interval_t&, const interval_t&, register_t, Value, location_t); + void assume_unsigned_lt(bool, bool, interval_t&&, interval_t&&, const interval_t&, + const interval_t&, const interval_t&, const interval_t&, register_t, Value, location_t); + void assume_unsigned_gt(bool, bool, interval_t&&, interval_t&&, const interval_t&, + const interval_t&, const interval_t&, const interval_t&, register_t, Value, location_t); + void update_gt(bool, bool, interval_t&&, interval_t&&, const interval_t&, const interval_t&, + const interval_t&, const interval_t&, register_t, Value, location_t, + interval_t&&, interval_t&&, bool, bool, bool, bool); + void update_lt(bool, bool, interval_t&&, interval_t&&, const interval_t&, const interval_t&, + const interval_t&, const interval_t&, register_t, Value, location_t, + interval_t&&, interval_t&&, bool, bool, bool, bool); + std::optional find_interval_value(register_t) const; + std::optional find_signed_interval_value(register_t) const; + std::optional find_unsigned_interval_value(register_t) const; + std::optional find_signed_interval_at_loc(const reg_with_loc_t reg) const; + std::optional find_unsigned_interval_at_loc(const reg_with_loc_t reg) const; + std::optional find_in_stack_signed(uint64_t) const; + std::optional find_in_stack_unsigned(uint64_t) const; + void insert_in_registers(register_t, location_t, interval_t); + void insert_in_registers_signed(register_t, location_t, interval_t); + void insert_in_registers_unsigned(register_t, location_t, interval_t); + void store_in_stack(uint64_t, mock_interval_t, int); + void store_in_stack_signed(uint64_t, mock_interval_t, int); + void store_in_stack_unsigned(uint64_t, mock_interval_t, int); + void adjust_bb_for_types(location_t); + std::vector get_stack_keys() const; + bool all_numeric_in_stack(uint64_t, int) const; + std::vector find_overlapping_cells_in_stack(uint64_t, int) const; + void remove_overlap_in_stack(const std::vector&, uint64_t, int); + void fill_values_in_stack(const std::vector&, uint64_t, int); + [[nodiscard]] std::vector& get_errors() { + operator+=(m_signed.get_errors()); + operator+=(m_unsigned.get_errors()); + return m_errors; + } + void reset_errors() { + m_errors.clear(); + m_signed.reset_errors(); + m_unsigned.reset_errors(); + } + void operator+=(std::vector& errs) { + m_errors.insert(m_errors.end(), errs.begin(), errs.end()); + } + + private: + void scratch_caller_saved_registers(); + bool load_from_stack(register_t, interval_t, int, location_t); + void store_in_stack(const Mem&, uint64_t, int); +}; // end interval_domain_t + +} // namespace crab diff --git a/src/crab/interval_prop_domain.cpp b/src/crab/interval_prop_domain.cpp deleted file mode 100644 index 67c9a4d59..000000000 --- a/src/crab/interval_prop_domain.cpp +++ /dev/null @@ -1,1171 +0,0 @@ -// Copyright (c) Prevail Verifier contributors. -// SPDX-License-Identifier: MIT - -#include "crab/interval_prop_domain.hpp" -#include "boost/endian/conversion.hpp" - -namespace crab { - -bool registers_cp_state_t::is_bottom() const { - return m_is_bottom; -} - -bool registers_cp_state_t::is_top() const { - if (m_is_bottom) return false; - if (m_interval_env == nullptr) return true; - for (auto it : m_cur_def) { - if (it != nullptr) return false; - } - return true; -} - -void registers_cp_state_t::set_to_top() { - m_interval_env = std::make_shared(); - m_cur_def = live_registers_t{nullptr}; - m_is_bottom = false; -} - -void registers_cp_state_t::set_to_bottom() { - m_is_bottom = true; -} - -void registers_cp_state_t::insert(register_t reg, const location_t& loc, interval_t interval) { - auto reg_with_loc = reg_with_loc_t{reg, loc}; - (*m_interval_env)[reg_with_loc] = mock_interval_t{interval}; - m_cur_def[reg] = std::make_shared(reg_with_loc); -} - -std::optional registers_cp_state_t::find(reg_with_loc_t reg) const { - auto it = m_interval_env->find(reg); - if (it == m_interval_env->end()) return {}; - return it->second; -} - -std::optional registers_cp_state_t::find(register_t key) const { - if (m_cur_def[key] == nullptr) return {}; - const reg_with_loc_t& reg = *(m_cur_def[key]); - return find(reg); -} - -registers_cp_state_t registers_cp_state_t::operator|(const registers_cp_state_t& other) const { - if (is_bottom() || other.is_top()) { - return other; - } else if (other.is_bottom() || is_top()) { - return *this; - } - auto intervals_env = std::make_shared(); - registers_cp_state_t intervals_joined(intervals_env); - location_t loc = location_t(std::make_pair(label_t(-2, -2), 0)); - for (uint8_t i = 0; i < NUM_REGISTERS; i++) { - if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; - auto it1 = find(*(m_cur_def[i])); - auto it2 = other.find(*(other.m_cur_def[i])); - if (it1 && it2) { - auto interval1 = it1->to_interval(), interval2 = it2->to_interval(); - intervals_joined.insert(register_t{i}, loc, - std::move(interval1 | interval2)); - } - } - return intervals_joined; -} - -void registers_cp_state_t::adjust_bb_for_registers(location_t loc) { - for (uint8_t i = 0; i < NUM_REGISTERS; i++) { - if (auto it = find(register_t{i})) { - insert(register_t{i}, loc, it->to_interval()); - } - } -} - -void registers_cp_state_t::operator-=(register_t var) { - if (is_bottom()) return; - m_cur_def[var] = nullptr; -} - -void registers_cp_state_t::scratch_caller_saved_registers() { - for (uint8_t i = R1_ARG; i <= R5_ARG; i++) { - operator-=(register_t{i}); - } -} - -bool stack_cp_state_t::is_bottom() const { - return m_is_bottom; -} - -bool stack_cp_state_t::is_top() const { - if (m_is_bottom) return false; - return m_interval_values.empty(); -} - -void stack_cp_state_t::set_to_top() { - m_interval_values.clear(); - m_is_bottom = false; -} - -void stack_cp_state_t::set_to_bottom() { - m_is_bottom = true; -} - -stack_cp_state_t stack_cp_state_t::top() { - return stack_cp_state_t(false); -} - -std::optional stack_cp_state_t::find(uint64_t key) const { - auto it = m_interval_values.find(key); - if (it == m_interval_values.end()) return {}; - return it->second; -} - -void stack_cp_state_t::store(uint64_t key, mock_interval_t val, int width) { - m_interval_values[key] = std::make_pair(val, width); -} - -void stack_cp_state_t::operator-=(uint64_t key) { - auto it = find(key); - if (it) - m_interval_values.erase(key); -} - -bool stack_cp_state_t::all_numeric(uint64_t start_loc, int width) const { - auto overlapping_cells = find_overlapping_cells(start_loc, width); - if (overlapping_cells.empty()) return false; - for (std::size_t i = 0; i < overlapping_cells.size()-1; i++) { - int width_i = find(overlapping_cells[i]).value().second; - if (overlapping_cells[i]+width_i != overlapping_cells[i+1]) return false; - } - return true; -} - -void stack_cp_state_t::remove_overlap(const std::vector& keys, uint64_t start, int width) { - for (auto& key : keys) { - auto type = find(key); - auto width_key = type.value().second; - if (key < start) { - int new_width = start-key; - store(key, interval_t::top(), new_width); - } - if (key+width_key > start+width) { - int new_width = key+width_key-(start+width); - store(start+width, interval_t::top(), new_width); - } - if (key >= start) *this -= key; - } -} - -std::vector stack_cp_state_t::find_overlapping_cells(uint64_t start, int width) const { - std::vector overlapping_cells; - auto it = m_interval_values.begin(); - while (it != m_interval_values.end() && it->first < start) { - it++; - } - if (it != m_interval_values.begin()) { - it--; - auto key = it->first; - auto width_key = it->second.second; - if (key < start && key+width_key > start) overlapping_cells.push_back(key); - } - - for (; it != m_interval_values.end(); it++) { - auto key = it->first; - if (key >= start && key < start+width) overlapping_cells.push_back(key); - if (key >= start+width) break; - } - return overlapping_cells; -} - -void join_stack(const stack_cp_state_t& stack1, uint64_t key1, int& loc1, - const stack_cp_state_t& stack2, uint64_t key2, int& loc2, - interval_values_stack_t& interval_values_joined) { - auto type1 = stack1.find(key1); auto type2 = stack2.find(key2); - auto& cells1 = type1.value(); auto& cells2 = type2.value(); - int width1 = cells1.second; int width2 = cells2.second; - auto interval1 = cells1.first.to_interval(); - auto interval2 = cells2.first.to_interval(); - auto top_interval_mock = mock_interval_t(interval_t::top()); - if (key1 == key2) { - if (width1 == width2) { - interval_values_joined[key1] = std::make_pair( - mock_interval_t(interval1 | interval2), width1); - loc1++; loc2++; - } - else if (width1 < width2) { - interval_values_joined[key1] = std::make_pair(top_interval_mock, width1); - loc1++; - } - else { - interval_values_joined[key1] = std::make_pair(top_interval_mock, width2); - loc2++; - } - } - else if (key1 > key2) { - if (key2+width2 > key1+width1) { - interval_values_joined[key1] = std::make_pair(top_interval_mock, width1); - loc1++; - } - else if (key2+width2 > key1) { - interval_values_joined[key1] = std::make_pair(top_interval_mock, key2+width2-key1); - loc2++; - } - else loc2++; - } - else { - join_stack(stack2, key2, loc2, stack1, key1, loc1, interval_values_joined); - } -} - -stack_cp_state_t stack_cp_state_t::operator|(const stack_cp_state_t& other) const { - if (is_bottom() || other.is_top()) { - return other; - } else if (other.is_bottom() || is_top()) { - return *this; - } - interval_values_stack_t interval_values_joined; - auto stack1_keys = get_keys(); - auto stack2_keys = other.get_keys(); - int i = 0, j = 0; - while (i < static_cast(stack1_keys.size()) && j < static_cast(stack2_keys.size())) { - int key1 = stack1_keys[i], key2 = stack2_keys[j]; - join_stack(*this, key1, i, other, key2, j, interval_values_joined); - } - return stack_cp_state_t(std::move(interval_values_joined)); -} - -size_t stack_cp_state_t::size() const { - return m_interval_values.size(); -} - -std::vector stack_cp_state_t::get_keys() const { - std::vector keys; - keys.reserve(size()); - - for (auto const&kv : m_interval_values) { - keys.push_back(kv.first); - } - return keys; -} - -bool interval_prop_domain_t::is_bottom() const { - if (m_is_bottom) return true; - return (m_registers_interval_values.is_bottom() || m_stack_slots_interval_values.is_bottom()); -} - -bool interval_prop_domain_t::is_top() const { - if (m_is_bottom) return false; - return (m_registers_interval_values.is_top() && m_stack_slots_interval_values.is_top()); -} - -interval_prop_domain_t interval_prop_domain_t::bottom() { - interval_prop_domain_t cp; - cp.set_to_bottom(); - return cp; -} - -void interval_prop_domain_t::set_to_bottom() { - m_is_bottom = true; -} - -void interval_prop_domain_t::set_to_top() { - m_registers_interval_values.set_to_top(); - m_stack_slots_interval_values.set_to_top(); -} - -std::optional interval_prop_domain_t::find_interval_value(register_t reg) const { - return m_registers_interval_values.find(reg); -} - -std::optional interval_prop_domain_t::find_interval_at_loc( - const reg_with_loc_t reg) const { - return m_registers_interval_values.find(reg); -} - -void interval_prop_domain_t::insert_in_registers(register_t reg, location_t loc, - interval_t interval) { - m_registers_interval_values.insert(reg, loc, interval); -} - -void interval_prop_domain_t::store_in_stack(uint64_t key, mock_interval_t interval, int width) { - m_stack_slots_interval_values.store(key, interval, width); -} - -bool interval_prop_domain_t::operator<=(const interval_prop_domain_t& abs) const { - /* WARNING: The operation is not implemented yet.*/ - return true; -} - -void interval_prop_domain_t::operator|=(const interval_prop_domain_t& abs) { - interval_prop_domain_t tmp{abs}; - operator|=(std::move(tmp)); -} - -void interval_prop_domain_t::operator|=(interval_prop_domain_t&& abs) { - if (is_bottom()) { - *this = abs; - return; - } - *this = *this | std::move(abs); -} - -interval_prop_domain_t interval_prop_domain_t::operator|(const interval_prop_domain_t& other) const { - if (is_bottom() || other.is_top()) { - return other; - } - else if (other.is_bottom() || is_top()) { - return *this; - } - return interval_prop_domain_t(m_registers_interval_values | other.m_registers_interval_values, - m_stack_slots_interval_values | other.m_stack_slots_interval_values); -} - -interval_prop_domain_t interval_prop_domain_t::operator|(interval_prop_domain_t&& other) const { - if (is_bottom() || other.is_top()) { - return std::move(other); - } - else if (other.is_bottom() || is_top()) { - return *this; - } - return interval_prop_domain_t( - m_registers_interval_values | std::move(other.m_registers_interval_values), - m_stack_slots_interval_values | std::move(other.m_stack_slots_interval_values)); -} - -interval_prop_domain_t interval_prop_domain_t::operator&(const interval_prop_domain_t& abs) const { - /* WARNING: The operation is not implemented yet.*/ - return abs; -} - -interval_prop_domain_t interval_prop_domain_t::widen(const interval_prop_domain_t& abs, bool to_constants) { - /* WARNING: The operation is not implemented yet.*/ - return abs; -} - -interval_prop_domain_t interval_prop_domain_t::narrow(const interval_prop_domain_t& other) const { - /* WARNING: The operation is not implemented yet.*/ - return other; -} - -void interval_prop_domain_t::write(std::ostream& os) const {} - -crab::bound_t interval_prop_domain_t::get_loop_count_upper_bound() { - /* WARNING: The operation is not implemented yet.*/ - return crab::bound_t{crab::number_t{0}}; -} - -void interval_prop_domain_t::initialize_loop_counter(const label_t& label) { - /* WARNING: The operation is not implemented yet.*/ -} - -string_invariant interval_prop_domain_t::to_set() { - return string_invariant{}; -} - -interval_prop_domain_t interval_prop_domain_t::setup_entry() { - registers_cp_state_t registers(std::make_shared()); - interval_prop_domain_t cp(std::move(registers), stack_cp_state_t::top()); - return cp; -} - -void interval_prop_domain_t::operator()(const Un& u, location_t loc) { - auto swap_endianness = [&](interval_t&& v, auto input, const auto& be_or_le) { - if (std::optional n = v.singleton()) { - if (n->fits_cast_to_int64()) { - input = (decltype(input))n.value().cast_to_sint64(); - decltype(input) output = be_or_le(input); - m_registers_interval_values.insert(u.dst.v, loc, interval_t{number_t{output}}); - return; - } - } - m_registers_interval_values.insert(u.dst.v, loc, interval_t::top()); - }; - - auto mock_interval = m_registers_interval_values.find(u.dst.v); - if (!mock_interval) return; - interval_t interval = mock_interval->to_interval(); - - // Swap bytes. For 64-bit types we need the weights to fit in a - // signed int64, but for smaller types we don't want sign extension, - // so we use unsigned which still fits in a signed int64. - switch (u.op) { - case Un::Op::BE16: - swap_endianness(std::move(interval), uint16_t(0), - boost::endian::native_to_big); - break; - case Un::Op::BE32: - swap_endianness(std::move(interval), uint32_t(0), - boost::endian::native_to_big); - break; - case Un::Op::BE64: - swap_endianness(std::move(interval), int64_t(0), - boost::endian::native_to_big); - break; - case Un::Op::LE16: - swap_endianness(std::move(interval), uint16_t(0), - boost::endian::native_to_little); - break; - case Un::Op::LE32: - swap_endianness(std::move(interval), uint32_t(0), - boost::endian::native_to_little); - break; - case Un::Op::LE64: - swap_endianness(std::move(interval), int64_t(0), - boost::endian::native_to_little); - break; - case Un::Op::NEG: - auto reg_with_loc = reg_with_loc_t(u.dst.v, loc); - m_registers_interval_values.insert(u.dst.v, loc, -interval); - break; - } -} - -void interval_prop_domain_t::operator()(const LoadMapFd &u, location_t loc) { - m_registers_interval_values -= u.dst.v; -} - -void interval_prop_domain_t::operator()(const ValidSize& s, location_t loc) { - // nothing to do here -} - -void interval_prop_domain_t::do_call(const Call& u, const stack_cells_t& store_in_stack, - location_t loc) { - - for (const auto& kv : store_in_stack) { - auto offset = kv.first; - auto width = kv.second; - auto overlapping_cells - = m_stack_slots_interval_values.find_overlapping_cells(offset, width); - m_stack_slots_interval_values.remove_overlap(overlapping_cells, offset, width); - m_stack_slots_interval_values.store(offset, interval_t::top(), width); - } - auto r0 = register_t{R0_RETURN_VALUE}; - if (u.is_map_lookup) { - m_registers_interval_values -= r0; - } - else { - m_registers_interval_values.insert(r0, loc, interval_t::top()); - } - m_registers_interval_values.scratch_caller_saved_registers(); -} - -void interval_prop_domain_t::operator()(const Packet& u, location_t loc) { - auto r0 = register_t{R0_RETURN_VALUE}; - m_registers_interval_values.insert(r0, loc, interval_t::top()); - m_registers_interval_values.scratch_caller_saved_registers(); -} - -void interval_prop_domain_t::assume_lt(bool strict, - interval_t&& left_interval, interval_t&& right_interval, - interval_t&& left_interval_orig, interval_t&& right_interval_orig, - register_t left, Value right, location_t loc, bool is_signed) { - auto rlb = right_interval.lb(); - auto rub = right_interval.ub(); - auto llb = left_interval.lb(); - auto lub = left_interval.ub(); - auto rlb_orig = right_interval_orig.lb(); - auto rub_orig = right_interval_orig.ub(); - auto llb_orig = left_interval_orig.lb(); - auto lub_orig = left_interval_orig.ub(); - - if (strict ? llb < rlb : llb <= rlb && lub >= rlb) { - auto lb = is_signed ? llb_orig : number_t{0}; - auto interval_to_insert = interval_t(lb, strict ? rlb - number_t{1} : rlb); - m_registers_interval_values.insert(left, loc, interval_to_insert); - } - else if (left_interval <= right_interval && strict ? lub < rub : lub <= rub && - std::holds_alternative(right)) { - auto right_reg = std::get(right).v; - auto interval_to_insert = interval_t(strict ? lub + number_t{1} : lub, rub_orig); - m_registers_interval_values.insert(right_reg, loc, interval_to_insert); - } - else if (lub >= rub && strict ? llb < rub : llb <= rub) { - auto lb = is_signed ? llb_orig : number_t{0}; - auto interval_to_insert_left = interval_t(lb, strict ? rub - number_t{1} : rub); - m_registers_interval_values.insert(left, loc, interval_to_insert_left); - // this is only one way to resolve this scenario, i.e. set right to singleton value (rub) - // and set left to the rest of the interval < (or <=) of right - // a more sound analysis is needed - if (std::holds_alternative(right)) { - auto right_reg = std::get(right).v; - m_registers_interval_values.insert(right_reg, loc, interval_t{rub}); - } - } - else { - // TODO: verify if any legitimate case can fall into here - m_registers_interval_values.set_to_bottom(); - } -} - -void interval_prop_domain_t::assume_gt(bool strict, - interval_t&& left_interval, interval_t&& right_interval, - interval_t&& left_interval_orig, interval_t&& right_interval_orig, - register_t left, Value right, location_t loc) { - auto rlb = right_interval.lb(); - auto rub = right_interval.ub(); - auto llb = left_interval.lb(); - auto lub = left_interval.ub(); - auto rlb_orig = right_interval_orig.lb(); - auto rub_orig = right_interval_orig.ub(); - auto llb_orig = left_interval_orig.lb(); - auto lub_orig = left_interval_orig.ub(); - - if (strict ? lub > rub : lub >= rub && llb <= rub) { - auto interval_to_insert = interval_t(strict ? rub + number_t{1} : rub, lub_orig); - m_registers_interval_values.insert(left, loc, interval_to_insert); - } - else if (left_interval <= right_interval && strict ? llb > rlb : llb >= rlb && - std::holds_alternative(right)) { - auto right_reg = std::get(right).v; - auto interval_to_insert = interval_t(rlb_orig, strict ? llb - number_t{1} : llb); - m_registers_interval_values.insert(right_reg, loc, interval_to_insert); - } - else if (llb <= rlb && strict ? lub > rlb : lub >= rlb) { - auto interval_to_insert_left = interval_t(strict ? rlb + number_t{1} : rlb, lub_orig); - m_registers_interval_values.insert(left, loc, interval_to_insert_left); - // this is only one way to resolve this scenario, i.e. set right to singleton value (rlb) - // and set left to the rest of the interval > (or >=) of right - // a more sound analysis is needed - if (std::holds_alternative(right)) { - auto right_reg = std::get(right).v; - m_registers_interval_values.insert(right_reg, loc, interval_t{rlb}); - } - } - else { - // TODO: verify if any legitimate case can fall into here - m_registers_interval_values.set_to_bottom(); - } -} - -void interval_prop_domain_t::assume_gt_and_lt(bool is64, bool strict, bool is_lt, - interval_t&& left_interval, interval_t&& right_interval, - interval_t&& left_interval_orig, interval_t&& right_interval_orig, - register_t left, Value right, location_t loc, bool is_signed) { - - auto llb = left_interval.lb(); - auto lub = left_interval.ub(); - auto rlb = right_interval.lb(); - auto rub = right_interval.ub(); - if (!is_lt && (strict ? (lub <= rlb) : (lub < rlb))) { - // Left unsigned interval is lower than right unsigned interval. - m_registers_interval_values.set_to_bottom(); - return; - } else if (is_lt && (strict ? (llb >= rub) : (llb > rub))) { - // Left unsigned interval is higher than right unsigned interval. - m_registers_interval_values.set_to_bottom(); - return; - } - if (is_lt && (strict ? (lub < rlb) : (lub <= rlb))) { - // Left unsigned interval is lower than right unsigned interval. - // TODO: verify if setting to top is the correct equivalent of returning linear cst true - m_registers_interval_values.set_to_top(); - return; - } else if (!is_lt && (strict ? (llb > rub) : (llb >= rub))) { - // Left unsigned interval is higher than right unsigned interval. - m_registers_interval_values.set_to_top(); - return; - } else if (left_interval_orig.is_top() && right_interval_orig.is_top()) { - m_registers_interval_values.insert(left, loc, interval_t::top()); - if (std::holds_alternative(right)) { - auto right_reg = std::get(right).v; - m_registers_interval_values.insert(right_reg, loc, interval_t::top()); - } - return; - } - - if (is_lt) - assume_lt(strict, std::move(left_interval), std::move(right_interval), - std::move(left_interval_orig), std::move(right_interval_orig), left, right, loc, - is_signed); - else - assume_gt(strict, std::move(left_interval), std::move(right_interval), - std::move(left_interval_orig), std::move(right_interval_orig), left, right, loc); -} - -void interval_prop_domain_t::assume_unsigned_cst_interval(Condition::Op op, bool is64, - interval_t&& left_interval, interval_t&& right_interval, - interval_t&& left_interval_orig, interval_t&& right_interval_orig, - register_t left, Value right, location_t loc) { - - for (interval_t* interval : {&left_interval, &right_interval}) { - if (!(*interval <= interval_t::unsigned_int(is64))) { - *interval = interval->truncate_to_uint(is64); - } - } - - // Handle uvalue != right. - if (op == Condition::Op::NE) { - if (auto rn = right_interval.singleton()) { - if (rn == left_interval.truncate_to_uint(is64).lb().number()) { - // "NE lower bound" is equivalent to "GT lower bound". - op = Condition::Op::GT; - right_interval = interval_t{left_interval.lb()}; - } else if (rn == left_interval.ub().number()) { - // "NE upper bound" is equivalent to "LT upper bound". - op = Condition::Op::LT; - right_interval = interval_t{left_interval.ub()}; - } else { - return; - } - } else { - return; - } - } - - const bool is_lt = op == Condition::Op::LT || op == Condition::Op::LE; - bool strict = op == Condition::Op::LT || op == Condition::Op::GT; - - assume_gt_and_lt(is64, strict, is_lt, std::move(left_interval), std::move(right_interval), - std::move(left_interval_orig), std::move(right_interval_orig), left, right, loc, - false); -} - -void interval_prop_domain_t::assume_signed_cst_interval(Condition::Op op, bool is64, - interval_t&& left_interval, interval_t&& right_interval, - interval_t&& left_interval_orig, interval_t&& right_interval_orig, - register_t left, Value right, location_t loc) { - - for (interval_t* interval : {&left_interval, &right_interval}) { - if (!(*interval <= interval_t::signed_int(is64))) { - *interval = interval->truncate_to_sint(is64); - } - } - - if (op == Condition::Op::EQ) { - auto eq_interval = right_interval & left_interval; - m_registers_interval_values.insert(left, loc, eq_interval); - if (std::holds_alternative(right)) { - auto right_reg = std::get(right).v; - m_registers_interval_values.insert(right_reg, loc, eq_interval); - } - return; - } - - const bool is_lt = op == Condition::Op::SLT || op == Condition::Op::SLE; - bool strict = op == Condition::Op::SLT || op == Condition::Op::SGT; - - assume_gt_and_lt(is64, strict, is_lt, std::move(left_interval), std::move(right_interval), - std::move(left_interval_orig), std::move(right_interval_orig), - left, right, loc); -} - -void interval_prop_domain_t::assume_cst(Condition::Op op, bool is64, register_t left, - Value right, interval_t&& left_interval, interval_t&& right_interval, location_t loc) { - using Op = Condition::Op; - interval_t left_interval_orig = left_interval; - interval_t right_interval_orig = right_interval; - - switch (op) { - case Op::EQ: - case Op::SGE: - case Op::SLE: - case Op::SGT: - case Op::SLT: { - assume_signed_cst_interval(op, is64, std::move(left_interval), - std::move(right_interval), std::move(left_interval_orig), - std::move(right_interval_orig), left, right, loc); - break; - } - case Op::SET: - case Op::NSET: { - // TODO: implement SET and NSET - break; - } - case Op::NE: - case Op::GE: - case Op::LE: - case Op::GT: - case Op::LT: { - assume_unsigned_cst_interval(op, is64, std::move(left_interval), - std::move(right_interval), std::move(left_interval_orig), - std::move(right_interval_orig), left, right, loc); - break; - } - } -} - -void interval_prop_domain_t::operator()(const Assume& s, location_t loc) { - // nothing to do here -} - -void interval_prop_domain_t::do_bin(const Bin& bin, - const std::optional& src_interval_opt, - const std::optional& dst_interval_opt, - const std::optional& src_ptr_or_mapfd_opt, - const std::optional& dst_ptr_or_mapfd_opt, - const interval_t& subtract_interval, location_t loc) { - using Op = Bin::Op; - // if op is not MOV, - // we skip handling in this domain is when dst is pointer and src is numerical value - if (bin.op != Op::MOV && dst_ptr_or_mapfd_opt && src_interval_opt) return; - // if op is MOV, - // we skip handling in this domain is when src is pointer, - // additionally, we forget the dst pointer - - auto dst_register = register_t{bin.dst.v}; - if (bin.op == Op::MOV && src_ptr_or_mapfd_opt) { - m_registers_interval_values -= dst_register; - return; - } - - int finite_width = bin.is64 ? 64 : 32; - uint64_t imm = std::holds_alternative(bin.v) ? std::get(bin.v).v : 0; - interval_t src_interval = interval_t::bottom(), dst_interval = interval_t::bottom(); - if (src_interval_opt) src_interval = std::move(src_interval_opt.value()); - if (dst_interval_opt) dst_interval = std::move(dst_interval_opt.value()); - - switch (bin.op) - { - // ra = b - case Op::MOV: { - if (src_interval_opt) { - dst_interval = src_interval; - } - else { - m_registers_interval_values -= dst_register; - return; - } - break; - } - // ra += b - case Op::ADD: { - if (dst_ptr_or_mapfd_opt && src_ptr_or_mapfd_opt) return; - if (dst_interval_opt && src_interval_opt) - dst_interval += src_interval; - else if (dst_interval_opt && src_ptr_or_mapfd_opt) { - m_registers_interval_values -= dst_register; - return; - } - break; - } - // ra -= b - case Op::SUB: { - if (dst_ptr_or_mapfd_opt && src_ptr_or_mapfd_opt) - dst_interval = subtract_interval; - else if (dst_interval_opt && src_interval_opt) - dst_interval -= src_interval; - else if (dst_interval_opt && src_ptr_or_mapfd_opt) { - m_registers_interval_values -= dst_register; - return; - } - break; - } - // ra *= b - case Op::MUL: { - dst_interval *= src_interval; - break; - } - // ra /= b - case Op::UDIV: { - dst_interval /= src_interval; - break; - } - // ra %= b - case Op::UMOD: { - dst_interval = dst_interval.SRem(src_interval); - break; - } - // ra |= b - case Op::OR: { - dst_interval = dst_interval.Or(src_interval); - break; - } - // ra &= b - case Op::AND: { - if ((int32_t)imm > 0) { - dst_interval = interval_t(number_t{0}, number_t(static_cast(imm))); - break; - } - if (!(dst_interval <= interval_t::unsigned_int(bin.is64))) { - // TODO: This is only based on observation, need to verify this - // This is done because we do not track uvalues and svalues separately - // however, current implementation of eBPF using zoneCrab does - // this operation mimics uvalue being not set (i.e., TOP) - dst_interval = interval_t::top(); - } - if (!(src_interval <= interval_t::unsigned_int(bin.is64))) { - // likewise explanation as above - src_interval = interval_t::top(); - } - if (imm != 0) { - src_interval = interval_t(number_t{(uint64_t)imm}); - } - dst_interval = dst_interval.And(src_interval); - break; - } - // ra <<= b - case Op::LSH: { - if (imm == 0) { - if (std::optional sn = src_interval.singleton()) { - // TODO: verify if this is correct - if (bin.is64) { - uint64_t imm_val = sn->cast_to_uint64() & 63; - if (!(imm_val <= INT32_MAX)) return; - imm = (int32_t)imm_val; - } - else { - uint32_t imm_val = sn->cast_to_uint32() & 31; - if (!(imm_val <= INT32_MAX)) return; - imm = (int32_t)imm_val; - } - } - } - if (imm != 0) { - // The BPF ISA requires masking the imm. - imm &= finite_width - 1; - if (!(dst_interval <= interval_t::unsigned_int(bin.is64))) { - // This is non-standard, but done to mimic uvalue being not set (i.e., TOP) - dst_interval = interval_t::top(); - } - if (dst_interval.finite_size()) { - number_t lb = dst_interval.lb().number().value(); - number_t ub = dst_interval.ub().number().value(); - uint64_t lb_n = lb.cast_to_uint64(); - uint64_t ub_n = ub.cast_to_uint64(); - uint64_t uint_max = (finite_width == 64) ? UINT64_MAX : UINT32_MAX; - if ((lb_n >> (finite_width - imm)) != (ub_n >> (finite_width - imm))) { - // The bits that will be shifted out to the left are different, - // which means all combinations of remaining bits are possible. - lb_n = 0; - ub_n = (uint_max << imm) & uint_max; - } else { - // The bits that will be shifted out to the left are identical - // for all values in the interval, so we can safely shift left - // to get a new interval. - lb_n = (lb_n << imm) & uint_max; - ub_n = (ub_n << imm) & uint_max; - } - dst_interval = interval_t(number_t{lb_n}, number_t{ub_n}); - } - } - else { - dst_interval = interval_t::top(); - } - break; - } - // ra >>= b - case Op::RSH: { - if (imm == 0) { - if (std::optional sn = src_interval.singleton()) { - // TODO: verify if this is correct - if (bin.is64) { - uint64_t imm_val = sn->cast_to_uint64() & 63; - if (!(imm_val <= INT32_MAX)) return; - imm = (int32_t)imm_val; - } - else { - uint32_t imm_val = sn->cast_to_uint32() & 31; - if (!(imm_val <= INT32_MAX)) return; - imm = (int32_t)imm_val; - } - } - } - if (imm != 0) { - imm &= finite_width - 1; - number_t lb_n{0}; - number_t ub_n{UINT64_MAX >> imm}; - if (!(dst_interval <= interval_t::unsigned_int(bin.is64))) { - // This is done because we do not track uvalues and svalues separately - // however, current implementation of eBPF using zoneCrab does - // this operation mimics uvalue being not set (i.e., TOP) - dst_interval = interval_t::top(); - } - if (dst_interval.finite_size()) { - number_t lb = dst_interval.lb().number().value(); - number_t ub = dst_interval.ub().number().value(); - if (finite_width == 64) { - lb_n = lb.cast_to_uint64() >> imm; - ub_n = ub.cast_to_uint64() >> imm; - } else { - number_t lb_w = lb.cast_to_signed_finite_width(finite_width); - number_t ub_w = ub.cast_to_signed_finite_width(finite_width); - lb_n = lb_w.cast_to_uint32() >> imm; - ub_n = ub_w.cast_to_uint32() >> imm; - - // The interval must be valid since a signed range crossing 0 - // was earlier converted to a full unsigned range. - assert(lb_n <= ub_n); - } - } - if ((int64_t)ub_n >= (int64_t)lb_n) { - dst_interval = interval_t(number_t{lb_n}, number_t{ub_n}); - } else { - dst_interval = interval_t::top(); - } - } - else { - dst_interval = interval_t::top(); - } - break; - } - // ra >>>= b - case Op::ARSH: { - for (interval_t* interval : {&dst_interval, &src_interval}) { - if (!(*interval <= interval_t::signed_int(finite_width == 64))) { - *interval = interval->truncate_to_sint(finite_width == 64); - } - } - - if (auto sn = src_interval.singleton()) { - // The BPF ISA requires masking the imm. - int64_t imm = sn->cast_to_sint64() & (finite_width - 1); - - int64_t lb_n = INT64_MIN >> imm; - int64_t ub_n = INT64_MAX >> imm; - if (dst_interval.finite_size()) { - number_t lb = dst_interval.lb().number().value(); - number_t ub = dst_interval.ub().number().value(); - if (finite_width == 64) { - lb_n = lb.cast_to_sint64() >> imm; - ub_n = ub.cast_to_sint64() >> imm; - } else { - number_t lb_w = lb.cast_to_signed_finite_width(finite_width) >> (int)imm; - number_t ub_w = ub.cast_to_signed_finite_width(finite_width) >> (int)imm; - if (lb_w.cast_to_uint32() <= ub_w.cast_to_uint32()) { - lb_n = lb_w.cast_to_uint32(); - ub_n = ub_w.cast_to_uint32(); - } - } - } - dst_interval = interval_t(number_t{lb_n}, number_t{ub_n}); - } - else { - dst_interval = interval_t::top(); - } - break; - } - // ra ^= b - case Op::XOR: { - dst_interval = dst_interval.Xor(src_interval); - break; - } - default: { - dst_interval = interval_t::bottom(); - break; - } - } - m_registers_interval_values.insert(dst_register, loc, dst_interval); -} - - -void interval_prop_domain_t::do_load(const Mem& b, const register_t& target_register, - std::optional basereg_type, bool load_in_region, location_t loc) { - - if (!basereg_type) { - m_registers_interval_values -= target_register; - return; - } - - // we check if we already loaded a pointer from ctx or stack in region domain, - // we then do not store a number - if (load_in_region) { - m_registers_interval_values -= target_register; - return; - } - int width = b.access.width; - int offset = b.access.offset; - auto basereg_ptr_or_mapfd_type = basereg_type.value(); - - if (is_ctx_ptr(basereg_type)) { - m_registers_interval_values.insert(target_register, loc, interval_t::top()); - return; - } - if (is_packet_ptr(basereg_type) || is_shared_ptr(basereg_type)) { - interval_t to_insert = interval_t::top(); - if (width == 1) to_insert = interval_t(number_t{0}, number_t{UINT8_MAX}); - else if (width == 2) to_insert = interval_t(number_t{0}, number_t{UINT16_MAX}); - m_registers_interval_values.insert(target_register, loc, to_insert); - return; - } - - if (is_stack_ptr(basereg_type)) { - auto ptr_with_off = std::get(basereg_ptr_or_mapfd_type); - auto p_offset = ptr_with_off.get_offset(); - auto load_at = p_offset.to_interval() + interval_t(number_t{offset}); - uint64_t start_offset = 0; - if (auto load_at_singleton = load_at.singleton()) { - start_offset = (uint64_t)(*load_at_singleton); - if (auto loaded = m_stack_slots_interval_values.find(start_offset)) { - m_registers_interval_values.insert(target_register, loc, - (*loaded).first.to_interval()); - return; - } - } - else { - auto load_at_lb = load_at.lb(); - auto load_at_ub = load_at.ub(); - if (auto finite_size = load_at.finite_size()) { - if (auto load_at_lb = load_at.lb().number()) { - start_offset = (uint64_t)(*load_at_lb); - width = (int)(*finite_size + number_t{width}); - } - } - } - if (m_stack_slots_interval_values.all_numeric(start_offset, width)) { - m_registers_interval_values.insert(target_register, loc, interval_t::top()); - return; - } - } - m_registers_interval_values -= target_register; -} - -void interval_prop_domain_t::do_mem_store(const Mem& b, - std::optional basereg_type) { - int offset = b.access.offset; - int width = b.access.width; - - if (!is_stack_ptr(basereg_type)) { - // we only store for stack pointers - return; - } - - auto basereg_ptr_with_off_type = std::get(*basereg_type); - auto offset_singleton = basereg_ptr_with_off_type.get_offset().to_interval().singleton(); - if (!offset_singleton) { - //std::cout << "type error: doing a store with unknown offset\n"; - m_errors.push_back("doing a store with unknown offset"); - return; - } - auto store_at = (uint64_t)(*offset_singleton + offset); - auto overlapping_cells = m_stack_slots_interval_values.find_overlapping_cells(store_at, width); - m_stack_slots_interval_values.remove_overlap(overlapping_cells, store_at, width); - - std::optional targetreg_type; - if (std::holds_alternative(b.value)) { - auto target_reg = std::get(b.value); - if (auto targetreg_mock_interval = m_registers_interval_values.find(target_reg.v)) { - targetreg_type = targetreg_mock_interval->to_interval(); - } - } - else { - auto imm = static_cast(std::get(b.value).v); - targetreg_type = interval_t(number_t{imm}); - } - - // if targetreg_type is empty, we are storing a pointer - // else, we store a number - if (targetreg_type) - m_stack_slots_interval_values.store(store_at, *targetreg_type, width); -} - -void interval_prop_domain_t::check_valid_access(const ValidAccess& s, interval_t&& interval, - int width, bool check_stack_all_numeric) { - - if (check_stack_all_numeric) { - auto start_interval = interval + interval_t{number_t{s.offset}}; - if (auto finite_size = start_interval.finite_size()) { - if (auto start_interval_lb = start_interval.lb().number()) { - auto start_offset = (uint64_t)(*start_interval_lb); - int width_from_start = (int)(*finite_size) + width; - if (!m_stack_slots_interval_values.all_numeric(start_offset, width_from_start)) { - m_errors.push_back("Stack access not numeric"); - } - } - else { - m_errors.push_back("Offset information not available"); - } - } - else { - m_errors.push_back("Register interval not finite for stack access"); - } - } - else { - bool is_comparison_check = s.width == (Value)Imm{0}; - if (!is_comparison_check) { - if (s.or_null) { - if (auto singleton = interval.singleton()) { - if (*singleton == number_t{0}) return; - } - m_errors.push_back("Non-null number"); - } - else { - m_errors.push_back("Only pointers can be dereferenced"); - } - } - } -} - -void interval_prop_domain_t::operator()(const ValidAccess& s, location_t loc) { - // nothing to do here -} - -void interval_prop_domain_t::operator()(const Undefined& u, location_t loc) { - // nothing to do here -} - -void interval_prop_domain_t::operator()(const Bin& b, location_t loc) { - // nothing to do here -} - -void interval_prop_domain_t::operator()(const Call&, location_t loc) { - // nothing to do here -} - -void interval_prop_domain_t::operator()(const Exit&, location_t loc) { - // nothing to do here -} - -void interval_prop_domain_t::operator()(const Jmp&, location_t loc) { - // nothing to do here -} - -void interval_prop_domain_t::operator()(const Mem&, location_t loc) { - // nothing to do here -} - -void interval_prop_domain_t::operator()(const Assert&, location_t loc) { - // nothing to do here -} - -void interval_prop_domain_t::operator()(const Comparable&, location_t loc) { - // nothing to do here -} - -void interval_prop_domain_t::operator()(const Addable&, location_t loc) { - // nothing to do here -} - -void interval_prop_domain_t::operator()(const ValidStore&, location_t loc) { - // nothing to do here -} - -void interval_prop_domain_t::operator()(const TypeConstraint&, location_t loc) { - // nothing to do here -} - -void interval_prop_domain_t::operator()(const ValidMapKeyValue&, location_t loc) { - // nothing to do here -} - -void interval_prop_domain_t::operator()(const ZeroCtxOffset&, location_t loc) { - // nothing to do here -} - -void interval_prop_domain_t::operator()(const basic_block_t& bb, int print) { - // nothing to do here -} -void interval_prop_domain_t::set_require_check(check_require_func_t f) {} - -std::optional interval_prop_domain_t::find_in_stack(uint64_t key) const { - return m_stack_slots_interval_values.find(key); -} - -void interval_prop_domain_t::adjust_bb_for_types(location_t loc) { - m_registers_interval_values.adjust_bb_for_registers(loc); -} - -std::vector interval_prop_domain_t::get_stack_keys() const { - return m_stack_slots_interval_values.get_keys(); -} - -bool interval_prop_domain_t::all_numeric_in_stack(uint64_t start_loc, int width) const { - return m_stack_slots_interval_values.all_numeric(start_loc, width); -} - -std::vector interval_prop_domain_t::find_overlapping_cells_in_stack(uint64_t start_loc, - int width) const { - return m_stack_slots_interval_values.find_overlapping_cells(start_loc, width); -} - -void interval_prop_domain_t::remove_overlap_in_stack(const std::vector& overlap, - uint64_t start_loc, int width) { - m_stack_slots_interval_values.remove_overlap(overlap, start_loc, width); -} - -} // namespace crab diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index 9a03bfd1e..f9e88229a 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -601,141 +601,158 @@ void offset_domain_t::operator()(const Assume &b, location_t loc) { else {} //we do not need to deal with other cases } -void offset_domain_t::update_offset_info(const dist_t&& dist, const interval_t&& change, - const location_t& loc, uint8_t reg, Bin::Op op) { +void offset_domain_t::update_offset_info(dist_t&& dist, interval_t&& change, + const location_t& loc, register_t reg) { auto offset = dist.m_dist; - if (op == Bin::Op::ADD) { - if (dist.is_forward_pointer()) offset += change; - else if (dist.is_backward_pointer()) offset -= change; - else offset -= change; - } - else if (op == Bin::Op::SUB) { - // TODO: needs precise handling of subtraction - offset = interval_t::top(); - } - m_reg_state.insert(reg, loc, dist_t(offset)); + if (dist.is_forward_pointer()) offset += change; + else if (dist.is_backward_pointer()) offset -= change; + else offset -= change; + m_reg_state.insert(reg, loc, dist_t{offset}); } -interval_t offset_domain_t::do_bin(const Bin &bin, - const std::optional& src_interval_opt, - const std::optional& dst_interval_opt, - std::optional& src_ptr_or_mapfd_opt, - std::optional& dst_ptr_or_mapfd_opt, location_t loc) { +interval_t offset_domain_t::do_bin(const Bin& bin, + const std::optional& src_signed_interval_opt, + const std::optional& src_ptr_or_mapfd_opt, + const std::optional& dst_signed_interval_opt, + const std::optional& dst_ptr_or_mapfd_opt, location_t loc) { - using Op = Bin::Op; - // if both src and dst are numbers, nothing to do in offset domain - // if we are doing a move, where src is a number and dst is not set, nothing to do - if ((dst_interval_opt && src_interval_opt) - || (src_interval_opt && !dst_ptr_or_mapfd_opt && bin.op == Op::MOV)) - return interval_t::bottom(); // offset domain only handles packet pointers if (!is_packet_ptr(src_ptr_or_mapfd_opt) && !is_packet_ptr(dst_ptr_or_mapfd_opt)) return interval_t::bottom(); - interval_t src_interval = interval_t::bottom(), dst_interval = interval_t::bottom(); - if (src_interval_opt) src_interval = std::move(src_interval_opt.value()); - if (dst_interval_opt) dst_interval = std::move(dst_interval_opt.value()); - - Reg src; - if (std::holds_alternative(bin.v)) src = std::get(bin.v); + using Op = Bin::Op; auto dst_register = register_t{bin.dst.v}; - switch (bin.op) - { - // ra = rb; - case Op::MOV: { - if (!is_packet_ptr(src_ptr_or_mapfd_opt)) { - m_reg_state -= dst_register; - return interval_t::bottom(); - } - auto src_offset_opt = m_reg_state.find(src.v); - if (!src_offset_opt) { - m_errors.push_back("src is a packet_pointer and no offset info found"); - //std::cout << "type_error: src is a packet_pointer and no offset info found\n"; - return interval_t::bottom(); - } - m_reg_state.insert(dst_register, loc, std::move(*src_offset_opt)); - break; + + if (std::holds_alternative(bin.v)) { + int64_t imm; + if (bin.is64) { + // Use the full signed value. + imm = static_cast(std::get(bin.v).v); + } else { + // Use only the low 32 bits of the value. + imm = static_cast(std::get(bin.v).v); } - // ra += rb - case Op::ADD: { - dist_t dist_to_update; - interval_t interval_to_add = interval_t::bottom(); - if (is_packet_ptr(dst_ptr_or_mapfd_opt) - && is_packet_ptr(src_ptr_or_mapfd_opt)) { + auto imm_interval = interval_t{number_t{imm}}; + switch (bin.op) { + case Op::MOV: { + // ra = imm, we forget the type in the offset domain m_reg_state -= dst_register; - return interval_t::bottom(); + break; } - else if (is_packet_ptr(dst_ptr_or_mapfd_opt) && src_interval_opt) { - auto dst_offset_opt = m_reg_state.find(dst_register); - if (!dst_offset_opt) { - m_errors.push_back("dst is a packet_pointer and no offset info found"); - //std::cout << "type_error: dst is a packet_pointer and no offset info found\n"; - m_reg_state -= dst_register; - return interval_t::bottom(); + case Op::ADD: { + // ra += imm + if (imm == 0) break; + if (is_packet_ptr(dst_ptr_or_mapfd_opt)) { + if (auto dst_offset_opt = m_reg_state.find(dst_register)) { + update_offset_info(std::move(*dst_offset_opt), std::move(imm_interval), + loc, dst_register); + return interval_t::bottom(); + } } - dist_to_update = std::move(dst_offset_opt.value()); - interval_to_add = std::move(src_interval_opt.value()); + m_reg_state -= dst_register; + break; } - // Condition might not be necessary once interval domain is added - else if (is_packet_ptr(src_ptr_or_mapfd_opt) && dst_interval_opt) { - auto src_offset_opt = m_reg_state.find(src.v); - if (!src_offset_opt) { - m_errors.push_back("src is a packet_pointer and no offset info found"); - //std::cout << "type_error: src is a packet_pointer and no offset info found\n"; - m_reg_state -= dst_register; - return interval_t::bottom(); + case Op::SUB: { + // ra -= imm + if (imm == 0) break; + if (is_packet_ptr(dst_ptr_or_mapfd_opt)) { + if (auto dst_offset_opt = m_reg_state.find(dst_register)) { + update_offset_info(std::move(*dst_offset_opt), -std::move(imm_interval), + loc, dst_register); + return interval_t::bottom(); + } } - dist_to_update = std::move(src_offset_opt.value()); - interval_to_add = std::move(dst_interval_opt.value()); + m_reg_state -= dst_register; + break; } - else if (is_packet_ptr(dst_ptr_or_mapfd_opt)) { - // this case is only needed till interval domain is added - m_reg_state.insert(dst_register, loc, dist_t()); + default: { + // no other operations supported for offset domain + m_reg_state -= dst_register; break; } - update_offset_info(std::move(dist_to_update), std::move(interval_to_add), - loc, dst_register, bin.op); - break; } - // ra -= rb - case Op::SUB: { - dist_t dist_to_update; - interval_t interval_to_sub = interval_t::bottom(); - if (is_packet_ptr(dst_ptr_or_mapfd_opt) - && is_packet_ptr(src_ptr_or_mapfd_opt)) { + } + else { + auto src = std::get(bin.v); + switch (bin.op) { + case Op::MOV: { + // ra = rb + if (is_packet_ptr(src_ptr_or_mapfd_opt)) { + if (auto src_offset_opt = m_reg_state.find(src.v)) { + m_reg_state.insert(dst_register, loc, std::move(*src_offset_opt)); + return interval_t::bottom(); + } + } m_reg_state -= dst_register; - return interval_t::top(); + break; } - else if (is_packet_ptr(dst_ptr_or_mapfd_opt) && src_interval_opt) { - auto dst_offset_opt = m_reg_state.find(dst_register); - if (!dst_offset_opt) { - m_errors.push_back("dst is a packet_pointer and no offset info found"); - //std::cout << "type_error: dst is a packet_pointer and no offset info found\n"; - m_reg_state -= dst_register; - return interval_t::bottom(); + case Op::ADD: { + // ra += rb + if (is_packet_ptr(dst_ptr_or_mapfd_opt) && src_signed_interval_opt) { + auto src_signed = std::move(*src_signed_interval_opt); + if (auto dst_offset_opt = m_reg_state.find(dst_register)) { + update_offset_info(std::move(*dst_offset_opt), + std::move(src_signed), loc, dst_register); + return interval_t::bottom(); + } + } + else if (is_packet_ptr(src_ptr_or_mapfd_opt) && dst_signed_interval_opt) { + auto dst_signed = std::move(*dst_signed_interval_opt); + if (auto src_offset_opt = m_reg_state.find(src.v)) { + update_offset_info(std::move(*src_offset_opt), + std::move(dst_signed), loc, dst_register); + return interval_t::bottom(); + } } - dist_to_update = std::move(dst_offset_opt.value()); - interval_to_sub = std::move(src_interval_opt.value()); + else if (dst_signed_interval_opt && src_signed_interval_opt) { + // we do not deal with numbers in offset domain + } + else { + // possibly adding two pointers + set_to_bottom(); + } + m_reg_state -= dst_register; + break; } - else { - auto src_offset_opt = m_reg_state.find(src.v); - if (!src_offset_opt) { - m_errors.push_back("src is a packet_pointer and no offset info found"); - //std::cout << "type_error: src is a packet_pointer and no offset info found\n"; + case Op::SUB: { + // ra -= rb + if (is_packet_ptr(dst_ptr_or_mapfd_opt) && src_signed_interval_opt) { + if (auto dst_offset_opt = m_reg_state.find(dst_register)) { + update_offset_info(std::move(*dst_offset_opt), + -std::move(*src_signed_interval_opt), loc, dst_register); + return interval_t::bottom(); + } + } + else if (is_packet_ptr(src_ptr_or_mapfd_opt) && dst_signed_interval_opt) { m_reg_state -= dst_register; - return interval_t::bottom(); } - dist_to_update = std::move(src_offset_opt.value()); - interval_to_sub = std::move(dst_interval_opt.value()); + else if (dst_signed_interval_opt && src_signed_interval_opt) { + // we do not deal with numbers in region domain + } + else { + // ptr -= ptr + // TODO: be precise with ptr -= ptr, especially for packet end + if (is_packet_ptr(dst_ptr_or_mapfd_opt), is_packet_ptr(src_ptr_or_mapfd_opt)) { + m_reg_state -= dst_register; + return interval_t::top(); + /* + if (auto dst_offset_opt = m_reg_state.find(dst_register)) { + if (auto src_offset_opt = m_reg_state.find(src.v)) { + return (dst_offset_opt->m_dist - src_offset_opt->m_dist); + } + } + */ + } + } + m_reg_state -= dst_register; + break; + } + default: { + // no other operations supported for offset domain + m_reg_state -= dst_register; + break; } - update_offset_info(std::move(dist_to_update), std::move(interval_to_sub), - loc, dst_register, bin.op); - break; - } - default: { - m_reg_state -= dst_register; - break; } } return interval_t::bottom(); diff --git a/src/crab/offset_domain.hpp b/src/crab/offset_domain.hpp index f5754b5aa..afc319890 100644 --- a/src/crab/offset_domain.hpp +++ b/src/crab/offset_domain.hpp @@ -3,10 +3,6 @@ #pragma once -#include - -#include -#include "crab/abstract_domain.hpp" #include "crab/common.hpp" namespace crab { @@ -273,9 +269,9 @@ class offset_domain_t final { void do_load(const Mem&, const register_t&, std::optional, location_t loc); void do_mem_store(const Mem&, std::optional, std::optional&); interval_t do_bin(const Bin&, const std::optional&, + const std::optional&, const std::optional&, - std::optional&, - std::optional&, location_t); + const std::optional&, location_t); void do_call(const Call&, const stack_cells_t&, location_t); bool upper_bound_satisfied(const dist_t&, int, int, bool) const; bool lower_bound_satisfied(const dist_t&, int) const; @@ -286,9 +282,7 @@ class offset_domain_t final { std::optional find_in_stack(int) const; std::optional find_offset_at_loc(const reg_with_loc_t) const; std::optional find_offset_info(register_t reg) const; - void update_offset_info(const dist_t&&, const interval_t&&, const location_t&, - uint8_t, Bin::Op); - dist_t update_offset(const dist_t&, const weight_t&, const interval_t&, Bin::Op); + void update_offset_info(dist_t&&, interval_t&&, const location_t&, register_t); void insert_in_registers(register_t, location_t, dist_t); void store_in_stack(uint64_t, dist_t, int); void adjust_bb_for_types(location_t); diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index 5fe19a038..59a9c4a77 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -894,13 +894,12 @@ void region_domain_t::operator()(const TypeConstraint& s, location_t loc) { m_errors.push_back("type constraint assert fail"); } -void region_domain_t::update_ptr_or_mapfd(ptr_or_mapfd_t&& ptr_or_mapfd, const interval_t&& change, +void region_domain_t::update_ptr_or_mapfd(ptr_or_mapfd_t&& ptr_or_mapfd, interval_t&& change, const location_t& loc, register_t reg) { if (std::holds_alternative(ptr_or_mapfd)) { auto ptr_or_mapfd_with_off = std::get(ptr_or_mapfd); auto offset = ptr_or_mapfd_with_off.get_offset(); - auto updated_offset = - change == mock_interval_t::top() ? offset : offset.to_interval() + change; + auto updated_offset = offset.to_interval() + change; ptr_or_mapfd_with_off.set_offset(updated_offset); m_registers.insert(reg, loc, ptr_or_mapfd_with_off); } @@ -908,7 +907,6 @@ void region_domain_t::update_ptr_or_mapfd(ptr_or_mapfd_t&& ptr_or_mapfd, const i m_registers.insert(reg, loc, ptr_or_mapfd); } else { - //std::cout << "type error: mapfd register cannot be incremented/decremented\n"; m_errors.push_back("mapfd register cannot be incremented/decremented"); m_registers -= reg; } @@ -919,118 +917,155 @@ void region_domain_t::operator()(const Bin& b, location_t loc) { } interval_t region_domain_t::do_bin(const Bin& bin, - const std::optional& src_interval_opt, + const std::optional& src_signed_interval_opt, const std::optional& src_ptr_or_mapfd_opt, + const std::optional& dst_signed_interval_opt, const std::optional& dst_ptr_or_mapfd_opt, location_t loc) { using Op = Bin::Op; - // if we are doing a move, where src is a number and dst is not set, nothing to do - if (src_interval_opt && !dst_ptr_or_mapfd_opt && bin.op == Op::MOV) - return interval_t::bottom(); - ptr_or_mapfd_t src_ptr_or_mapfd, dst_ptr_or_mapfd; - interval_t src_interval = interval_t::bottom(); - if (src_ptr_or_mapfd_opt) src_ptr_or_mapfd = std::move(src_ptr_or_mapfd_opt.value()); - if (dst_ptr_or_mapfd_opt) dst_ptr_or_mapfd = std::move(dst_ptr_or_mapfd_opt.value()); - if (src_interval_opt) src_interval = std::move(src_interval_opt.value()); - - interval_t subtracted = interval_t::bottom(); auto dst_register = register_t{bin.dst.v}; - switch (bin.op) - { - // ra = b, where b is a pointer/mapfd, a numerical register, or a constant; - case Op::MOV: { - // b is a pointer/mapfd - if (src_ptr_or_mapfd_opt) { - if (is_shared_ptr(src_ptr_or_mapfd_opt)) { - auto shared_ptr = std::get(src_ptr_or_mapfd); - set_aliases(dst_register, shared_ptr); - m_registers.insert(dst_register, loc, shared_ptr); + + if (std::holds_alternative(bin.v)) { + int64_t imm; + if (bin.is64) { + // Use the full signed value. + imm = static_cast(std::get(bin.v).v); + } else { + // Use only the low 32 bits of the value. + imm = static_cast(std::get(bin.v).v); + } + auto imm_interval = interval_t{number_t{imm}}; + switch (bin.op) { + case Op::MOV: { + // ra = imm, we forget the type in the region domain + m_registers -= dst_register; + break; + } + case Op::ADD: { + // ra += imm + if (imm == 0) break; + if (dst_ptr_or_mapfd_opt) { + auto dst_ptr_or_mapfd = std::move(*dst_ptr_or_mapfd_opt); + update_ptr_or_mapfd(std::move(dst_ptr_or_mapfd), + std::move(imm_interval), loc, dst_register); + } + else { + m_registers -= dst_register; + } + break; + } + case Op::SUB: { + // ra -= imm + if (imm == 0) break; + if (dst_ptr_or_mapfd_opt) { + auto dst_ptr_or_mapfd = std::move(*dst_ptr_or_mapfd_opt); + update_ptr_or_mapfd(std::move(dst_ptr_or_mapfd), + -std::move(imm_interval), loc, dst_register); } else { - m_registers.insert(dst_register, loc, src_ptr_or_mapfd); + m_registers -= dst_register; } + break; } - // b is a numerical register, or constant - else if (dst_ptr_or_mapfd_opt) { + default: { + // no other operations supported for region domain m_registers -= dst_register; + break; } - break; } - // ra += b, where ra is a pointer/mapfd, or a numerical register, - // b is a pointer/mapfd, a numerical register, or a constant; - case Op::ADD: { - // adding pointer to another - if (src_ptr_or_mapfd_opt && dst_ptr_or_mapfd_opt) { - if (is_stack_ptr(dst_ptr_or_mapfd)) - m_stack.set_to_top(); + } + else { + switch (bin.op) { + case Op::MOV: { + // ra = rb + if (src_ptr_or_mapfd_opt) { + auto src_ptr_or_mapfd = std::move(*src_ptr_or_mapfd_opt); + if (is_shared_ptr(src_ptr_or_mapfd)) { + auto shared_ptr = std::get(src_ptr_or_mapfd); + set_aliases(dst_register, shared_ptr); + m_registers.insert(dst_register, loc, shared_ptr); + } + else { + m_registers.insert(dst_register, loc, *src_ptr_or_mapfd_opt); + } + } else { - // TODO: handle other cases properly - //std::cout << "type error: addition of two pointers\n"; - m_errors.push_back("addition of two pointers"); + m_registers -= dst_register; } - m_registers -= dst_register; + break; } - // ra is a pointer/mapfd - // b is a numerical register, or a constant - else if (dst_ptr_or_mapfd_opt && src_interval_opt) { - update_ptr_or_mapfd(std::move(dst_ptr_or_mapfd), std::move(src_interval), - loc, dst_register); - } - // b is a pointer/mapfd - // ra is a numerical register - else if (src_ptr_or_mapfd_opt && !dst_ptr_or_mapfd_opt) { - update_ptr_or_mapfd(std::move(src_ptr_or_mapfd), interval_t::top(), - loc, dst_register); - } - else if (dst_ptr_or_mapfd_opt && !src_ptr_or_mapfd_opt) { - if (std::holds_alternative(dst_ptr_or_mapfd)) { - auto updated_type = std::get(dst_ptr_or_mapfd); - updated_type.set_offset(mock_interval_t::top()); - m_registers.insert(dst_register, loc, updated_type); + case Op::ADD: { + // ra += rb + if (dst_ptr_or_mapfd_opt && src_signed_interval_opt) { + auto dst_ptr_or_mapfd = std::move(*dst_ptr_or_mapfd_opt); + auto src_signed = std::move(*src_signed_interval_opt); + update_ptr_or_mapfd(std::move(dst_ptr_or_mapfd), + std::move(src_signed), loc, dst_register); + } + else if (src_ptr_or_mapfd_opt && dst_signed_interval_opt) { + auto src_ptr_or_mapfd = std::move(*src_ptr_or_mapfd_opt); + auto dst_signed = std::move(*dst_signed_interval_opt); + update_ptr_or_mapfd(std::move(src_ptr_or_mapfd), + std::move(dst_signed), loc, dst_register); } - else if (std::holds_alternative(dst_ptr_or_mapfd)) { - m_registers.insert(dst_register, loc, dst_ptr_or_mapfd); + else if (dst_signed_interval_opt && src_signed_interval_opt) { + // we do not deal with numbers in region domain + m_registers -= dst_register; + } + else { + // possibly adding two pointers + set_to_bottom(); } + break; } - break; - } - // ra -= b, where ra is a pointer/mapfd - // b is a pointer/mapfd, numerical register, or a constant; - case Op::SUB: { - // b is a pointer/mapfd - if (dst_ptr_or_mapfd_opt && src_ptr_or_mapfd_opt) { - if (std::holds_alternative(dst_ptr_or_mapfd) && - std::holds_alternative(src_ptr_or_mapfd)) { - //std::cout << "type error: mapfd registers subtraction not defined\n"; - m_errors.push_back("mapfd registers subtraction not defined"); + case Op::SUB: { + // ra -= rb + if (dst_ptr_or_mapfd_opt && src_signed_interval_opt) { + auto dst_ptr_or_mapfd = std::move(*dst_ptr_or_mapfd_opt); + auto src_signed = std::move(*src_signed_interval_opt); + update_ptr_or_mapfd(std::move(dst_ptr_or_mapfd), + -std::move(src_signed), loc, dst_register); } - else if (same_region(dst_ptr_or_mapfd, src_ptr_or_mapfd)) { - if (std::holds_alternative(dst_ptr_or_mapfd) && - std::holds_alternative(src_ptr_or_mapfd)) { - auto dst_ptr_with_off = std::get(dst_ptr_or_mapfd); - auto src_ptr_with_off = std::get(src_ptr_or_mapfd); - subtracted = - dst_ptr_with_off.get_offset().to_interval() - - src_ptr_with_off.get_offset().to_interval(); - } + else if (src_ptr_or_mapfd_opt && dst_signed_interval_opt) { + m_registers -= dst_register; + } + else if (dst_signed_interval_opt && src_signed_interval_opt) { + // we do not deal with numbers in region domain + m_registers -= dst_register; } else { - //std::cout << "type error: subtraction between pointers of different region\n"; - m_errors.push_back("subtraction between pointers of different region"); + // ptr -= ptr + if (std::holds_alternative(*dst_ptr_or_mapfd_opt) && + std::holds_alternative(*src_ptr_or_mapfd_opt)) { + m_errors.push_back("mapfd registers subtraction not defined"); + } + else if (same_region(*dst_ptr_or_mapfd_opt, *src_ptr_or_mapfd_opt)) { + if (std::holds_alternative(*dst_ptr_or_mapfd_opt) && + std::holds_alternative(*src_ptr_or_mapfd_opt)) { + auto dst_ptr_with_off = std::get(*dst_ptr_or_mapfd_opt); + auto src_ptr_with_off = std::get(*src_ptr_or_mapfd_opt); + return (dst_ptr_with_off.get_offset().to_interval() - + src_ptr_with_off.get_offset().to_interval()); + } + } + else { + // Assertions should make sure we only perform this on + // non-shared pointers, hence this should not happen + m_errors.push_back("subtraction between pointers of different region"); + } + m_registers -= dst_register; } - m_registers -= dst_register; + break; } - // b is a numerical register, or a constant - else if (dst_ptr_or_mapfd_opt && src_interval_opt) { - update_ptr_or_mapfd(std::move(dst_ptr_or_mapfd), -std::move(src_interval), - loc, dst_register); + default: { + // no other operations supported for region domain + m_registers -= dst_register; + break; } - break; } - default: break; } - return subtracted; + return interval_t::bottom(); } void region_domain_t::do_load(const Mem& b, const register_t& target_register, bool unknown_ptr, diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index a6e3f681a..870f38f9c 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -3,10 +3,7 @@ #pragma once -#include "crab/abstract_domain.hpp" #include "crab/common.hpp" -#include "crab/type_ostream.hpp" -#include "crab/cfg.hpp" #include "platform.hpp" @@ -177,12 +174,13 @@ class region_domain_t final { void do_load(const Mem&, const register_t&, bool, location_t); void do_mem_store(const Mem&, location_t); interval_t do_bin(const Bin&, const std::optional&, - const std::optional&, - const std::optional&, location_t); + const std::optional&, + const std::optional&, + const std::optional&, location_t); void do_call(const Call&, const stack_cells_t&, location_t); void check_valid_access(const ValidAccess &, int); void assume_cst(Condition::Op, ptr_with_off_t&&, int64_t, register_t, location_t); - void update_ptr_or_mapfd(crab::ptr_or_mapfd_t&&, const interval_t&&, + void update_ptr_or_mapfd(crab::ptr_or_mapfd_t&&, interval_t&&, const crab::location_t&, register_t); std::optional find_ptr_or_mapfd_type(register_t) const; diff --git a/src/crab/signed_interval_domain.cpp b/src/crab/signed_interval_domain.cpp new file mode 100644 index 000000000..73e3f9a05 --- /dev/null +++ b/src/crab/signed_interval_domain.cpp @@ -0,0 +1,588 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT + +#include "crab/signed_interval_domain.hpp" +#include "boost/endian/conversion.hpp" + +namespace crab { + +bool registers_signed_state_t::is_bottom() const { + return m_is_bottom; +} + +bool registers_signed_state_t::is_top() const { + if (m_is_bottom) return false; + if (m_interval_env == nullptr) return true; + for (auto it : m_cur_def) { + if (it != nullptr) return false; + } + return true; +} + +void registers_signed_state_t::set_to_top() { + m_interval_env = std::make_shared(); + m_cur_def = live_registers_t{nullptr}; + m_is_bottom = false; +} + +void registers_signed_state_t::set_to_bottom() { + m_is_bottom = true; +} + +void registers_signed_state_t::insert(register_t reg, const location_t& loc, interval_t interval) { + auto reg_with_loc = reg_with_loc_t{reg, loc}; + (*m_interval_env)[reg_with_loc] = mock_interval_t{interval}; + m_cur_def[reg] = std::make_shared(reg_with_loc); +} + +std::optional registers_signed_state_t::find(reg_with_loc_t reg) const { + auto it = m_interval_env->find(reg); + if (it == m_interval_env->end()) return {}; + return it->second; +} + +std::optional registers_signed_state_t::find(register_t key) const { + if (m_cur_def[key] == nullptr) return {}; + const reg_with_loc_t& reg = *(m_cur_def[key]); + return find(reg); +} + +registers_signed_state_t registers_signed_state_t::operator|(const registers_signed_state_t& other) const { + if (is_bottom() || other.is_top()) { + return other; + } else if (other.is_bottom() || is_top()) { + return *this; + } + auto intervals_env = std::make_shared(); + registers_signed_state_t intervals_joined(intervals_env); + location_t loc = location_t(std::make_pair(label_t(-2, -2), 0)); + for (uint8_t i = 0; i < NUM_REGISTERS; i++) { + if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; + auto it1 = find(*(m_cur_def[i])); + auto it2 = other.find(*(other.m_cur_def[i])); + if (it1 && it2) { + auto interval1 = it1->to_interval(), interval2 = it2->to_interval(); + intervals_joined.insert(register_t{i}, loc, + std::move(interval1 | interval2)); + } + } + return intervals_joined; +} + +void registers_signed_state_t::adjust_bb_for_registers(location_t loc) { + for (uint8_t i = 0; i < NUM_REGISTERS; i++) { + if (auto it = find(register_t{i})) { + insert(register_t{i}, loc, it->to_interval()); + } + } +} + +void registers_signed_state_t::operator-=(register_t var) { + if (is_bottom()) return; + m_cur_def[var] = nullptr; +} + +bool stack_slots_signed_state_t::is_bottom() const { + return m_is_bottom; +} + +bool stack_slots_signed_state_t::is_top() const { + if (m_is_bottom) return false; + return m_interval_values.empty(); +} + +void stack_slots_signed_state_t::set_to_top() { + m_interval_values.clear(); + m_is_bottom = false; +} + +void stack_slots_signed_state_t::set_to_bottom() { + m_is_bottom = true; +} + +stack_slots_signed_state_t stack_slots_signed_state_t::top() { + return stack_slots_signed_state_t(false); +} + +std::optional stack_slots_signed_state_t::find(uint64_t key) const { + auto it = m_interval_values.find(key); + if (it == m_interval_values.end()) return {}; + return it->second; +} + +void stack_slots_signed_state_t::store(uint64_t key, mock_interval_t val, int width) { + m_interval_values[key] = std::make_pair(val, width); +} + +void stack_slots_signed_state_t::operator-=(uint64_t key) { + auto it = find(key); + if (it) + m_interval_values.erase(key); +} + +bool stack_slots_signed_state_t::all_numeric(uint64_t start_loc, int width) const { + auto overlapping_cells = find_overlapping_cells(start_loc, width); + if (overlapping_cells.empty()) return false; + for (std::size_t i = 0; i < overlapping_cells.size()-1; i++) { + int width_i = find(overlapping_cells[i]).value().second; + if (overlapping_cells[i]+width_i != overlapping_cells[i+1]) return false; + } + return true; +} + +void stack_slots_signed_state_t::fill_values(const std::vector& keys, + uint64_t start, int width) { + if (keys[0] < start) { + auto type = find(keys[0]); + auto width_key = type.value().second; + store(keys[0], interval_t::top(), width_key); + } + if (keys[0] > start) { + store(start, interval_t::top(), keys[0]-start); + } + for (size_t i = 0; i < keys.size()-1; i++) { + auto type = find(keys[i]); + auto width_key = type.value().second; + if (keys[i]+width_key != keys[i+1]) { + store(keys[i]+width_key, interval_t::top(), keys[i+1]-(keys[i]+width_key)); + } + } + auto type = find(keys[keys.size()-1]); + auto width_key = type.value().second; + if (keys[keys.size()-1]+width_key < start+width) { + store(keys[keys.size()-1]+width_key, interval_t::top(), + start+width-(keys[keys.size()-1]+width_key)); + } + if (keys[keys.size()-1]+width_key > start+width) { + store(keys[keys.size()-1], interval_t::top(), width_key); + } +} + +void stack_slots_signed_state_t::remove_overlap(const std::vector& keys, uint64_t start, + int width) { + for (auto& key : keys) { + auto type = find(key); + auto width_key = type.value().second; + if (key < start) { + int new_width = start-key; + store(key, interval_t::top(), new_width); + } + if (key+width_key > start+width) { + int new_width = key+width_key-(start+width); + store(start+width, interval_t::top(), new_width); + } + if (key >= start) *this -= key; + } +} + +std::vector stack_slots_signed_state_t::find_overlapping_cells(uint64_t start, int width) const { + std::vector overlapping_cells; + auto it = m_interval_values.begin(); + while (it != m_interval_values.end() && it->first < start) { + it++; + } + if (it != m_interval_values.begin()) { + it--; + auto key = it->first; + auto width_key = it->second.second; + if (key < start && key+width_key > start) overlapping_cells.push_back(key); + } + + for (; it != m_interval_values.end(); it++) { + auto key = it->first; + if (key >= start && key < start+width) overlapping_cells.push_back(key); + if (key >= start+width) break; + } + return overlapping_cells; +} + +static inline void join_stack(const stack_slots_signed_state_t& stack1, uint64_t key1, int& loc1, + const stack_slots_signed_state_t& stack2, uint64_t key2, int& loc2, + interval_values_stack_t& interval_values_joined) { + auto type1 = stack1.find(key1); auto type2 = stack2.find(key2); + auto& cells1 = type1.value(); auto& cells2 = type2.value(); + int width1 = cells1.second; int width2 = cells2.second; + auto interval1 = cells1.first.to_interval(); + auto interval2 = cells2.first.to_interval(); + auto top_interval_mock = mock_interval_t(interval_t::top()); + if (key1 == key2) { + if (width1 == width2) { + interval_values_joined[key1] = std::make_pair( + mock_interval_t(interval1 | interval2), width1); + loc1++; loc2++; + } + else if (width1 < width2) { + interval_values_joined[key1] = std::make_pair(top_interval_mock, width1); + loc1++; + } + else { + interval_values_joined[key1] = std::make_pair(top_interval_mock, width2); + loc2++; + } + } + else if (key1 > key2) { + if (key2+width2 > key1+width1) { + interval_values_joined[key1] = std::make_pair(top_interval_mock, width1); + loc1++; + } + else if (key2+width2 > key1) { + interval_values_joined[key1] = std::make_pair(top_interval_mock, key2+width2-key1); + loc2++; + } + else loc2++; + } + else { + join_stack(stack2, key2, loc2, stack1, key1, loc1, interval_values_joined); + } +} + +stack_slots_signed_state_t stack_slots_signed_state_t::operator|(const stack_slots_signed_state_t& other) const { + if (is_bottom() || other.is_top()) { + return other; + } else if (other.is_bottom() || is_top()) { + return *this; + } + interval_values_stack_t interval_values_joined; + auto stack1_keys = get_keys(); + auto stack2_keys = other.get_keys(); + int i = 0, j = 0; + while (i < static_cast(stack1_keys.size()) && j < static_cast(stack2_keys.size())) { + int key1 = stack1_keys[i], key2 = stack2_keys[j]; + join_stack(*this, key1, i, other, key2, j, interval_values_joined); + } + return stack_slots_signed_state_t(std::move(interval_values_joined)); +} + +size_t stack_slots_signed_state_t::size() const { + return m_interval_values.size(); +} + +std::vector stack_slots_signed_state_t::get_keys() const { + std::vector keys; + keys.reserve(size()); + + for (auto const&kv : m_interval_values) { + keys.push_back(kv.first); + } + return keys; +} + +bool signed_interval_domain_t::is_bottom() const { + if (m_is_bottom) return true; + return (m_registers_values.is_bottom() || m_stack_slots_values.is_bottom()); +} + +bool signed_interval_domain_t::is_top() const { + if (m_is_bottom) return false; + return (m_registers_values.is_top() && m_stack_slots_values.is_top()); +} + +signed_interval_domain_t signed_interval_domain_t::bottom() { + signed_interval_domain_t interval; + interval.set_to_bottom(); + return interval; +} + +void signed_interval_domain_t::set_to_bottom() { + m_is_bottom = true; + m_registers_values.set_to_bottom(); + m_stack_slots_values.set_to_bottom(); +} + +void signed_interval_domain_t::set_registers_to_bottom() { + m_registers_values.set_to_bottom(); +} + +void signed_interval_domain_t::set_registers_to_top() { + m_registers_values.set_to_top(); +} + +void signed_interval_domain_t::set_to_top() { + m_is_bottom = false; + m_registers_values.set_to_top(); + m_stack_slots_values.set_to_top(); +} + +std::optional signed_interval_domain_t::find_in_stack(uint64_t key) const { + return m_stack_slots_values.find(key); +} + +void signed_interval_domain_t::adjust_bb_for_types(location_t loc) { + m_registers_values.adjust_bb_for_registers(loc); +} + +std::vector signed_interval_domain_t::get_stack_keys() const { + return m_stack_slots_values.get_keys(); +} + +bool signed_interval_domain_t::all_numeric_in_stack(uint64_t start_loc, int width) const { + return m_stack_slots_values.all_numeric(start_loc, width); +} + +std::vector signed_interval_domain_t::find_overlapping_cells_in_stack(uint64_t start_loc, + int width) const { + return m_stack_slots_values.find_overlapping_cells(start_loc, width); +} + +void signed_interval_domain_t::remove_overlap_in_stack(const std::vector& overlap, + uint64_t start_loc, int width) { + m_stack_slots_values.remove_overlap(overlap, start_loc, width); +} + +void signed_interval_domain_t::fill_values_in_stack(const std::vector& overlap, + uint64_t start_loc, int width) { + m_stack_slots_values.fill_values(overlap, start_loc, width); +} + +std::optional signed_interval_domain_t::find_interval_value(register_t reg) const { + return m_registers_values.find(reg); +} + +std::optional signed_interval_domain_t::find_interval_at_loc( + const reg_with_loc_t reg) const { + return m_registers_values.find(reg); +} + +void signed_interval_domain_t::insert_in_registers(register_t reg, location_t loc, + interval_t interval) { + m_registers_values.insert(reg, loc, interval); +} + +void signed_interval_domain_t::store_in_stack(uint64_t key, mock_interval_t interval, int width) { + m_stack_slots_values.store(key, interval, width); +} + +bool signed_interval_domain_t::operator<=(const signed_interval_domain_t& abs) const { + /* WARNING: The operation is not implemented yet.*/ + return true; +} + +void signed_interval_domain_t::operator|=(const signed_interval_domain_t& abs) { + signed_interval_domain_t tmp{abs}; + operator|=(std::move(tmp)); +} + +void signed_interval_domain_t::operator|=(signed_interval_domain_t&& abs) { + if (is_bottom()) { + *this = abs; + return; + } + *this = *this | std::move(abs); +} + +signed_interval_domain_t signed_interval_domain_t::operator|(const signed_interval_domain_t& other) const { + if (is_bottom() || other.is_top()) { + return other; + } + else if (other.is_bottom() || is_top()) { + return *this; + } + return signed_interval_domain_t(m_registers_values | other.m_registers_values, + m_stack_slots_values | other.m_stack_slots_values); +} + +signed_interval_domain_t signed_interval_domain_t::operator|(signed_interval_domain_t&& other) const { + if (is_bottom() || other.is_top()) { + return std::move(other); + } + else if (other.is_bottom() || is_top()) { + return *this; + } + return signed_interval_domain_t( + m_registers_values | std::move(other.m_registers_values), + m_stack_slots_values | std::move(other.m_stack_slots_values)); +} + +signed_interval_domain_t signed_interval_domain_t::operator&(const signed_interval_domain_t& abs) const { + /* WARNING: The operation is not implemented yet.*/ + return abs; +} + +signed_interval_domain_t signed_interval_domain_t::widen(const signed_interval_domain_t& abs, bool to_constants) { + /* WARNING: The operation is not implemented yet.*/ + return abs; +} + +signed_interval_domain_t signed_interval_domain_t::narrow(const signed_interval_domain_t& other) const { + /* WARNING: The operation is not implemented yet.*/ + return other; +} + +crab::bound_t signed_interval_domain_t::get_loop_count_upper_bound() { + /* WARNING: The operation is not implemented yet.*/ + return crab::bound_t{crab::number_t{0}}; +} + +void signed_interval_domain_t::initialize_loop_counter(const label_t& label) { + /* WARNING: The operation is not implemented yet.*/ +} + +string_invariant signed_interval_domain_t::to_set() { + return string_invariant{}; +} + +signed_interval_domain_t signed_interval_domain_t::setup_entry() { + registers_signed_state_t registers(std::make_shared()); + signed_interval_domain_t interval(std::move(registers), stack_slots_signed_state_t::top()); + return interval; +} + +void signed_interval_domain_t::operator()(const Un& u, location_t loc) { + auto swap_endianness = [&](interval_t&& v, auto input, const auto& be_or_le) { + if (std::optional n = v.singleton()) { + if (n->fits_cast_to_int64()) { + input = (decltype(input))n.value().cast_to_sint64(); + decltype(input) output = be_or_le(input); + m_registers_values.insert(u.dst.v, loc, interval_t{number_t{output}}); + return; + } + } + m_registers_values.insert(u.dst.v, loc, interval_t::top()); + }; + + auto mock_interval = m_registers_values.find(u.dst.v); + if (!mock_interval) return; + interval_t interval = mock_interval->to_interval(); + + // Swap bytes. For 64-bit types we need the weights to fit in a + // signed int64, but for smaller types we don't want sign extension, + // so we use unsigned which still fits in a signed int64. + switch (u.op) { + case Un::Op::BE16: + swap_endianness(std::move(interval), uint16_t(0), + boost::endian::native_to_big); + break; + case Un::Op::BE32: + swap_endianness(std::move(interval), uint32_t(0), + boost::endian::native_to_big); + break; + case Un::Op::BE64: + swap_endianness(std::move(interval), int64_t(0), + boost::endian::native_to_big); + break; + case Un::Op::LE16: + swap_endianness(std::move(interval), uint16_t(0), + boost::endian::native_to_little); + break; + case Un::Op::LE32: + swap_endianness(std::move(interval), uint32_t(0), + boost::endian::native_to_little); + break; + case Un::Op::LE64: + swap_endianness(std::move(interval), int64_t(0), + boost::endian::native_to_little); + break; + case Un::Op::NEG: + auto reg_with_loc = reg_with_loc_t(u.dst.v, loc); + m_registers_values.insert(u.dst.v, loc, -interval); + break; + } +} + +void signed_interval_domain_t::operator()(const LoadMapFd& u, location_t loc) { + // nothing to do here +} + +void signed_interval_domain_t::operator()(const Packet& u, location_t loc) { + // nothing to do here +} + +void signed_interval_domain_t::operator()(const Assume& s, location_t loc) { + // nothing to do here +} + +bool signed_interval_domain_t::load_from_stack(register_t reg, uint64_t offset, location_t loc) { + if (auto loaded = m_stack_slots_values.find(offset)) { + m_registers_values.insert(reg, loc, (*loaded).first.to_interval()); + return true; + } + return false; +} + +void signed_interval_domain_t::store_in_stack(const Mem& b, uint64_t offset, int width) { + if (std::holds_alternative(b.value)) { + auto target_reg = std::get(b.value); + if (auto targetreg_mock_interval = m_registers_values.find(target_reg.v)) { + auto targetreg_type = targetreg_mock_interval->to_interval(); + m_stack_slots_values.store(offset, targetreg_type, width); + } + } + else { + auto imm = (int64_t)std::get(b.value).v; + auto targetreg_type = interval_t{number_t{imm}}; + m_stack_slots_values.store(offset, targetreg_type, width); + } +} + +void signed_interval_domain_t::check_valid_access(const ValidAccess& s, interval_t&& interval, + int width, bool check_stack_all_numeric) { + + if (check_stack_all_numeric) { + auto start_interval = interval + interval_t{number_t{s.offset}}; + if (auto finite_size = start_interval.finite_size()) { + if (auto start_interval_lb = start_interval.lb().number()) { + auto start_offset = (uint64_t)(*start_interval_lb); + int width_from_start = (int)(*finite_size) + width; + if (!m_stack_slots_values.all_numeric(start_offset, width_from_start)) { + m_errors.push_back("Stack access not numeric"); + } + } + else { + m_errors.push_back("Offset information not available"); + } + } + else { + m_errors.push_back("Register interval not finite for stack access"); + } + } + else { + bool is_comparison_check = s.width == (Value)Imm{0}; + if (!is_comparison_check) { + if (s.or_null) { + if (auto singleton = interval.singleton()) { + if (*singleton == number_t{0}) return; + } + m_errors.push_back("Non-null number"); + } + else { + m_errors.push_back("Only pointers can be dereferenced"); + } + } + } +} + +void signed_interval_domain_t::operator()(const Undefined& u, location_t loc) { + // nothing to do here +} + +void signed_interval_domain_t::operator()(const Bin& b, location_t loc) { + // nothing to do here +} + +void signed_interval_domain_t::operator()(const Call&, location_t loc) { + // nothing to do here +} + +void signed_interval_domain_t::operator()(const Exit&, location_t loc) { + // nothing to do here +} + +void signed_interval_domain_t::operator()(const Jmp&, location_t loc) { + // nothing to do here +} + +void signed_interval_domain_t::operator()(const Mem&, location_t loc) { + // nothing to do here +} + +void signed_interval_domain_t::operator()(const Assert&, location_t loc) { + // nothing to do here +} + +void signed_interval_domain_t::operator()(const basic_block_t& bb, int print) { + // nothing to do here +} + +void signed_interval_domain_t::set_require_check(check_require_func_t f) {} + +} // namespace crab diff --git a/src/crab/interval_prop_domain.hpp b/src/crab/signed_interval_domain.hpp similarity index 50% rename from src/crab/interval_prop_domain.hpp rename to src/crab/signed_interval_domain.hpp index 64003cb96..52de2e1a2 100644 --- a/src/crab/interval_prop_domain.hpp +++ b/src/crab/signed_interval_domain.hpp @@ -3,14 +3,6 @@ #pragma once -#include -#include - -#include "crab/abstract_domain.hpp" -#include "crab/region_domain.hpp" -#include "crab/cfg.hpp" -#include "linear_constraint.hpp" -#include "string_constraints.hpp" #include "crab/common.hpp" namespace crab { @@ -18,7 +10,7 @@ namespace crab { using live_registers_t = std::array, NUM_REGISTERS>; using global_interval_env_t = std::unordered_map; -class registers_cp_state_t { +class registers_signed_state_t { live_registers_t m_cur_def; std::shared_ptr m_interval_env; @@ -29,27 +21,27 @@ class registers_cp_state_t { bool is_top() const; void set_to_bottom(); void set_to_top(); + void set_registers_to_bottom(); std::optional find(reg_with_loc_t reg) const; std::optional find(register_t key) const; void insert(register_t, const location_t&, interval_t); void operator-=(register_t); - registers_cp_state_t operator|(const registers_cp_state_t& other) const; - registers_cp_state_t(bool is_bottom = false) : m_interval_env(nullptr), + registers_signed_state_t operator|(const registers_signed_state_t& other) const; + registers_signed_state_t(bool is_bottom = false) : m_interval_env(nullptr), m_is_bottom(is_bottom) {} - explicit registers_cp_state_t(live_registers_t&& vars, + explicit registers_signed_state_t(live_registers_t&& vars, std::shared_ptr interval_env, bool is_bottom = false) : m_cur_def(std::move(vars)), m_interval_env(interval_env), m_is_bottom(is_bottom) {} - explicit registers_cp_state_t(std::shared_ptr interval_env, + explicit registers_signed_state_t(std::shared_ptr interval_env, bool is_bottom = false) : m_interval_env(interval_env), m_is_bottom(is_bottom) {} void adjust_bb_for_registers(location_t); - void scratch_caller_saved_registers(); }; using interval_cells_t = std::pair; // intervals with width using interval_values_stack_t = std::map; -class stack_cp_state_t { +class stack_slots_signed_state_t { interval_values_stack_t m_interval_values; bool m_is_bottom = false; @@ -59,62 +51,65 @@ class stack_cp_state_t { bool is_top() const; void set_to_bottom(); void set_to_top(); - static stack_cp_state_t top(); + static stack_slots_signed_state_t top(); std::optional find(uint64_t) const; void store(uint64_t, mock_interval_t, int); void operator-=(uint64_t); bool all_numeric(uint64_t, int) const; - stack_cp_state_t operator|(const stack_cp_state_t& other) const; - stack_cp_state_t(bool is_bottom = false) : m_is_bottom(is_bottom) {} - explicit stack_cp_state_t(interval_values_stack_t&& interval_values, bool is_bottom = false) + stack_slots_signed_state_t operator|(const stack_slots_signed_state_t& other) const; + stack_slots_signed_state_t(bool is_bottom = false) : m_is_bottom(is_bottom) {} + explicit stack_slots_signed_state_t(interval_values_stack_t&& interval_values, + bool is_bottom = false) : m_interval_values(std::move(interval_values)), m_is_bottom(is_bottom) {} std::vector get_keys() const; size_t size() const; std::vector find_overlapping_cells(uint64_t, int) const; void remove_overlap(const std::vector&, uint64_t, int); + void fill_values(const std::vector&, uint64_t, int); }; -class interval_prop_domain_t final { - registers_cp_state_t m_registers_interval_values; - stack_cp_state_t m_stack_slots_interval_values; +class signed_interval_domain_t final { + registers_signed_state_t m_registers_values; + stack_slots_signed_state_t m_stack_slots_values; std::vector m_errors; bool m_is_bottom = false; public: - interval_prop_domain_t() = default; - interval_prop_domain_t(interval_prop_domain_t&& o) = default; - interval_prop_domain_t(const interval_prop_domain_t& o) = default; - explicit interval_prop_domain_t(registers_cp_state_t&& consts_regs, - stack_cp_state_t&& interval_stack_slots, bool is_bottom = false) : - m_registers_interval_values(std::move(consts_regs)), m_stack_slots_interval_values(std::move(interval_stack_slots)), + signed_interval_domain_t() = default; + signed_interval_domain_t(signed_interval_domain_t&& o) = default; + signed_interval_domain_t(const signed_interval_domain_t& o) = default; + explicit signed_interval_domain_t(registers_signed_state_t&& consts_regs, + stack_slots_signed_state_t&& interval_stack_slots, bool is_bottom = false) : + m_registers_values(std::move(consts_regs)), m_stack_slots_values(std::move(interval_stack_slots)), m_is_bottom(is_bottom) {} - interval_prop_domain_t& operator=(interval_prop_domain_t&& o) = default; - interval_prop_domain_t& operator=(const interval_prop_domain_t& o) = default; + signed_interval_domain_t& operator=(signed_interval_domain_t&& o) = default; + signed_interval_domain_t& operator=(const signed_interval_domain_t& o) = default; // eBPF initialization: R1 points to ctx, R10 to stack, etc. - static interval_prop_domain_t setup_entry(); + static signed_interval_domain_t setup_entry(); // bottom/top - static interval_prop_domain_t bottom(); + static signed_interval_domain_t bottom(); void set_to_top(); void set_to_bottom(); + void set_registers_to_bottom(); + void set_registers_to_top(); bool is_bottom() const; bool is_top() const; // inclusion - bool operator<=(const interval_prop_domain_t& other) const; + bool operator<=(const signed_interval_domain_t& other) const; // join - void operator|=(const interval_prop_domain_t& abs); - void operator|=(interval_prop_domain_t&& abs); - interval_prop_domain_t operator|(const interval_prop_domain_t& other) const; - interval_prop_domain_t operator|(interval_prop_domain_t&& abs) const; + void operator|=(const signed_interval_domain_t& abs); + void operator|=(signed_interval_domain_t&& abs); + signed_interval_domain_t operator|(const signed_interval_domain_t& other) const; + signed_interval_domain_t operator|(signed_interval_domain_t&& abs) const; // meet - interval_prop_domain_t operator&(const interval_prop_domain_t& other) const; + signed_interval_domain_t operator&(const signed_interval_domain_t& other) const; // widening - interval_prop_domain_t widen(const interval_prop_domain_t& other, bool); + signed_interval_domain_t widen(const signed_interval_domain_t& other, bool); // narrowing - interval_prop_domain_t narrow(const interval_prop_domain_t& other) const; + signed_interval_domain_t narrow(const signed_interval_domain_t& other) const; //forget - void operator-=(variable_t var); - void operator-=(register_t reg) { m_registers_interval_values -= reg; } + void operator-=(register_t reg) { m_registers_values -= reg; } //// abstract transformers void operator()(const Undefined&, location_t loc = boost::none); @@ -130,41 +125,15 @@ class interval_prop_domain_t final { void operator()(const Packet&, location_t loc = boost::none); void operator()(const Assume&, location_t loc = boost::none); void operator()(const Assert&, location_t loc = boost::none); - void operator()(const ValidAccess&, location_t loc = boost::none); - void operator()(const Comparable&, location_t loc = boost::none); - void operator()(const Addable&, location_t loc = boost::none); - void operator()(const ValidStore&, location_t loc = boost::none); - void operator()(const TypeConstraint&, location_t loc = boost::none); - void operator()(const ValidSize&, location_t loc = boost::none); - void operator()(const ValidMapKeyValue&, location_t loc = boost::none); - void operator()(const ZeroCtxOffset&, location_t loc = boost::none); - void operator()(const FuncConstraint&, location_t loc = boost::none) {} void operator()(const IncrementLoopCounter&, location_t loc = boost::none) {} void operator()(const basic_block_t& bb, int print = 0); - void write(std::ostream& os) const; + void write(std::ostream& os) const {} crab::bound_t get_loop_count_upper_bound(); void initialize_loop_counter(const label_t&); string_invariant to_set(); void set_require_check(check_require_func_t f); - void do_load(const Mem&, const register_t&, std::optional, bool, location_t); - void do_mem_store(const Mem&, std::optional); - void do_call(const Call&, const stack_cells_t&, location_t); - void do_bin(const Bin&, const std::optional&, const std::optional&, - const std::optional&, const std::optional&, - const interval_t&, location_t); - void check_valid_access(const ValidAccess&, interval_t&&, int, bool = false); - void assume_unsigned_cst_interval(Condition::Op, bool, interval_t&&, interval_t&&, - interval_t&&, interval_t&&, register_t, Value, location_t); - void assume_signed_cst_interval(Condition::Op, bool, interval_t&&, interval_t&&, - interval_t&&, interval_t&&, register_t, Value, location_t); - void assume_cst(Condition::Op, bool, register_t, Value, interval_t&&, interval_t&&, location_t); - void assume_lt(bool, interval_t&&, interval_t&&, interval_t&&, interval_t&&, - register_t, Value, location_t, bool); - void assume_gt(bool, interval_t&&, interval_t&&, interval_t&&, interval_t&&, - register_t, Value, location_t); - void assume_gt_and_lt(bool, bool, bool, interval_t&&, interval_t&&, - interval_t&&, interval_t&&, register_t, Value, location_t, bool = true); + void check_valid_access(const ValidAccess&, interval_t&&, int, bool); std::optional find_interval_value(register_t) const; std::optional find_interval_at_loc(const reg_with_loc_t reg) const; std::optional find_in_stack(uint64_t) const; @@ -175,8 +144,11 @@ class interval_prop_domain_t final { bool all_numeric_in_stack(uint64_t, int) const; std::vector find_overlapping_cells_in_stack(uint64_t, int) const; void remove_overlap_in_stack(const std::vector&, uint64_t, int); + void fill_values_in_stack(const std::vector&, uint64_t, int); [[nodiscard]] std::vector& get_errors() { return m_errors; } void reset_errors() { m_errors.clear(); } -}; // end interval_prop_domain_t + bool load_from_stack(register_t, uint64_t, location_t); + void store_in_stack(const Mem&, uint64_t, int); +}; // end signed_interval_domain_t } // namespace crab diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 9f31c9adb..e99c23b7f 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -74,7 +74,8 @@ type_domain_t type_domain_t::operator|(type_domain_t&& other) const { return *this; } return type_domain_t(m_region | std::move(other.m_region), - m_offset | std::move(other.m_offset), m_interval | std::move(other.m_interval)); + m_offset | std::move(other.m_offset), + m_interval | std::move(other.m_interval)); } type_domain_t type_domain_t::operator&(const type_domain_t& abs) const { @@ -102,10 +103,12 @@ string_invariant type_domain_t::to_set() const { for (uint8_t i = 0; i < NUM_REGISTERS; i++) { std::stringstream elem; auto maybe_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(register_t{i}); - auto maybe_width_interval = m_interval.find_interval_value(register_t{i}); + // TODO: Only allowing signed intervals for now, because otherwise parsing needs to be + // changed; support unsigned intervals + auto maybe_signed_interval = m_interval.find_signed_interval_value(register_t{i}); auto maybe_offset = m_offset.find_offset_info(register_t{i}); - if (maybe_ptr_or_mapfd.has_value() || maybe_width_interval.has_value()) { - print_register(elem, Reg{i}, maybe_ptr_or_mapfd, maybe_offset, maybe_width_interval); + if (maybe_ptr_or_mapfd.has_value() || maybe_signed_interval.has_value()) { + print_register(elem, Reg{i}, maybe_ptr_or_mapfd, maybe_offset, maybe_signed_interval); result.insert(elem.str()); } } @@ -133,9 +136,11 @@ string_invariant type_domain_t::to_set() const { std::vector stack_keys_interval = m_interval.get_stack_keys(); for (auto const& k : stack_keys_interval) { std::stringstream elem; - auto maybe_interval_cells = m_interval.find_in_stack(k); - if (maybe_interval_cells.has_value()) { - auto interval_cells = maybe_interval_cells.value(); + // TODO: Only allowing signed intervals for now, because otherwise parsing needs to be + // changed; support unsigned intervals + auto maybe_interval_cells_signed = m_interval.find_in_stack_signed(k); + if (maybe_interval_cells_signed.has_value()) { + auto interval_cells = maybe_interval_cells_signed.value(); elem << "stack"; print_numeric_memory_cell(elem, k, k+interval_cells.second-1, interval_cells.first.to_interval()); @@ -150,6 +155,8 @@ void type_domain_t::operator()(const Undefined& u, location_t loc) { } void type_domain_t::operator()(const Un& u, location_t loc) { + m_region(u, loc); + m_offset(u, loc); m_interval(u, loc); } @@ -173,7 +180,7 @@ void type_domain_t::operator()(const Call& u, location_t loc) { for (ArgPair param : u.pairs) { if (param.kind == ArgPair::Kind::PTR_TO_WRITABLE_MEM) { auto maybe_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(param.mem.v); - auto maybe_width_interval = m_interval.find_interval_value(param.size.v); + auto maybe_width_interval = m_interval.find_signed_interval_value(param.size.v); if (!maybe_ptr_or_mapfd || !maybe_width_interval) continue; if (is_stack_ptr(maybe_ptr_or_mapfd)) { auto ptr_with_off = std::get(*maybe_ptr_or_mapfd); @@ -228,10 +235,8 @@ void type_domain_t::operator()(const Assume& s, location_t loc) { maybe_left_interval, maybe_right_interval)) { if (maybe_left_interval) { // both numbers - auto left_interval = maybe_left_interval->to_interval(); - auto right_interval = maybe_right_interval->to_interval(); - m_interval.assume_cst(cond.op, cond.is64, cond.left.v, cond.right, - std::move(left_interval), std::move(right_interval), loc); + m_interval.assume_cst(cond.op, cond.is64, register_t{cond.left.v}, + cond.right, loc); } else if (maybe_left_type) { if (is_packet_ptr(maybe_left_type)) { @@ -262,19 +267,14 @@ void type_domain_t::operator()(const Assume& s, location_t loc) { // TODO: need to work with values } else if (maybe_left_interval) { - auto left_interval = maybe_left_interval->to_interval(); - int64_t imm = static_cast(std::get(cond.right).v); - auto right_interval = cond.is64 - ? interval_t{number_t{imm}} : interval_t{number_t{(uint64_t)imm}}; - m_interval.assume_cst(cond.op, cond.is64, cond.left.v, cond.right, - std::move(left_interval), std::move(right_interval), loc); + m_interval.assume_cst(cond.op, cond.is64, register_t{cond.left.v}, cond.right, loc); } } } void type_domain_t::operator()(const ValidDivisor& u, location_t loc) { auto maybe_ptr_or_mapfd_reg = m_region.find_ptr_or_mapfd_type(u.reg.v); - auto maybe_num_type_reg = m_interval.find_interval_value(u.reg.v); + auto maybe_num_type_reg = m_interval.find_unsigned_interval_value(u.reg.v); //assert(!maybe_ptr_or_mapfd_reg.has_value() || !maybe_num_type_reg.has_value()); if (is_ptr_type(maybe_ptr_or_mapfd_reg)) { @@ -290,9 +290,6 @@ void type_domain_t::operator()(const ValidDivisor& u, location_t loc) { void type_domain_t::operator()(const ValidAccess& s, location_t loc) { auto reg_type = m_region.find_ptr_or_mapfd_type(s.reg.v); - auto mock_interval_type = m_interval.find_interval_value(s.reg.v); - auto interval_type = - mock_interval_type ? mock_interval_type->to_interval() : std::optional{}; if (reg_type) { std::optional width_mock_interval; if (std::holds_alternative(s.width)) { @@ -324,7 +321,13 @@ void type_domain_t::operator()(const ValidAccess& s, location_t loc) { } } else { - m_interval.check_valid_access(s, std::move(*interval_type), -1); + auto mock_interval_type = m_interval.find_interval_value(s.reg.v); + if (mock_interval_type) { + m_interval.check_valid_access(s, std::move(mock_interval_type->to_interval())); + } + else { + m_errors.push_back("valid access on unknown register"); + } } } @@ -477,61 +480,41 @@ void type_domain_t::operator()(const ZeroCtxOffset& u, location_t loc) { type_domain_t type_domain_t::setup_entry(bool init_r1) { auto&& reg = crab::region_domain_t::setup_entry(init_r1); auto&& off = offset_domain_t::setup_entry(); - auto&& interval = interval_prop_domain_t::setup_entry(); + auto&& interval = interval_domain_t::setup_entry(); type_domain_t typ(std::move(reg), std::move(off), std::move(interval)); return typ; } void type_domain_t::operator()(const Bin& bin, location_t loc) { - if (is_bottom()) return; std::optional src_ptr_or_mapfd; - std::optional src_interval; + std::optional src_signed_interval, src_unsigned_interval; if (std::holds_alternative(bin.v)) { Reg r = std::get(bin.v); src_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(r.v); - auto src_mock_interval = m_interval.find_interval_value(r.v); - if (src_mock_interval) { - src_interval = src_mock_interval->to_interval(); + if (auto src_mock_signed_interval = m_interval.find_signed_interval_value(r.v)) { + src_signed_interval = src_mock_signed_interval->to_interval(); } - } - else { - int64_t imm; - if (bin.is64) { - imm = static_cast(std::get(bin.v).v); + if (auto src_mock_unsigned_interval = m_interval.find_unsigned_interval_value(r.v)) { + src_unsigned_interval = src_mock_unsigned_interval->to_interval(); } - else { - imm = static_cast(std::get(bin.v).v); - } - src_interval = interval_t{number_t{imm}}; } - //assert(!src_ptr_or_mapfd.has_value() || !src_interval.has_value()); - auto dst_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(bin.dst.v); - auto dst_mock_interval = m_interval.find_interval_value(bin.dst.v); - auto dst_interval = dst_mock_interval ? dst_mock_interval->to_interval() : - std::optional(); - //assert(!dst_ptr_or_mapfd.has_value() || !dst_interval.has_value()); - - using Op = Bin::Op; - // for all operations except mov, add, sub, the src and dst should be numbers - if ((src_ptr_or_mapfd || dst_ptr_or_mapfd) - && (bin.op != Op::MOV && bin.op != Op::ADD && bin.op != Op::SUB)) { - m_errors.push_back("operation on pointers not allowed"); - //std::cout << "type error: operation on pointers not allowed\n"; - m_region -= bin.dst.v; - m_offset -= bin.dst.v; - m_interval -= bin.dst.v; - return; + std::optional dst_signed_interval, dst_unsigned_interval; + if (auto dst_mock_signed_interval = m_interval.find_signed_interval_value(bin.dst.v)) { + dst_signed_interval = dst_mock_signed_interval->to_interval(); + } + if (auto dst_mock_unsigned_interval = m_interval.find_unsigned_interval_value(bin.dst.v)) { + dst_unsigned_interval = dst_mock_unsigned_interval->to_interval(); } - interval_t subtracted_reg = - m_region.do_bin(bin, src_interval, src_ptr_or_mapfd, dst_ptr_or_mapfd, loc); - interval_t subtracted_off = - m_offset.do_bin(bin, src_interval, dst_interval, src_ptr_or_mapfd, dst_ptr_or_mapfd, loc); + interval_t subtracted_reg = m_region.do_bin(bin, src_signed_interval, src_ptr_or_mapfd, + dst_signed_interval, dst_ptr_or_mapfd, loc); + interval_t subtracted_off = m_offset.do_bin(bin, src_signed_interval, src_ptr_or_mapfd, + dst_signed_interval, dst_ptr_or_mapfd, loc); auto subtracted = subtracted_reg.is_bottom() ? subtracted_off : subtracted_reg; - m_interval.do_bin(bin, src_interval, dst_interval, src_ptr_or_mapfd, dst_ptr_or_mapfd, - subtracted, loc); + m_interval.do_bin(bin, src_signed_interval, src_unsigned_interval, src_ptr_or_mapfd, + dst_signed_interval, dst_unsigned_interval, dst_ptr_or_mapfd, subtracted, loc); } void type_domain_t::do_load(const Mem& b, const Reg& target_reg, bool unknown_ptr, @@ -610,7 +593,8 @@ void type_domain_t::print_stack() const { } } for (auto const& k : stack_keys_interval) { - auto maybe_interval_cells = m_interval.find_in_stack(k); + auto maybe_interval_cells = m_interval.find_in_stack_signed(k); + // TODO: also handle unsigned intervals if (maybe_interval_cells) { auto interval_cells = maybe_interval_cells.value(); std::cout << "\t\t"; @@ -668,8 +652,13 @@ type_domain_t::find_offset_at_loc(const crab::reg_with_loc_t& loc) const { } std::optional -type_domain_t::find_interval_at_loc(const crab::reg_with_loc_t& loc) const { - return m_interval.find_interval_at_loc(loc); +type_domain_t::find_signed_interval_at_loc(const crab::reg_with_loc_t& loc) const { + return m_interval.find_signed_interval_at_loc(loc); +} + +std::optional +type_domain_t::find_unsigned_interval_at_loc(const crab::reg_with_loc_t& loc) const { + return m_interval.find_unsigned_interval_at_loc(loc); } static inline region_t string_to_region(const std::string& s) { @@ -690,10 +679,30 @@ void type_domain_t::insert_in_registers_in_interval_domain(register_t r, locatio m_interval.insert_in_registers(r, loc, interval); } +void type_domain_t::insert_in_registers_in_signed_interval_domain(register_t r, location_t loc, + interval_t interval) { + m_interval.insert_in_registers_signed(r, loc, interval); +} + +void type_domain_t::insert_in_registers_in_unsigned_interval_domain(register_t r, location_t loc, + interval_t interval) { + m_interval.insert_in_registers_unsigned(r, loc, interval); +} + void type_domain_t::store_in_stack_in_interval_domain(uint64_t key, mock_interval_t p, int width) { m_interval.store_in_stack(key, p, width); } +void type_domain_t::store_in_stack_in_signed_interval_domain(uint64_t key, mock_interval_t p, + int width) { + m_interval.store_in_stack_signed(key, p, width); +} + +void type_domain_t::store_in_stack_in_unsigned_interval_domain(uint64_t key, mock_interval_t p, + int width) { + m_interval.store_in_stack_unsigned(key, p, width); +} + void type_domain_t::insert_in_registers_in_offset_domain(register_t r, location_t loc, dist_t d) { m_offset.insert_in_registers(r, loc, d); } @@ -820,6 +829,7 @@ type_domain_t type_domain_t::from_predefined_types(const std::set& else if (regex_match(t, m, regex(REG ":" NUMBER))) { auto reg = register_t{static_cast(std::stoul(m[1]))}; auto num = create_interval(m[2], m[3]).to_interval(); + // TODO: support separately for signed and unsigned intervals typ.insert_in_registers_in_interval_domain(reg, loc, num); } else if (regex_match(t, m, regex(REG ":" MAPFD))) { @@ -898,14 +908,15 @@ void print_annotated(std::ostream& o, const crab::type_domain_t& typ, o << bb.label() << ":\n"; uint32_t curr_pos = 0; + // TODO: add support for printing unsigned intervals as well for (const Instruction& statement : bb) { ++curr_pos; - location_t loc = location_t(std::make_pair(bb.label(), curr_pos)); + crab::location_t loc = crab::location_t(std::make_pair(bb.label(), curr_pos)); o << " " << curr_pos << "."; if (std::holds_alternative(statement)) { auto r0_reg = crab::reg_with_loc_t(register_t{R0_RETURN_VALUE}, loc); auto region = typ.find_ptr_or_mapfd_at_loc(r0_reg); - auto interval = typ.find_interval_at_loc(r0_reg); + auto interval = typ.find_signed_interval_at_loc(r0_reg); print_annotated(o, std::get(statement), region, interval); } else if (std::holds_alternative(statement)) { @@ -913,7 +924,7 @@ void print_annotated(std::ostream& o, const crab::type_domain_t& typ, auto reg_with_loc = crab::reg_with_loc_t(b.dst.v, loc); auto region = typ.find_ptr_or_mapfd_at_loc(reg_with_loc); auto offset = typ.find_offset_at_loc(reg_with_loc); - auto interval = typ.find_interval_at_loc(reg_with_loc); + auto interval = typ.find_signed_interval_at_loc(reg_with_loc); print_annotated(o, b, region, offset, interval); } else if (std::holds_alternative(statement)) { @@ -923,7 +934,7 @@ void print_annotated(std::ostream& o, const crab::type_domain_t& typ, auto target_reg_loc = crab::reg_with_loc_t(target_reg.v, loc); auto region = typ.find_ptr_or_mapfd_at_loc(target_reg_loc); auto offset = typ.find_offset_at_loc(target_reg_loc); - auto interval = typ.find_interval_at_loc(target_reg_loc); + auto interval = typ.find_signed_interval_at_loc(target_reg_loc); print_annotated(o, u, region, offset, interval); } else o << " " << u << "\n"; @@ -937,7 +948,7 @@ void print_annotated(std::ostream& o, const crab::type_domain_t& typ, else if (std::holds_alternative(statement)) { auto u = std::get(statement); auto reg = crab::reg_with_loc_t(u.dst.v, loc); - auto interval = typ.find_interval_at_loc(reg); + auto interval = typ.find_signed_interval_at_loc(reg); print_annotated(o, u, interval); } else o << " " << statement << "\n"; diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index af0ab4e2f..ddd7972ec 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -3,18 +3,18 @@ #pragma once -#include "crab/abstract_domain.hpp" #include "crab/region_domain.hpp" -#include "crab/interval_prop_domain.hpp" +#include "crab/interval_domain.hpp" #include "crab/offset_domain.hpp" #include "crab/common.hpp" +#include "crab/type_ostream.hpp" namespace crab { class type_domain_t final { region_domain_t m_region; offset_domain_t m_offset; - interval_prop_domain_t m_interval; + interval_domain_t m_interval; bool m_is_bottom = false; std::vector m_errors; @@ -23,8 +23,8 @@ class type_domain_t final { type_domain_t() = default; type_domain_t(type_domain_t&& o) = default; type_domain_t(const type_domain_t& o) = default; - explicit type_domain_t(region_domain_t&& reg, offset_domain_t&& off, interval_prop_domain_t&& - interval, bool is_bottom = false) : + explicit type_domain_t(region_domain_t&& reg, offset_domain_t&& off, + interval_domain_t&& interval, bool is_bottom = false) : m_region(reg), m_offset(off), m_interval(interval), m_is_bottom(is_bottom) {} type_domain_t& operator=(type_domain_t&& o) = default; type_domain_t& operator=(const type_domain_t& o) = default; @@ -89,12 +89,17 @@ class type_domain_t final { void print_stack() const; std::optional find_ptr_or_mapfd_at_loc(const crab::reg_with_loc_t&) const; std::optional find_offset_at_loc(const crab::reg_with_loc_t&) const; - std::optional find_interval_at_loc(const crab::reg_with_loc_t&) const; + std::optional find_signed_interval_at_loc(const crab::reg_with_loc_t&) const; + std::optional find_unsigned_interval_at_loc(const crab::reg_with_loc_t&) const; static type_domain_t from_predefined_types(const std::set&, bool); void insert_in_registers_in_region_domain(register_t, location_t, const ptr_or_mapfd_t&); void store_in_stack_in_region_domain(uint64_t, ptr_or_mapfd_t, int); void insert_in_registers_in_interval_domain(register_t, location_t, interval_t); + void insert_in_registers_in_signed_interval_domain(register_t, location_t, interval_t); + void insert_in_registers_in_unsigned_interval_domain(register_t, location_t, interval_t); void store_in_stack_in_interval_domain(uint64_t, mock_interval_t, int); + void store_in_stack_in_signed_interval_domain(uint64_t, mock_interval_t, int); + void store_in_stack_in_unsigned_interval_domain(uint64_t, mock_interval_t, int); void insert_in_registers_in_offset_domain(register_t, location_t, dist_t); void store_in_stack_in_offset_domain(uint64_t, dist_t, int); diff --git a/src/crab/type_ostream.hpp b/src/crab/type_ostream.hpp index a1f34d709..7dcbb8a8c 100644 --- a/src/crab/type_ostream.hpp +++ b/src/crab/type_ostream.hpp @@ -3,9 +3,6 @@ #pragma once -#include "string_constraints.hpp" -#include "asm_syntax.hpp" -#include "asm_ostream.hpp" #include "crab/common.hpp" #include "crab/offset_domain.hpp" diff --git a/src/crab/unsigned_interval_domain.cpp b/src/crab/unsigned_interval_domain.cpp new file mode 100644 index 000000000..b6c105d82 --- /dev/null +++ b/src/crab/unsigned_interval_domain.cpp @@ -0,0 +1,507 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT + +#include "crab/unsigned_interval_domain.hpp" +#include "boost/endian/conversion.hpp" + +namespace crab { + +bool registers_unsigned_state_t::is_bottom() const { + return m_is_bottom; +} + +bool registers_unsigned_state_t::is_top() const { + if (m_is_bottom) return false; + if (m_interval_env == nullptr) return true; + for (auto it : m_cur_def) { + if (it != nullptr) return false; + } + return true; +} + +void registers_unsigned_state_t::set_to_top() { + m_interval_env = std::make_shared(); + m_cur_def = live_registers_t{nullptr}; + m_is_bottom = false; +} + +void registers_unsigned_state_t::set_to_bottom() { + m_is_bottom = true; +} + +void registers_unsigned_state_t::insert(register_t reg, const location_t& loc, interval_t interval) { + auto reg_with_loc = reg_with_loc_t{reg, loc}; + (*m_interval_env)[reg_with_loc] = mock_interval_t{interval}; + m_cur_def[reg] = std::make_shared(reg_with_loc); +} + +std::optional registers_unsigned_state_t::find(reg_with_loc_t reg) const { + auto it = m_interval_env->find(reg); + if (it == m_interval_env->end()) return {}; + return it->second; +} + +std::optional registers_unsigned_state_t::find(register_t key) const { + if (m_cur_def[key] == nullptr) return {}; + const reg_with_loc_t& reg = *(m_cur_def[key]); + return find(reg); +} + +registers_unsigned_state_t registers_unsigned_state_t::operator|(const registers_unsigned_state_t& other) const { + if (is_bottom() || other.is_top()) { + return other; + } else if (other.is_bottom() || is_top()) { + return *this; + } + auto intervals_env = std::make_shared(); + registers_unsigned_state_t intervals_joined(intervals_env); + location_t loc = location_t(std::make_pair(label_t(-2, -2), 0)); + for (uint8_t i = 0; i < NUM_REGISTERS; i++) { + if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; + auto it1 = find(*(m_cur_def[i])); + auto it2 = other.find(*(other.m_cur_def[i])); + if (it1 && it2) { + auto interval1 = it1->to_interval(), interval2 = it2->to_interval(); + intervals_joined.insert(register_t{i}, loc, + std::move(interval1 | interval2)); + } + } + return intervals_joined; +} + +void registers_unsigned_state_t::adjust_bb_for_registers(location_t loc) { + for (uint8_t i = 0; i < NUM_REGISTERS; i++) { + if (auto it = find(register_t{i})) { + insert(register_t{i}, loc, it->to_interval()); + } + } +} + +void registers_unsigned_state_t::operator-=(register_t var) { + if (is_bottom()) return; + m_cur_def[var] = nullptr; +} + +bool stack_slots_unsigned_state_t::is_bottom() const { + return m_is_bottom; +} + +bool stack_slots_unsigned_state_t::is_top() const { + if (m_is_bottom) return false; + return m_interval_values.empty(); +} + +void stack_slots_unsigned_state_t::set_to_top() { + m_interval_values.clear(); + m_is_bottom = false; +} + +void stack_slots_unsigned_state_t::set_to_bottom() { + m_is_bottom = true; +} + +stack_slots_unsigned_state_t stack_slots_unsigned_state_t::top() { + return stack_slots_unsigned_state_t(false); +} + +std::optional stack_slots_unsigned_state_t::find(uint64_t key) const { + auto it = m_interval_values.find(key); + if (it == m_interval_values.end()) return {}; + return it->second; +} + +void stack_slots_unsigned_state_t::store(uint64_t key, mock_interval_t val, int width) { + m_interval_values[key] = std::make_pair(val, width); +} + +void stack_slots_unsigned_state_t::operator-=(uint64_t key) { + auto it = find(key); + if (it) + m_interval_values.erase(key); +} + +void stack_slots_unsigned_state_t::fill_values(const std::vector& keys, + uint64_t start, int width) { + if (keys[0] < start) { + auto type = find(keys[0]); + auto width_key = type.value().second; + store(keys[0], interval_t::top(), width_key); + } + if (keys[0] > start) { + store(start, interval_t::top(), keys[0]-start); + } + for (size_t i = 0; i < keys.size()-1; i++) { + auto type = find(keys[i]); + auto width_key = type.value().second; + if (keys[i]+width_key != keys[i+1]) { + store(keys[i]+width_key, interval_t::top(), keys[i+1]-(keys[i]+width_key)); + } + } + auto type = find(keys[keys.size()-1]); + auto width_key = type.value().second; + if (keys[keys.size()-1]+width_key < start+width) { + store(keys[keys.size()-1]+width_key, interval_t::top(), + start+width-(keys[keys.size()-1]+width_key)); + } + if (keys[keys.size()-1]+width_key > start+width) { + store(keys[keys.size()-1], interval_t::top(), width_key); + } +} + +void stack_slots_unsigned_state_t::remove_overlap(const std::vector& keys, uint64_t start, int width) { + for (auto& key : keys) { + auto type = find(key); + auto width_key = type.value().second; + if (key < start) { + int new_width = start-key; + store(key, interval_t::top(), new_width); + } + if (key+width_key > start+width) { + int new_width = key+width_key-(start+width); + store(start+width, interval_t::top(), new_width); + } + if (key >= start) *this -= key; + } +} + +static inline void join_stack(const stack_slots_unsigned_state_t& stack1, uint64_t key1, int& loc1, + const stack_slots_unsigned_state_t& stack2, uint64_t key2, int& loc2, + interval_values_stack_t& interval_values_joined) { + auto type1 = stack1.find(key1); auto type2 = stack2.find(key2); + auto& cells1 = type1.value(); auto& cells2 = type2.value(); + int width1 = cells1.second; int width2 = cells2.second; + auto interval1 = cells1.first.to_interval(); + auto interval2 = cells2.first.to_interval(); + auto top_interval_mock = mock_interval_t(interval_t::top()); + if (key1 == key2) { + if (width1 == width2) { + interval_values_joined[key1] = std::make_pair( + mock_interval_t(interval1 | interval2), width1); + loc1++; loc2++; + } + else if (width1 < width2) { + interval_values_joined[key1] = std::make_pair(top_interval_mock, width1); + loc1++; + } + else { + interval_values_joined[key1] = std::make_pair(top_interval_mock, width2); + loc2++; + } + } + else if (key1 > key2) { + if (key2+width2 > key1+width1) { + interval_values_joined[key1] = std::make_pair(top_interval_mock, width1); + loc1++; + } + else if (key2+width2 > key1) { + interval_values_joined[key1] = std::make_pair(top_interval_mock, key2+width2-key1); + loc2++; + } + else loc2++; + } + else { + join_stack(stack2, key2, loc2, stack1, key1, loc1, interval_values_joined); + } +} + +stack_slots_unsigned_state_t stack_slots_unsigned_state_t::operator|(const stack_slots_unsigned_state_t& other) const { + if (is_bottom() || other.is_top()) { + return other; + } else if (other.is_bottom() || is_top()) { + return *this; + } + interval_values_stack_t interval_values_joined; + auto stack1_keys = get_keys(); + auto stack2_keys = other.get_keys(); + int i = 0, j = 0; + while (i < static_cast(stack1_keys.size()) && j < static_cast(stack2_keys.size())) { + int key1 = stack1_keys[i], key2 = stack2_keys[j]; + join_stack(*this, key1, i, other, key2, j, interval_values_joined); + } + return stack_slots_unsigned_state_t(std::move(interval_values_joined)); +} + +size_t stack_slots_unsigned_state_t::size() const { + return m_interval_values.size(); +} + +std::vector stack_slots_unsigned_state_t::get_keys() const { + std::vector keys; + keys.reserve(size()); + + for (auto const&kv : m_interval_values) { + keys.push_back(kv.first); + } + return keys; +} + +bool unsigned_interval_domain_t::is_bottom() const { + if (m_is_bottom) return true; + return (m_registers_values.is_bottom() || m_stack_slots_values.is_bottom()); +} + +bool unsigned_interval_domain_t::is_top() const { + if (m_is_bottom) return false; + return (m_registers_values.is_top() && m_stack_slots_values.is_top()); +} + +unsigned_interval_domain_t unsigned_interval_domain_t::bottom() { + unsigned_interval_domain_t cp; + cp.set_to_bottom(); + return cp; +} + +void unsigned_interval_domain_t::set_to_bottom() { + m_is_bottom = true; + m_registers_values.set_to_bottom(); + m_stack_slots_values.set_to_bottom(); +} + +void unsigned_interval_domain_t::set_registers_to_bottom() { + m_registers_values.set_to_bottom(); +} + +void unsigned_interval_domain_t::set_registers_to_top() { + m_registers_values.set_to_top(); +} + +void unsigned_interval_domain_t::set_to_top() { + m_registers_values.set_to_top(); + m_stack_slots_values.set_to_top(); +} + +std::optional unsigned_interval_domain_t::find_in_stack(uint64_t key) const { + return m_stack_slots_values.find(key); +} + +void unsigned_interval_domain_t::adjust_bb_for_types(location_t loc) { + m_registers_values.adjust_bb_for_registers(loc); +} + +void unsigned_interval_domain_t::remove_overlap_in_stack(const std::vector& overlap, + uint64_t start_loc, int width) { + m_stack_slots_values.remove_overlap(overlap, start_loc, width); +} + +void unsigned_interval_domain_t::fill_values_in_stack(const std::vector& overlap, + uint64_t start_loc, int width) { + m_stack_slots_values.fill_values(overlap, start_loc, width); +} + +std::optional unsigned_interval_domain_t::find_interval_value(register_t reg) const { + return m_registers_values.find(reg); +} + +std::optional unsigned_interval_domain_t::find_interval_at_loc( + const reg_with_loc_t reg) const { + return m_registers_values.find(reg); +} + +void unsigned_interval_domain_t::insert_in_registers(register_t reg, location_t loc, + interval_t interval) { + m_registers_values.insert(reg, loc, interval); +} + +void unsigned_interval_domain_t::store_in_stack(uint64_t key, mock_interval_t interval, int width) { + m_stack_slots_values.store(key, interval, width); +} + +bool unsigned_interval_domain_t::operator<=(const unsigned_interval_domain_t& abs) const { + /* WARNING: The operation is not implemented yet.*/ + return true; +} + +void unsigned_interval_domain_t::operator|=(const unsigned_interval_domain_t& abs) { + unsigned_interval_domain_t tmp{abs}; + operator|=(std::move(tmp)); +} + +void unsigned_interval_domain_t::operator|=(unsigned_interval_domain_t&& abs) { + if (is_bottom()) { + *this = abs; + return; + } + *this = *this | std::move(abs); +} + +unsigned_interval_domain_t unsigned_interval_domain_t::operator|(const unsigned_interval_domain_t& other) const { + if (is_bottom() || other.is_top()) { + return other; + } + else if (other.is_bottom() || is_top()) { + return *this; + } + return unsigned_interval_domain_t(m_registers_values | other.m_registers_values, + m_stack_slots_values | other.m_stack_slots_values); +} + +unsigned_interval_domain_t unsigned_interval_domain_t::operator|(unsigned_interval_domain_t&& other) const { + if (is_bottom() || other.is_top()) { + return std::move(other); + } + else if (other.is_bottom() || is_top()) { + return *this; + } + return unsigned_interval_domain_t( + m_registers_values | std::move(other.m_registers_values), + m_stack_slots_values | std::move(other.m_stack_slots_values)); +} + +unsigned_interval_domain_t unsigned_interval_domain_t::operator&(const unsigned_interval_domain_t& abs) const { + /* WARNING: The operation is not implemented yet.*/ + return abs; +} + +unsigned_interval_domain_t unsigned_interval_domain_t::widen(const unsigned_interval_domain_t& abs, bool to_constants) { + /* WARNING: The operation is not implemented yet.*/ + return abs; +} + +unsigned_interval_domain_t unsigned_interval_domain_t::narrow(const unsigned_interval_domain_t& other) const { + /* WARNING: The operation is not implemented yet.*/ + return other; +} + +void unsigned_interval_domain_t::write(std::ostream& os) const {} + +crab::bound_t unsigned_interval_domain_t::get_loop_count_upper_bound() { + /* WARNING: The operation is not implemented yet.*/ + return crab::bound_t{crab::number_t{0}}; +} + +void unsigned_interval_domain_t::initialize_loop_counter(const label_t& head) { + /* WARNING: The operation is not implemented yet.*/ +} + +string_invariant unsigned_interval_domain_t::to_set() { + return string_invariant{}; +} + +unsigned_interval_domain_t unsigned_interval_domain_t::setup_entry() { + registers_unsigned_state_t registers(std::make_shared()); + unsigned_interval_domain_t interval(std::move(registers), stack_slots_unsigned_state_t::top()); + return interval; +} + +void unsigned_interval_domain_t::operator()(const Un& u, location_t loc) { + auto swap_endianness = [&](interval_t&& v, auto input, const auto& be_or_le) { + if (std::optional n = v.singleton()) { + if (n->fits_cast_to_int64()) { + input = (decltype(input))n.value().cast_to_sint64(); + decltype(input) output = be_or_le(input); + m_registers_values.insert(u.dst.v, loc, interval_t{number_t{output}}); + return; + } + } + m_registers_values.insert(u.dst.v, loc, interval_t::top()); + }; + + auto mock_interval = m_registers_values.find(u.dst.v); + if (!mock_interval) return; + interval_t interval = mock_interval->to_interval(); + + // Swap bytes. For 64-bit types we need the weights to fit in a + // signed int64, but for smaller types we don't want sign extension, + // so we use unsigned which still fits in a signed int64. + switch (u.op) { + case Un::Op::BE16: + swap_endianness(std::move(interval), uint16_t(0), + boost::endian::native_to_big); + break; + case Un::Op::BE32: + swap_endianness(std::move(interval), uint32_t(0), + boost::endian::native_to_big); + break; + case Un::Op::BE64: + swap_endianness(std::move(interval), uint64_t(0), + boost::endian::native_to_big); + break; + case Un::Op::LE16: + swap_endianness(std::move(interval), uint16_t(0), + boost::endian::native_to_little); + break; + case Un::Op::LE32: + swap_endianness(std::move(interval), uint32_t(0), + boost::endian::native_to_little); + break; + case Un::Op::LE64: + swap_endianness(std::move(interval), uint64_t(0), + boost::endian::native_to_little); + break; + case Un::Op::NEG: + auto reg_with_loc = reg_with_loc_t(u.dst.v, loc); + m_registers_values.insert(u.dst.v, loc, -interval); + break; + } +} + +void unsigned_interval_domain_t::operator()(const LoadMapFd &u, location_t loc) { + // nothing to do here +} + +void unsigned_interval_domain_t::operator()(const Packet& u, location_t loc) { + // nothing to do here +} + +void unsigned_interval_domain_t::operator()(const Assume& s, location_t loc) { + // nothing to do here +} + +bool unsigned_interval_domain_t::load_from_stack(register_t reg, uint64_t offset, location_t loc) { + if (auto loaded = m_stack_slots_values.find(offset)) { + m_registers_values.insert(reg, loc, (*loaded).first.to_interval()); + return true; + } + return false; +} + +void unsigned_interval_domain_t::store_in_stack(const Mem& b, uint64_t offset, int width) { + if (std::holds_alternative(b.value)) { + auto target_reg = std::get(b.value); + if (auto targetreg_mock_interval = m_registers_values.find(target_reg.v)) { + auto targetreg_type = targetreg_mock_interval->to_interval(); + m_stack_slots_values.store(offset, targetreg_type, width); + } + } + else { + auto imm = (uint64_t)std::get(b.value).v; + auto targetreg_type = interval_t{number_t{imm}}; + m_stack_slots_values.store(offset, targetreg_type, width); + } +} + +void unsigned_interval_domain_t::operator()(const Undefined& u, location_t loc) { + // nothing to do here +} + +void unsigned_interval_domain_t::operator()(const Bin& b, location_t loc) { + // nothing to do here +} + +void unsigned_interval_domain_t::operator()(const Call&, location_t loc) { + // nothing to do here +} + +void unsigned_interval_domain_t::operator()(const Exit&, location_t loc) { + // nothing to do here +} + +void unsigned_interval_domain_t::operator()(const Jmp&, location_t loc) { + // nothing to do here +} + +void unsigned_interval_domain_t::operator()(const Mem&, location_t loc) { + // nothing to do here +} + +void unsigned_interval_domain_t::operator()(const Assert&, location_t loc) { + // nothing to do here +} + +void unsigned_interval_domain_t::operator()(const basic_block_t& bb, int print) { + // nothing to do here +} + +void unsigned_interval_domain_t::set_require_check(check_require_func_t f) {} + +} // namespace crab diff --git a/src/crab/unsigned_interval_domain.hpp b/src/crab/unsigned_interval_domain.hpp new file mode 100644 index 000000000..6620810c3 --- /dev/null +++ b/src/crab/unsigned_interval_domain.hpp @@ -0,0 +1,143 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT + +#pragma once + +#include "crab/common.hpp" + +namespace crab { + +using live_registers_t = std::array, NUM_REGISTERS>; +using global_interval_env_t = std::unordered_map; + +class registers_unsigned_state_t { + + live_registers_t m_cur_def; + std::shared_ptr m_interval_env; + bool m_is_bottom = false; + + public: + bool is_bottom() const; + bool is_top() const; + void set_to_bottom(); + void set_to_top(); + std::optional find(reg_with_loc_t reg) const; + std::optional find(register_t key) const; + void insert(register_t, const location_t&, interval_t); + void operator-=(register_t); + registers_unsigned_state_t operator|(const registers_unsigned_state_t& other) const; + registers_unsigned_state_t(bool is_bottom = false) : m_interval_env(nullptr), + m_is_bottom(is_bottom) {} + explicit registers_unsigned_state_t(live_registers_t&& vars, + std::shared_ptr interval_env, bool is_bottom = false) + : m_cur_def(std::move(vars)), m_interval_env(interval_env), m_is_bottom(is_bottom) {} + explicit registers_unsigned_state_t(std::shared_ptr interval_env, + bool is_bottom = false) + : m_interval_env(interval_env), m_is_bottom(is_bottom) {} + void adjust_bb_for_registers(location_t); +}; + +using interval_cells_t = std::pair; // intervals with width +using interval_values_stack_t = std::map; + +class stack_slots_unsigned_state_t { + + interval_values_stack_t m_interval_values; + bool m_is_bottom = false; + + public: + bool is_bottom() const; + bool is_top() const; + void set_to_bottom(); + void set_to_top(); + static stack_slots_unsigned_state_t top(); + std::optional find(uint64_t) const; + void store(uint64_t, mock_interval_t, int); + void operator-=(uint64_t); + stack_slots_unsigned_state_t operator|(const stack_slots_unsigned_state_t& other) const; + stack_slots_unsigned_state_t(bool is_bottom = false) : m_is_bottom(is_bottom) {} + explicit stack_slots_unsigned_state_t(interval_values_stack_t&& interval_values, bool is_bottom = false) + : m_interval_values(std::move(interval_values)), m_is_bottom(is_bottom) {} + std::vector get_keys() const; + size_t size() const; + void remove_overlap(const std::vector&, uint64_t, int); + void fill_values(const std::vector&, uint64_t, int); +}; + +class unsigned_interval_domain_t final { + registers_unsigned_state_t m_registers_values; + stack_slots_unsigned_state_t m_stack_slots_values; + std::vector m_errors; + bool m_is_bottom = false; + + public: + + unsigned_interval_domain_t() = default; + unsigned_interval_domain_t(unsigned_interval_domain_t&& o) = default; + unsigned_interval_domain_t(const unsigned_interval_domain_t& o) = default; + explicit unsigned_interval_domain_t(registers_unsigned_state_t&& consts_regs, + stack_slots_unsigned_state_t&& interval_stack_slots, bool is_bottom = false) : + m_registers_values(std::move(consts_regs)), m_stack_slots_values(std::move(interval_stack_slots)), + m_is_bottom(is_bottom) {} + unsigned_interval_domain_t& operator=(unsigned_interval_domain_t&& o) = default; + unsigned_interval_domain_t& operator=(const unsigned_interval_domain_t& o) = default; + // eBPF initialization: R1 points to ctx, R10 to stack, etc. + static unsigned_interval_domain_t setup_entry(); + // bottom/top + static unsigned_interval_domain_t bottom(); + void set_to_top(); + void set_to_bottom(); + void set_registers_to_bottom(); + void set_registers_to_top(); + bool is_bottom() const; + bool is_top() const; + // inclusion + bool operator<=(const unsigned_interval_domain_t& other) const; + // join + void operator|=(const unsigned_interval_domain_t& abs); + void operator|=(unsigned_interval_domain_t&& abs); + unsigned_interval_domain_t operator|(const unsigned_interval_domain_t& other) const; + unsigned_interval_domain_t operator|(unsigned_interval_domain_t&& abs) const; + // meet + unsigned_interval_domain_t operator&(const unsigned_interval_domain_t& other) const; + // widening + unsigned_interval_domain_t widen(const unsigned_interval_domain_t& other, bool); + // narrowing + unsigned_interval_domain_t narrow(const unsigned_interval_domain_t& other) const; + //forget + void operator-=(register_t reg) { m_registers_values -= reg; } + + //// abstract transformers + void operator()(const Undefined&, location_t loc = boost::none); + void operator()(const Bin&, location_t loc = boost::none); + void operator()(const Un&, location_t loc = boost::none); + void operator()(const LoadMapFd&, location_t loc = boost::none); + void operator()(const Call&, location_t loc = boost::none); + void operator()(const Exit&, location_t loc = boost::none); + void operator()(const Jmp&, location_t loc = boost::none); + void operator()(const Mem&, location_t loc = boost::none); + void operator()(const Packet&, location_t loc = boost::none); + void operator()(const Assume&, location_t loc = boost::none); + void operator()(const Assert&, location_t loc = boost::none); + void operator()(const basic_block_t& bb, int print = 0); + void write(std::ostream& os) const; + crab::bound_t get_loop_count_upper_bound(); + void initialize_loop_counter(const label_t&); + string_invariant to_set(); + void set_require_check(check_require_func_t f); + + std::optional find_interval_value(register_t) const; + std::optional find_interval_at_loc(const reg_with_loc_t reg) const; + std::optional find_in_stack(uint64_t) const; + void insert_in_registers(register_t, location_t, interval_t); + void store_in_stack(uint64_t, mock_interval_t, int); + void adjust_bb_for_types(location_t); + void remove_overlap_in_stack(const std::vector&, uint64_t, int); + void fill_values_in_stack(const std::vector&, uint64_t, int); + [[nodiscard]] std::vector& get_errors() { return m_errors; } + void reset_errors() { m_errors.clear(); } + bool load_from_stack(register_t, uint64_t, location_t); + void store_in_stack(const Mem&, uint64_t, int); +}; // end unsigned_interval_domain_t + +} // namespace crab From f71651754be4451bc4134a14b55238f90dfa43ce Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 26 Dec 2023 06:13:35 -0500 Subject: [PATCH 148/373] Better handling for bottom/top for multiple domains Signed-off-by: Ameer Hamza --- src/crab/offset_domain.cpp | 17 +++++++++++++---- src/crab/region_domain.cpp | 3 +++ src/crab/type_domain.cpp | 11 +++++++++-- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index f9e88229a..a9859ec42 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -37,7 +37,8 @@ bool dist_t::is_top() const { } bool dist_t::is_bottom() const { - return m_is_bottom; + if (m_is_bottom) return true; + return (m_slack == -1 && m_dist.is_bottom()); } void dist_t::set_to_top() { @@ -71,7 +72,8 @@ bool inequality_t::is_top() const { } bool inequality_t::is_bottom() const { - return m_is_bottom; + if (m_is_bottom) return true; + return (m_slack == -1 && m_value.is_bottom()); } void inequality_t::set_to_top() { @@ -103,7 +105,8 @@ bool equality_t::is_top() const { } bool equality_t::is_bottom() const { - return m_is_bottom; + if (m_is_bottom) return true; + return (m_lhs.is_bottom() || m_rhs.is_bottom()); } void equality_t::set_to_top() { @@ -466,14 +469,20 @@ void offset_domain_t::set_to_top() { m_reg_state.set_to_top(); m_stack_state.set_to_top(); m_extra_constraints.set_to_top(); + m_is_bottom = false; } void offset_domain_t::set_to_bottom() { m_is_bottom = true; + m_reg_state.set_to_bottom(); + m_stack_state.set_to_bottom(); + m_extra_constraints.set_to_bottom(); } bool offset_domain_t::is_bottom() const { - return m_is_bottom; + if (m_is_bottom) return true; + return (m_reg_state.is_bottom() || m_stack_state.is_bottom() + || m_extra_constraints.is_bottom()); } bool offset_domain_t::is_top() const { diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index 59a9c4a77..ae075695b 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -344,9 +344,12 @@ region_domain_t region_domain_t::bottom() { void region_domain_t::set_to_bottom() { m_is_bottom = true; + m_stack.set_to_bottom(); + m_registers.set_to_bottom(); } void region_domain_t::set_to_top() { + m_is_bottom = false; m_stack.set_to_top(); m_registers.set_to_top(); } diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index e99c23b7f..7c0cfe9b7 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -7,7 +7,8 @@ namespace crab { bool type_domain_t::is_bottom() const { - return m_is_bottom; + if (m_is_bottom) return true; + return (m_region.is_bottom() || m_offset.is_bottom() || m_interval.is_bottom()); } bool type_domain_t::is_top() const { @@ -23,9 +24,13 @@ type_domain_t type_domain_t::bottom() { void type_domain_t::set_to_bottom() { m_is_bottom = true; + m_region.set_to_bottom(); + m_offset.set_to_bottom(); + m_interval.set_to_bottom(); } void type_domain_t::set_to_top() { + m_is_bottom = false; m_region.set_to_top(); m_offset.set_to_top(); m_interval.set_to_top(); @@ -213,7 +218,9 @@ void type_domain_t::operator()(const Exit& u, location_t loc) { // nothing to do here } -void type_domain_t::operator()(const Jmp& u, location_t loc) {} +void type_domain_t::operator()(const Jmp& u, location_t loc) { + // nothing to do here +} void type_domain_t::operator()(const Packet& u, location_t loc) { m_region(u, loc); From 32d0ce0d926148a8614fc289057755f3d309b17f Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 26 Dec 2023 06:15:28 -0500 Subject: [PATCH 149/373] Refactoring, and simplifying a check in stack Some cleanup of redundant code and transformers; Commenting some code related to assertions in the code, since those assertions are commented for now, activate later; Simplifying a check related to stack all numeric in ValidMapKeyValue; Signed-off-by: Ameer Hamza --- src/crab/offset_domain.cpp | 39 --------------------------------- src/crab/offset_domain.hpp | 11 ---------- src/crab/region_domain.cpp | 24 --------------------- src/crab/region_domain.hpp | 8 ------- src/crab/type_domain.cpp | 44 +++++++++++++++----------------------- src/crab/type_domain.hpp | 2 -- 6 files changed, 17 insertions(+), 111 deletions(-) diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index a9859ec42..7704bbb8d 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -555,9 +555,6 @@ offset_domain_t offset_domain_t::narrow(const offset_domain_t& other) const { return other; } -//forget -void offset_domain_t::operator-=(variable_t var) {} - void offset_domain_t::write(std::ostream& os) const {} std::string offset_domain_t::domain_name() const { @@ -812,42 +809,6 @@ void offset_domain_t::operator()(const Packet& u, location_t loc) { m_reg_state.scratch_caller_saved_registers(); } -void offset_domain_t::operator()(const ValidDivisor& u, location_t loc) { - /* WARNING: This operation is not implemented yet. */ -} - -void offset_domain_t::operator()(const ValidAccess& u, location_t loc) { - // nothing to do here -} - -void offset_domain_t::operator()(const Comparable& u, location_t loc) { - // nothing to do here -} - -void offset_domain_t::operator()(const Addable& u, location_t loc) { - // nothing to do here -} - -void offset_domain_t::operator()(const ValidStore& u, location_t loc) { - // nothing to do here -} - -void offset_domain_t::operator()(const TypeConstraint& u, location_t loc) { - // nothing to do here -} - -void offset_domain_t::operator()(const ValidSize& u, location_t loc) { - /* WARNING: This operation is not implemented yet. */ -} - -void offset_domain_t::operator()(const ValidMapKeyValue& u, location_t loc) { - /* WARNING: This operation is not implemented yet. */ -} - -void offset_domain_t::operator()(const ZeroCtxOffset&, location_t loc) { - // nothing to do here -} - bool offset_domain_t::lower_bound_satisfied(const dist_t& dist, int offset) const { auto meta_limit = m_extra_constraints.get_meta_limit(); auto end_limit = m_extra_constraints.get_end_limit(); diff --git a/src/crab/offset_domain.hpp b/src/crab/offset_domain.hpp index afc319890..e116af30f 100644 --- a/src/crab/offset_domain.hpp +++ b/src/crab/offset_domain.hpp @@ -230,7 +230,6 @@ class offset_domain_t final { // narrowing offset_domain_t narrow(const offset_domain_t& other) const; //forget - void operator-=(variable_t var); void operator-=(register_t reg) { m_reg_state -= reg; } //// abstract transformers @@ -247,16 +246,6 @@ class offset_domain_t final { void operator()(const Packet&, location_t loc = boost::none); void operator()(const Assume&, location_t loc = boost::none); void operator()(const Assert&, location_t loc = boost::none); - void operator()(const ValidAccess&, location_t loc = boost::none); - void operator()(const Comparable&, location_t loc = boost::none); - void operator()(const Addable&, location_t loc = boost::none); - void operator()(const ValidStore&, location_t loc = boost::none); - void operator()(const TypeConstraint&, location_t loc = boost::none); - void operator()(const ValidSize&, location_t loc = boost::none); - void operator()(const ValidMapKeyValue&, location_t loc = boost::none); - void operator()(const ZeroCtxOffset&, location_t loc = boost::none); - void operator()(const ValidDivisor&, location_t loc = boost::none); - void operator()(const FuncConstraint& s, location_t loc = boost::none) {}; void operator()(const IncrementLoopCounter&, location_t loc = boost::none) {}; void operator()(const basic_block_t& bb, int print = 0); void write(std::ostream& os) const; diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index ae075695b..5b2a0e343 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -531,14 +531,6 @@ void region_domain_t::operator()(const Assert& u, location_t loc) { // nothing to do here } -void region_domain_t::operator()(const Comparable& u, location_t loc) { - // nothing to do here -} - -void region_domain_t::operator()(const ValidMapKeyValue& u, location_t loc) { - // nothing to do here -} - void region_domain_t::operator()(const ZeroCtxOffset& u, location_t loc) { auto maybe_ptr_or_mapfd = m_registers.find(u.reg.v); if (is_ctx_ptr(maybe_ptr_or_mapfd)) { @@ -557,14 +549,6 @@ void region_domain_t::operator()(const Un& u, location_t loc) { m_registers -= u.dst.v; } -void region_domain_t::operator()(const ValidDivisor& u, location_t loc) { - // nothing to do here -} - -void region_domain_t::operator()(const ValidSize& u, location_t loc) { - /* WARNING: The operation is not implemented yet.*/ -} - // Get the start and end of the range of possible map fd values. // In the future, it would be cleaner to use a set rather than an interval // for map fds. @@ -763,10 +747,6 @@ void region_domain_t::operator()(const Packet& u, location_t loc) { m_registers.scratch_caller_saved_registers(); } -void region_domain_t::operator()(const Addable &u, location_t loc) { - // nothing to do here -} - void region_domain_t::check_valid_access(const ValidAccess &s, int width) { bool is_comparison_check = s.width == (Value)Imm{0}; @@ -823,10 +803,6 @@ void region_domain_t::operator()(const ValidAccess &s, location_t loc) { // nothing to do here } -void region_domain_t::operator()(const ValidStore& u, location_t loc) { - // nothing to do here -} - region_domain_t&& region_domain_t::setup_entry(bool init_r1) { std::shared_ptr ctx diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index 870f38f9c..cb2f912c0 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -129,7 +129,6 @@ class region_domain_t final { // narrowing region_domain_t narrow(const region_domain_t& other) const; //forget - void operator-=(crab::variable_t var); void operator-=(register_t var) { m_registers -= var; } //// abstract transformers @@ -147,15 +146,8 @@ class region_domain_t final { void operator()(const Assume&, location_t loc = boost::none); void operator()(const Assert&, location_t loc = boost::none); void operator()(const ValidAccess&, location_t loc = boost::none); - void operator()(const Comparable&, location_t loc = boost::none); - void operator()(const Addable&, location_t loc = boost::none); - void operator()(const ValidStore&, location_t loc = boost::none); void operator()(const TypeConstraint&, location_t loc = boost::none); - void operator()(const ValidSize&, location_t loc = boost::none); - void operator()(const ValidMapKeyValue&, location_t loc = boost::none); void operator()(const ZeroCtxOffset&, location_t loc = boost::none); - void operator()(const ValidDivisor&, location_t loc = boost::none); - void operator()(const FuncConstraint& s, location_t loc = boost::none) {}; void operator()(const IncrementLoopCounter&, location_t loc = boost::none); void operator()(const basic_block_t& bb, int print = 0); void write(std::ostream& o) const {} diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 7c0cfe9b7..c49c2daee 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -339,8 +339,8 @@ void type_domain_t::operator()(const ValidAccess& s, location_t loc) { } void type_domain_t::operator()(const TypeConstraint& s, location_t loc) { - auto reg_type = m_region.find_ptr_or_mapfd_type(s.reg.v); - auto mock_interval_type = m_interval.find_interval_value(s.reg.v); + //auto reg_type = m_region.find_ptr_or_mapfd_type(s.reg.v); + //auto mock_interval_type = m_interval.find_interval_value(s.reg.v); //assert(!reg_type.has_value() || !mock_interval_type.has_value()); m_region(s, loc); } @@ -353,8 +353,8 @@ void type_domain_t::operator()(const Comparable& u, location_t loc) { auto maybe_ptr_or_mapfd1 = m_region.find_ptr_or_mapfd_type(u.r1.v); auto maybe_ptr_or_mapfd2 = m_region.find_ptr_or_mapfd_type(u.r2.v); - auto maybe_num_type1 = m_interval.find_interval_value(u.r1.v); - auto maybe_num_type2 = m_interval.find_interval_value(u.r2.v); + //auto maybe_num_type1 = m_interval.find_interval_value(u.r1.v); + //auto maybe_num_type2 = m_interval.find_interval_value(u.r2.v); //assert(!maybe_ptr_or_mapfd1.has_value() || !maybe_num_type1.has_value()); //assert(!maybe_ptr_or_mapfd2.has_value() || !maybe_num_type2.has_value()); if (maybe_ptr_or_mapfd1 && maybe_ptr_or_mapfd2) { @@ -375,7 +375,7 @@ void type_domain_t::operator()(const Comparable& u, location_t loc) { void type_domain_t::operator()(const Addable& u, location_t loc) { auto maybe_ptr_or_mapfd_ptr = m_region.find_ptr_or_mapfd_type(u.ptr.v); auto maybe_ptr_or_mapfd_num = m_region.find_ptr_or_mapfd_type(u.num.v); - auto maybe_num_type_ptr = m_interval.find_interval_value(u.ptr.v); + //auto maybe_num_type_ptr = m_interval.find_interval_value(u.ptr.v); auto maybe_num_type_num = m_interval.find_interval_value(u.num.v); //assert(!maybe_ptr_or_mapfd_ptr.has_value() || !maybe_num_type_ptr.has_value()); //assert(!maybe_ptr_or_mapfd_num.has_value() || !maybe_num_type_num.has_value()); @@ -392,7 +392,7 @@ void type_domain_t::operator()(const Addable& u, location_t loc) { void type_domain_t::operator()(const ValidStore& u, location_t loc) { auto maybe_ptr_or_mapfd_mem = m_region.find_ptr_or_mapfd_type(u.mem.v); auto maybe_ptr_or_mapfd_val = m_region.find_ptr_or_mapfd_type(u.val.v); - auto maybe_num_type_mem = m_interval.find_interval_value(u.mem.v); + //auto maybe_num_type_mem = m_interval.find_interval_value(u.mem.v); auto maybe_num_type_val = m_interval.find_interval_value(u.val.v); //assert(!maybe_ptr_or_mapfd_mem.has_value() || !maybe_num_type_mem.has_value()); //assert(!maybe_ptr_or_mapfd_val.has_value() || !maybe_num_type_val.has_value()); @@ -447,28 +447,19 @@ void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc) { if (maybe_ptr_or_mapfd_basereg && maybe_mapfd) { auto mapfd = maybe_mapfd.value(); if (is_mapfd_type(maybe_mapfd)) { - auto ptr_or_mapfd_basereg = maybe_ptr_or_mapfd_basereg.value(); - if (std::holds_alternative(ptr_or_mapfd_basereg)) { - auto ptr_with_off = std::get(ptr_or_mapfd_basereg); - if (ptr_with_off.get_region() == region_t::T_STACK) { - auto offset_singleton = ptr_with_off.get_offset().to_interval().singleton(); - if (!offset_singleton) { - //std::cout << "type error: reading the stack at an unknown offset\n"; - m_errors.push_back("reading the stack at an unknown offset"); - return; - } - auto offset_to_check = (uint64_t)offset_singleton.value(); - auto it = m_interval.all_numeric_in_stack(offset_to_check, width); - if (it) return; - auto it2 = m_region.find_in_stack(offset_to_check); - if (it2) { - //std::cout << "type error: map update with a non-numerical value\n"; - m_errors.push_back("map update with a non-numerical value"); - } + if (is_stack_ptr(maybe_ptr_or_mapfd_basereg)) { + auto ptr_with_off = std::get(*maybe_ptr_or_mapfd_basereg); + auto offset_singleton = ptr_with_off.get_offset().to_interval().singleton(); + if (!offset_singleton) { + //std::cout << "type error: reading the stack at an unknown offset\n"; + m_errors.push_back("reading the stack at an unknown offset"); return; } + auto offset_to_check = (uint64_t)offset_singleton.value(); + auto it = m_interval.all_numeric_in_stack(offset_to_check, width); + if (it) return; } - else if (std::holds_alternative(ptr_or_mapfd_basereg)) { + else if (is_packet_ptr(maybe_ptr_or_mapfd_basereg)) { if (m_offset.check_packet_access(u.access_reg, width, 0, true)) return; } else { @@ -476,8 +467,7 @@ void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc) { } } } - //std::cout << "type error: valid map key value assertion failed\n"; - m_errors.push_back("valid map key value assertion failed"); + m_errors.push_back("map update with a non-numerical value"); } void type_domain_t::operator()(const ZeroCtxOffset& u, location_t loc) { diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index ddd7972ec..79442d52d 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -49,8 +49,6 @@ class type_domain_t final { type_domain_t widen(const type_domain_t& other, bool); // narrowing type_domain_t narrow(const type_domain_t& other) const; - //forget - void operator-=(crab::variable_t var); //// abstract transformers void operator()(const Undefined&, location_t loc = boost::none); From a88419a77108704fddede6e6959acdc1f78c4db1 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Tue, 26 Dec 2023 06:20:50 -0500 Subject: [PATCH 150/373] Avoid setting to top when handling assume for shared pointers Signed-off-by: Ameer Hamza --- src/crab/region_domain.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index 5b2a0e343..f85b92f14 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -498,7 +498,7 @@ void region_domain_t::assume_cst(Condition::Op op, ptr_with_off_t&& shared_ptr, if (imm == 0) { if (op == Condition::Op::EQ) { if (nullness == nullness_t::_NULL) { - m_registers.set_to_top(); + //m_registers.set_to_top(); } else if (nullness == nullness_t::NOT_NULL) { m_registers.set_to_bottom(); @@ -510,7 +510,7 @@ void region_domain_t::assume_cst(Condition::Op op, ptr_with_off_t&& shared_ptr, } else if (op == Condition::Op::NE) { if (nullness == nullness_t::NOT_NULL) { - m_registers.set_to_top(); + //m_registers.set_to_top(); } else if (nullness == nullness_t::_NULL) { m_registers.set_to_bottom(); From 7ac4485913b1bd2d9f2bacc182c84b93bc579991 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 25 Mar 2024 18:39:31 -0400 Subject: [PATCH 151/373] Added a definition of symbols for offset domain Added implementation to represent begin, end, meta, and other slack variables a_0, a_1, ... for offset domain Signed-off-by: Ameer Hamza --- src/crab/symbol.hpp | 49 +++++++++++++++++++++++++++++++++++++ src/crab/symbol_factory.cpp | 34 +++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 src/crab/symbol.hpp create mode 100644 src/crab/symbol_factory.cpp diff --git a/src/crab/symbol.hpp b/src/crab/symbol.hpp new file mode 100644 index 000000000..efd645102 --- /dev/null +++ b/src/crab/symbol.hpp @@ -0,0 +1,49 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT +#pragma once + +#include +#include + +namespace crab { + +using index_t = int64_t; + +class symbol_t final { + index_t _id; + + explicit symbol_t(index_t id) : _id(id) {} + + public: + [[nodiscard]] std::size_t hash() const { return (size_t)_id; } + bool operator==(symbol_t o) const { return _id == o._id; } + bool operator!=(symbol_t o) const { return (!(operator==(o))); } + bool operator<(symbol_t o) const { return _id < o._id; } + bool operator>(symbol_t o) const { return _id > o._id; } + + private: + static int64_t count; + + public: + static symbol_t begin() { return symbol_t(0); } + static symbol_t end() { return symbol_t(1); } + static symbol_t meta() { return symbol_t(2); } + static symbol_t nu() { return symbol_t(3); } + static symbol_t pkt_symbol() { return symbol_t(4); } + static symbol_t make() { count++; return symbol_t(count); } + bool is_nu() const { return *this == symbol_t::nu(); } + bool is_pkt_symbol() const { return *this == symbol_t::pkt_symbol(); } + bool is_meta() const { return *this == symbol_t::meta(); } + bool is_end() const { return *this == symbol_t::end(); } + bool is_begin() const { return *this == symbol_t::begin(); } + bool is_slack() const { return _id >= 5; } + void write(std::ostream& o) const; + + struct Hasher { + std::size_t operator()(const symbol_t& s) const { return s.hash(); } + }; +}; // class symbol_t + +std::ostream& operator<<(std::ostream& o, const symbol_t& s); + +} // namespace crab diff --git a/src/crab/symbol_factory.cpp b/src/crab/symbol_factory.cpp new file mode 100644 index 000000000..b931dcd7a --- /dev/null +++ b/src/crab/symbol_factory.cpp @@ -0,0 +1,34 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT +/* + * Factories for symbol names. + */ + +#include "crab/symbol.hpp" + +namespace crab { + +int64_t symbol_t::count = 4; + +void symbol_t::write(std::ostream& o) const { + if (is_begin()) { + o << "begin"; + } else if (is_end()) { + o << "end"; + } else if (is_meta()) { + o << "meta"; + } else if (is_nu()) { + o << "v"; + } else if (is_pkt_symbol()) { + o << "i"; + } else { + o << "a_" << ((int)_id-4); + } +} + +std::ostream& operator<<(std::ostream& o, const symbol_t& s) { + s.write(o); + return o; +} + +} // namespace crab From e5096fa203842d87805fad14b11b47214f78bc64 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 25 Mar 2024 18:40:51 -0400 Subject: [PATCH 152/373] Added a definition for expression for offset domain Signed-off-by: Ameer Hamza --- src/crab/expression.cpp | 132 ++++++++++++++++++++++++++++++++++++++++ src/crab/expression.hpp | 78 ++++++++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 src/crab/expression.cpp create mode 100644 src/crab/expression.hpp diff --git a/src/crab/expression.cpp b/src/crab/expression.cpp new file mode 100644 index 000000000..a47b8df38 --- /dev/null +++ b/src/crab/expression.cpp @@ -0,0 +1,132 @@ + +#include "expression.hpp" + +namespace crab { + +std::ostream& operator<<(std::ostream& o, const expression_t& e) { + e.write(o); + return o; +} + +bool expression_t::operator==(const expression_t &other) const { + return _symbol_terms == other._symbol_terms && _interval == other._interval; +} + +bool expression_t::operator<(const expression_t &other) const { + return _symbol_terms == other._symbol_terms && _interval.ub() < other._interval.lb(); +} + +bool expression_t::operator<=(const expression_t &other) const { + return _symbol_terms == other._symbol_terms && _interval.ub() <= other._interval.lb(); +} + +bool expression_t::operator>(const expression_t &other) const { + return _symbol_terms == other._symbol_terms && _interval.lb() > other._interval.ub(); +} + +bool expression_t::is_singleton() const { + return _symbol_terms.size() == 1 && _interval == interval_t{0}; +} + +bool expression_t::contains(const symbol_t &s) const { + return _symbol_terms.find(s) != _symbol_terms.end(); +} + +symbol_t expression_t::get_singleton() const { + if (!is_singleton()) { + throw std::runtime_error("expression does not contain a single symbol"); + } + return _symbol_terms.begin()->first; +} + +static void insert(symbol_terms_t &terms, const std::pair& kv) { + if (terms.find(kv.first) != terms.end()) { + terms[kv.first] += kv.second; + } else { + terms[kv.first] = kv.second; + } +} + +expression_t expression_t::substitute(const symbol_t &from, const expression_t &to) const { + expression_t result = *this; + auto it = result._symbol_terms.find(from); + if (it != result._symbol_terms.end()) { + result._symbol_terms.erase(it); + result = result + to; + } + return result; +} + +expression_t expression_t::negate() const { + symbol_terms_t new_terms; + for (const auto &term : _symbol_terms) { + new_terms[term.first] = -term.second; + } + return expression_t(new_terms, -_interval); +} + +expression_t expression_t::operator+(const expression_t &other) const { + auto new_terms = _symbol_terms; + for (const auto &term : other._symbol_terms) { + insert(new_terms, term); + } + return expression_t(new_terms, _interval + other._interval); +} + +expression_t expression_t::operator+(interval_t interval) const { + return expression_t(_symbol_terms, _interval + interval); +} + +expression_t expression_t::operator+(int n) const { + return operator+(interval_t{n}); +} + +expression_t expression_t::operator|(const expression_t &other) const { + if (_symbol_terms.size() != other._symbol_terms.size()) { + return expression_t(); + } + symbol_terms_t new_terms; + for (auto &term : _symbol_terms) { + auto it = other._symbol_terms.find(term.first); + if (it == other._symbol_terms.end()) { + // we need to know what is the other slack, but it's not accesible directly + //if (term.first.is_slack() && it->first.is_slack()) { + auto new_slack = symbol_t::make(); + new_terms[new_slack] = 1; + //} + //else { + // return expression_t(); + //} + } + else { + // TODO: some complex logic is needed here + new_terms[term.first] = term.second; + } + } + interval_t new_interval = _interval | other._interval; + return expression_t(new_terms, new_interval); +} + +void expression_t::write(std::ostream &o) const { + size_t i = 0; + for (const auto &term : _symbol_terms) { + if (term.second != 1) { + o << (int)term.second << " * "; + } + o << term.first; + if (i < _symbol_terms.size() - 1) { + o << " + "; + } + i++; + } + if (auto s = _interval.singleton()) { + if ((int)*s != 0 || _symbol_terms.empty()) { + if (!_symbol_terms.empty()) o << " + "; + o << *s; + } + } else { + o << " + " << _interval; + } +} + +} // namespace crab diff --git a/src/crab/expression.hpp b/src/crab/expression.hpp new file mode 100644 index 000000000..cc17b4642 --- /dev/null +++ b/src/crab/expression.hpp @@ -0,0 +1,78 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT +#pragma once + +#include +#include +#include "symbol.hpp" +#include "interval.hpp" + +namespace crab { +// An expression is of form: Ax + By + Cz + ... + I. +// x, y, z, ... are symbols, and A, B, C, ... are coefficients. +// I is an interval. + +using symbol_terms_t = std::map; +class expression_t { + private: + symbol_terms_t _symbol_terms; + interval_t _interval; + + public: + expression_t() : _interval(interval_t::top()) {}; + expression_t(symbol_terms_t symbol_terms, interval_t interval = interval_t{0}) + : _symbol_terms(symbol_terms), _interval(interval) {} + expression_t(symbol_t symbol) : _interval(interval_t{0}) { + _symbol_terms[symbol] = 1; + } + expression_t(interval_t interval) + : _symbol_terms(symbol_terms_t{}), _interval(interval) {} + expression_t(int n) + : _symbol_terms(symbol_terms_t{}), _interval(interval_t{n}) {} + bool operator==(const expression_t &other) const; + bool operator<(const expression_t &other) const; + bool operator<=(const expression_t &other) const; + bool operator>(const expression_t &other) const; + void operator-=(const symbol_t& s) { _symbol_terms.erase(s); } + bool is_constant() const { return _symbol_terms.empty(); } + expression_t negate() const; + expression_t operator+(const expression_t &other) const; + expression_t operator+(interval_t) const; + expression_t operator+(int n) const; + expression_t operator|(const expression_t &other) const; + void write(std::ostream &o) const; + symbol_terms_t get_symbol_terms() const { return _symbol_terms; } + bool is_singleton() const; + expression_t substitute(const symbol_t &, const expression_t &) const; + bool contains(const symbol_t &s) const; + symbol_t get_singleton() const; + interval_t get_interval() const { return _interval; } + + static expression_t begin() { + return expression_t({std::make_pair(symbol_t::begin(), 1)}); + } + + static expression_t end() { + return expression_t({std::make_pair(symbol_t::end(), 1)}); + } + + static expression_t meta() { + return expression_t({std::make_pair(symbol_t::meta(), 1)}); + } + + static expression_t nu() { + return expression_t({std::make_pair(symbol_t::nu(), 1)}); + } + + static expression_t pkt_symbol() { + return expression_t({std::make_pair(symbol_t::pkt_symbol(), 1)}); + } + + static expression_t make_slack() { + return expression_t({std::make_pair(symbol_t::make(), 1)}); + } +}; + +std::ostream& operator<<(std::ostream &, const expression_t&); + +} // namespace crab From 9d2c0f86beb85da69ce6fd63bbc522ffc6f52ea3 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 25 Mar 2024 18:41:37 -0400 Subject: [PATCH 153/373] Added a definition for constraint for offset domain Signed-off-by: Ameer Hamza --- src/crab/constraint.cpp | 50 +++++++++++++++++++++++++++++++++++++++++ src/crab/constraint.hpp | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 src/crab/constraint.cpp create mode 100644 src/crab/constraint.hpp diff --git a/src/crab/constraint.cpp b/src/crab/constraint.cpp new file mode 100644 index 000000000..4fd3eba22 --- /dev/null +++ b/src/crab/constraint.cpp @@ -0,0 +1,50 @@ + +#include "constraint.hpp" + +namespace crab { + +std::ostream &operator<<(std::ostream &o, const constraint_t &c) { + c.write(o); + return o; +} + +void constraint_t::simplify(std::shared_ptr slacks) { + auto neg = _expression_rhs.negate(); + _expression_lhs = _expression_lhs + _expression_rhs.negate(); + _expression_rhs = expression_t(0); + for (auto &e : _expression_lhs.get_symbol_terms()) { + if (e.second == 0) { + _expression_lhs -= e.first; + } + else if (e.first.is_slack()) { + auto val = slacks->find(e.first); + if (val != slacks->end()) { + _expression_lhs -= e.first; + _expression_lhs = _expression_lhs + + val->second.to_interval() * interval_t{(int)e.second}; + } + } + } +} + +bool constraint_t::is_bottom(std::shared_ptr slacks) { + simplify(slacks); + if (!_expression_lhs.is_constant()) { + return false; + } + auto constant = _expression_lhs.get_interval(); + //auto interval_0 = _expression_rhs.get_interval(); + return (bound_t{number_t{0}} < constant.lb()); + //return (constant.ub() > interval_0.lb()) && ((constant & interval_0) == interval_t::bottom()); +} + +constraint_t constraint_t::negate() const { + // neg(x <= y) -> x > y -> y < x -> y <= x - 1 + return constraint_t(_expression_rhs, _expression_lhs + (-1)); +} + +void constraint_t::write(std::ostream &o) const { + o << _expression_lhs << " <= " << _expression_rhs; +} + +} // namespace crab diff --git a/src/crab/constraint.hpp b/src/crab/constraint.hpp new file mode 100644 index 000000000..c70cad2d8 --- /dev/null +++ b/src/crab/constraint.hpp @@ -0,0 +1,42 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT +#pragma once + +#include "expression.hpp" +#include "common.hpp" + +namespace crab { + +using slacks_t = std::map; + +// represents a constraint of the form lhs <= rhs +class constraint_t { + private: + expression_t _expression_lhs; + expression_t _expression_rhs; + + public: + constraint_t(expression_t lhs, expression_t rhs) + : _expression_lhs(lhs), _expression_rhs(rhs) {} + + bool is_bottom(std::shared_ptr); + + expression_t get_lhs() const { return _expression_lhs; } + expression_t get_rhs() const { return _expression_rhs; } + + constraint_t operator+(const constraint_t&) const; + constraint_t operator+(int) const; + constraint_t operator+(interval_t) const; + constraint_t operator-(const constraint_t&) const; + constraint_t operator|(const constraint_t&) const; + constraint_t operator<=(const constraint_t&) const; + constraint_t operator>(const constraint_t&) const; + bool is_equality() const; + void simplify(std::shared_ptr); + [[nodiscard]] constraint_t negate() const; + void write(std::ostream&) const; +}; + +std::ostream& operator<<(std::ostream &, const constraint_t&); + +} // namespace crab From d9f00140f7b8dd476713dfdcd5d8e7148dc10d82 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 25 Mar 2024 18:42:25 -0400 Subject: [PATCH 154/373] Added a definition for refinement types Refinement type represents types in the offset domain and interval domain Signed-off-by: Ameer Hamza --- src/crab/refinement.cpp | 218 ++++++++++++++++++++++++++++++++++++++++ src/crab/refinement.hpp | 63 ++++++++++++ 2 files changed, 281 insertions(+) create mode 100644 src/crab/refinement.cpp create mode 100644 src/crab/refinement.hpp diff --git a/src/crab/refinement.cpp b/src/crab/refinement.cpp new file mode 100644 index 000000000..6a137bc01 --- /dev/null +++ b/src/crab/refinement.cpp @@ -0,0 +1,218 @@ + +#include "refinement.hpp" + +namespace crab { + +constexpr int PROPAGATE_INEQUALITIES = 2; + +static void propagate_inequalities(std::vector& constraints_to_check) { + std::vector new_constraints; + for (int i = 0; i < (int)constraints_to_check.size()-1; i++) { + for (int j = i + 1; j < (int)constraints_to_check.size(); j++) { + auto c1 = constraints_to_check[i]; + auto c2 = constraints_to_check[j]; + expression_t lhs1 = c1.get_lhs(); + expression_t rhs1 = c1.get_rhs(); + expression_t lhs2 = c2.get_lhs(); + expression_t rhs2 = c2.get_rhs(); + if (rhs1.is_singleton()) { + symbol_t y = rhs1.get_singleton(); + if (lhs2.contains(y)) { + expression_t new_lhs2 = lhs2.substitute(y, lhs1); + new_constraints.push_back(constraint_t(new_lhs2, rhs2)); + } + } + if (rhs2.is_singleton()) { + symbol_t y = rhs2.get_singleton(); + if (lhs1.contains(y)) { + expression_t new_lhs1 = lhs1.substitute(y, lhs2); + new_constraints.push_back(constraint_t(new_lhs1, rhs1)); + } + } + } + } + constraints_to_check.insert(constraints_to_check.end(), + new_constraints.begin(), new_constraints.end()); +} + +std::ostream& operator<<(std::ostream& o, const refinement_t& r) { + r.write(o); + return o; +} + +bool refinement_t::is_bottom(std::shared_ptr slacks) { + std::vector constraints_to_check = _constraints; + int i = 0; + while (i <= PROPAGATE_INEQUALITIES) { + for (constraint_t c : constraints_to_check) { + if (c.is_bottom(slacks)) { + return true; + } + } + if (i < PROPAGATE_INEQUALITIES) { + propagate_inequalities(constraints_to_check); + } + i++; + } + return false; +} + +bool refinement_t::has_value(refinement_t&& other) const { + return (_type == other._type && _value == other._value); +} + +void refinement_t::write(std::ostream& o) const { + symbol_t nu = symbol_t::nu(); + symbol_t i = symbol_t::pkt_symbol(); + o << "{" << nu << " : "; + if (_type == data_type_t::NUM) { + o << "num | " << nu << " = "; + } + else if (_type == data_type_t::PACKET) { + o << "pkt<" << i << "> | " << i << " = "; + } + else { + o << "_"; + } + o << _value; + if (_constraints.size() > 0) { + o << " & "; + for (size_t i = 0; i < _constraints.size(); i++) { + o << _constraints[i]; + if (i < _constraints.size() - 1) { + o << " & "; + } + } + } + o << "}"; +} + +void refinement_t::simplify() { + std::set to_remove; + std::vector constraints; + for (int i = 0; i < (int)_constraints.size()-1; i++) { + for (int j = i+1; j < (int)_constraints.size(); j++) { + auto c = _constraints[i]; + auto c1 = _constraints[j]; + if (c.get_rhs() == c1.get_rhs()) { + if (c.get_lhs() < c1.get_lhs()) { + to_remove.insert(i); + } + else if (c1.get_lhs() < c.get_lhs()) { + to_remove.insert(j); + } + } + } + } + for (size_t i = 0; i < _constraints.size(); i++) { + if (to_remove.find(i) == to_remove.end()) { + constraints.push_back(_constraints[i]); + } + } + _constraints = constraints; +} + +refinement_t refinement_t::operator+(interval_t i) const { + expression_t added_value = _value + i; + return refinement_t(_type, added_value, _constraints); +} + +refinement_t refinement_t::operator+(int n) const { + return operator+(interval_t{n}); +} + +refinement_t refinement_t::operator+(const refinement_t &other) const { + expression_t new_value = _value + other._value; + std::vector new_constraints; + new_constraints.insert(new_constraints.end(), _constraints.begin(), _constraints.end()); + new_constraints.insert(new_constraints.end(), + other._constraints.begin(), other._constraints.end()); + data_type_t new_type = (_type == other._type) ? _type + : (_type == data_type_t::PACKET || other._type == data_type_t::PACKET) ? data_type_t::PACKET + : data_type_t::ANY; + return refinement_t(new_type, new_value, new_constraints); +} + +refinement_t refinement_t::operator-(const refinement_t &other) const { + // TODO: Handle subtraction between packet pointers + expression_t new_value = _value + other._value.negate(); + std::vector new_constraints; + new_constraints.insert(new_constraints.end(), _constraints.begin(), _constraints.end()); + new_constraints.insert(new_constraints.end(), + other._constraints.begin(), other._constraints.end()); + data_type_t new_type = (_type == other._type) ? data_type_t::NUM + : (_type == data_type_t::PACKET || other._type == data_type_t::PACKET) ? data_type_t::PACKET + : data_type_t::ANY; + return refinement_t(new_type, new_value, new_constraints); +} + +bool refinement_t::same_type(const refinement_t &other) const { + return _type == other._type; +} + +refinement_t refinement_t::operator|(const refinement_t &other) const { + assert(same_type(other)); + auto joined_value = _value | other._value; + std::vector new_constraints; + std::set to_keep, to_keep1; + for (size_t i = 0; i < _constraints.size(); i++) { + for (size_t j = 0; j < other._constraints.size(); j++) { + auto c = _constraints[i]; + auto c1 = other._constraints[j]; + if (c.get_lhs() == c1.get_lhs() && c.get_rhs() == c1.get_rhs()) { + to_keep.insert(i); + } + else if (c.get_rhs() == c1.get_rhs() && c.get_lhs() < c1.get_lhs()) { + to_keep.insert(i); + } + else if (c.get_rhs() == c1.get_rhs() && c1.get_lhs() < c.get_lhs()) { + to_keep1.insert(j); + } + } + } + for (size_t i = 0; i < _constraints.size(); i++) { + if (to_keep.find(i) != to_keep.end()) { + new_constraints.push_back(_constraints[i]); + } + } + for (size_t i = 0; i < other._constraints.size(); i++) { + if (to_keep1.find(i) != to_keep1.end()) { + new_constraints.push_back(other._constraints[i]); + } + } + return refinement_t(_type, joined_value, new_constraints); +} + +constraint_t refinement_t::operator<=(const refinement_t &other) const { + assert(same_type(other)); + return constraint_t(_value, other._value); +} + +constraint_t refinement_t::operator>(const refinement_t &other) const { + assert(same_type(other)); + return constraint_t(other._value, _value + (-1)); +} + +void refinement_t::add_constraint(const constraint_t& c) { + _constraints.push_back(c); +} + +bool refinement_t::is_safe_with(refinement_t begin, std::shared_ptr slacks, + bool is_comparison_check) const { + refinement_t check_lb = begin; + auto lb = constraint_t(expression_t::meta(), _value); + constraint_t neg_lb = lb.negate(); + check_lb.add_constraint(neg_lb); + bool lb_satisfied = check_lb.is_bottom(slacks); + + refinement_t check_ub = std::move(begin); + auto ub = is_comparison_check ? constraint_t(_value, expression_t(interval_t{MAX_PACKET_SIZE})) + : constraint_t(_value, expression_t::end()); + constraint_t neg_ub = ub.negate(); + check_ub.add_constraint(neg_ub); + bool ub_satisfied = check_ub.is_bottom(slacks); + + return lb_satisfied && ub_satisfied; +} + +} // namespace crab diff --git a/src/crab/refinement.hpp b/src/crab/refinement.hpp new file mode 100644 index 000000000..b9ec68ce2 --- /dev/null +++ b/src/crab/refinement.hpp @@ -0,0 +1,63 @@ +// Copyright (c) Prevail Verifier contributors. +// SPDX-License-Identifier: MIT +#pragma once + +#include "constraint.hpp" + +namespace crab { + +enum class data_type_t { + NUM, + PACKET, + ANY +}; + +class refinement_t { + data_type_t _type; + expression_t _value; + std::vector _constraints; + + public: + explicit refinement_t(data_type_t type, expression_t value, + std::vector constraints = {}) + : _type(type), _value(value), _constraints(constraints) {} + refinement_t() : _type(data_type_t::ANY) {} + + bool is_bottom(std::shared_ptr); + [[nodiscard]] data_type_t get_type() const { return _type; } + [[nodiscard]] std::vector get_constraints() const { return _constraints; } + [[nodiscard]] expression_t get_value() const { return _value; } + refinement_t operator+(int n) const; + refinement_t operator+(interval_t) const; + refinement_t operator+(const refinement_t &other) const; + refinement_t operator-(const refinement_t &other) const; + refinement_t operator|(const refinement_t &other) const; + constraint_t operator<=(const refinement_t &other) const; + constraint_t operator>(const refinement_t &other) const; + bool has_value(refinement_t&&) const; + void write(std::ostream &o) const; + bool same_type(const refinement_t &other) const; + void add_constraint(const constraint_t&); + void simplify(); + bool is_safe_with(refinement_t, std::shared_ptr, bool) const; + + static refinement_t begin() { + return refinement_t(data_type_t::PACKET, expression_t::begin()); + } + + static refinement_t end() { + return refinement_t(data_type_t::PACKET, expression_t::end()); + } + + static refinement_t meta() { + return refinement_t(data_type_t::PACKET, expression_t::meta()); + } + + static refinement_t make_slack() { + return refinement_t(data_type_t::NUM, expression_t::make_slack()); + } +}; + +std::ostream &operator<<(std::ostream &, const refinement_t&); + +} // namespace crab From 684be059d5871154b0da38f668bc52a0e6c149bd Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 25 Mar 2024 18:51:52 -0400 Subject: [PATCH 155/373] Better printing for offset and interval domains In offset domain, now we keep track of refinements, that should be printed; In interval domain, we distinguish between signed and unsigned numbers Signed-off-by: Ameer Hamza --- src/crab/type_ostream.cpp | 104 ++++++++++++++++++++++++++++---------- src/crab/type_ostream.hpp | 20 ++++---- 2 files changed, 86 insertions(+), 38 deletions(-) diff --git a/src/crab/type_ostream.cpp b/src/crab/type_ostream.cpp index b1b7a99e1..d0aa03dc8 100644 --- a/src/crab/type_ostream.cpp +++ b/src/crab/type_ostream.cpp @@ -4,14 +4,13 @@ #include "crab/type_ostream.hpp" void print_non_numeric_memory_cell(std::ostream& o, int start, int end, - const crab::ptr_or_mapfd_t& ptr, std::optional d) { + const crab::ptr_or_mapfd_t& ptr, std::optional d) { if (std::holds_alternative(ptr)) { o << "[" << start << "-" << end << "] : " << std::get(ptr); } else if (std::holds_alternative(ptr)) { if (d) { - o << "[" << start << "-" << end << "] : " << - std::get(ptr) << "<" << *d << ">"; + o << "[" << start << "-" << end << "] : " << *d; } else { o << "[" << start << "-" << end << "] : " << std::get(ptr); @@ -22,35 +21,57 @@ void print_non_numeric_memory_cell(std::ostream& o, int start, int end, } } -void print_numeric_memory_cell(std::ostream& o, int start, int end, crab::interval_t n) { +void print_numeric_memory_cell(std::ostream& o, int start, int end, crab::interval_t n, + bool is_signed) { if (n.is_top()) { - o << "[" << start << "-" << end << "] : number"; + if (is_signed) { + o << "[" << start << "-" << end << "] : snumber"; + } + else { + o << "[" << start << "-" << end << "] : unumber"; + } } else { if (auto n_singleton = n.singleton()) { - o << "[" << start << "-" << end << "] : number<" << *n_singleton << ">"; + if (is_signed) { + o << "[" << start << "-" << end << "] : snumber<" << *n_singleton << ">"; + } + else { + o << "[" << start << "-" << end << "] : unumber<" << *n_singleton << ">"; + } } else { - o << "[" << start << "-" << end << "] : number<" << n << ">"; + if (is_signed) { + o << "[" << start << "-" << end << "] : snumber<" << n << ">"; + } + else { + o << "[" << start << "-" << end << "] : unumber<" << n << ">"; + } } } } void print_memory_cell(std::ostream& o, int start, int end, - const std::optional& p, std::optional d, - std::optional n) { - if (n) print_numeric_memory_cell(o, start, end, n->to_interval()); + const std::optional& p, std::optional d + , std::optional signed_interval, + std::optional unsigned_interval) { + if (signed_interval) { + print_numeric_memory_cell(o, start, end, signed_interval->to_interval(), true); + } + if (unsigned_interval) { + print_numeric_memory_cell(o, start, end, unsigned_interval->to_interval(), false); + } else if (p) print_non_numeric_memory_cell(o, start, end, *p, d); } void print_non_numeric_register(std::ostream& o, Reg r, const crab::ptr_or_mapfd_t& ptr, - std::optional d) { + std::optional d) { if (std::holds_alternative(ptr)) { o << r << " : " << std::get(ptr); } else if (std::holds_alternative(ptr)) { if (d) { - o << r << " : " << std::get(ptr) << "<" << *d << ">"; + o << r << " : " << *d; } else { o << r << " : " << std::get(ptr); @@ -61,57 +82,83 @@ void print_non_numeric_register(std::ostream& o, Reg r, const crab::ptr_or_mapfd } } -void print_numeric_register(std::ostream& o, Reg r, crab::interval_t n) { +void print_numeric_register(std::ostream& o, Reg r, crab::interval_t n, bool is_signed) { if (n.is_top()) { - o << r << " : number"; + if (is_signed) { + o << r << " : snumber"; + } + else { + o << r << " : unumber"; + } } else { if (auto n_singleton = n.singleton()) { - o << r << " : number<" << *n_singleton << ">"; + if (is_signed) { + o << r << " : snumber<" << *n_singleton << ">"; + } + else { + o << r << " : unumber<" << *n_singleton << ">"; + } } else { - o << r << " : number<" << n << ">"; + if (is_signed) { + o << r << " : snumber<" << n << ">"; + } + else { + o << r << " : unumber<" << n << ">"; + } } } } void print_register(std::ostream& o, Reg r, const std::optional& p, - std::optional d, std::optional n) { - if (n) print_numeric_register(o, r, n->to_interval()); + std::optional d, std::optional interval, + bool is_signed) { + if (interval) print_numeric_register(o, r, interval->to_interval(), is_signed); else if (p) print_non_numeric_register(o, r, *p, d); } inline std::string size_(int w) { return std::string("u") + std::to_string(w * 8); } -void print_annotated(std::ostream& o, const Call& call, std::optional& p, - std::optional& n) { +void print_annotated(std::ostream& o, const Call& call, std::optional& p, std::optional& d, + std::optional& n, bool is_signed) { o << " "; - print_register(o, Reg{(uint8_t)R0_RETURN_VALUE}, p, std::nullopt, n); + print_register(o, Reg{(uint8_t)R0_RETURN_VALUE}, p, d, n, is_signed); o << " = " << call.name << ":" << call.func << "(...)\n"; + if (d) { + o << " " << *d << "\n"; + } } void print_annotated(std::ostream& o, const Bin& b, std::optional& p, - std::optional& d, std::optional& n) { + std::optional& d, std::optional& n, + bool is_signed) { o << " "; - print_register(o, b.dst, p, d, n); + print_register(o, b.dst, p, d, n, is_signed); o << " " << b.op << "= " << b.v << "\n"; + if (d) { + o << " " << *d << "\n"; + } } void print_annotated(std::ostream& o, const LoadMapFd& u, std::optional& p) { o << " "; - print_register(o, u.dst, p, std::nullopt, std::nullopt); + print_register(o, u.dst, p, std::nullopt, std::nullopt, false); o << " = map_fd " << u.mapfd << "\n"; } void print_annotated(std::ostream& o, const Mem& b, std::optional& p, - std::optional& d, std::optional& n) { + std::optional& d, std::optional& n, bool is_signed) { o << " "; - print_register(o, std::get(b.value), p, d, n); + print_register(o, std::get(b.value), p, d, n, is_signed); o << " = "; std::string sign = b.access.offset < 0 ? " - " : " + "; int offset = std::abs(b.access.offset); o << "*(" << size_(b.access.width) << " *)"; o << "(" << b.access.basereg << sign << offset << ")\n"; + if (d) { + o << " " << *d << "\n"; + } } std::string op(Un::Op op) { @@ -135,8 +182,9 @@ std::string op(Un::Op op) { } } -void print_annotated(std::ostream& o, const Un& b, std::optional& n) { +void print_annotated(std::ostream& o, const Un& b, std::optional& n, + bool is_signed) { o << " "; - print_register(o, b.dst, std::nullopt, std::nullopt, n); + print_register(o, b.dst, std::nullopt, std::nullopt, n, is_signed); o << " = " << op(b.op) << " " << b.dst << "\n"; } diff --git a/src/crab/type_ostream.hpp b/src/crab/type_ostream.hpp index 7dcbb8a8c..b2bb1dd52 100644 --- a/src/crab/type_ostream.hpp +++ b/src/crab/type_ostream.hpp @@ -6,23 +6,23 @@ #include "crab/common.hpp" #include "crab/offset_domain.hpp" -void print_numeric_register(std::ostream&, Reg, crab::interval_t); -void print_numeric_memory_cell(std::ostream&, int, int, crab::interval_t); +void print_numeric_register(std::ostream&, Reg, crab::interval_t, bool); +void print_numeric_memory_cell(std::ostream&, int, int, crab::interval_t, bool); void print_non_numeric_register(std::ostream&, Reg, const crab::ptr_or_mapfd_t& ptr, - std::optional = std::nullopt); + std::optional = std::nullopt); void print_non_numeric_memory_cell(std::ostream&, int, int, const crab::ptr_or_mapfd_t& ptr, - std::optional = std::nullopt); + std::optional = std::nullopt); void print_register(std::ostream&, Reg, const std::optional&, - std::optional, std::optional); + std::optional, std::optional, bool); void print_memory_cell(std::ostream&, int, int, const std::optional&, - std::optional, std::optional); + std::optional, std::optional); // Print select transformers void print_annotated(std::ostream&, const Call&, std::optional&, - std::optional&); + std::optional&, std::optional&, bool); void print_annotated(std::ostream&, const Bin&, std::optional&, - std::optional&, std::optional&); + std::optional&, std::optional&, bool); void print_annotated(std::ostream&, const LoadMapFd&, std::optional&); void print_annotated(std::ostream&, const Mem&, std::optional&, - std::optional&, std::optional&); -void print_annotated(std::ostream&, const Un&, std::optional&); + std::optional&, std::optional&, bool); +void print_annotated(std::ostream&, const Un&, std::optional&, bool); From 6907f7a29eafca28f623a6287ce3a4e76db43715 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 25 Mar 2024 19:03:50 -0400 Subject: [PATCH 156/373] Some fixes in the interval domain Keeping consistency in signed and unsigned domain, where if we do not store something in one domain but do in another, we should store top in former; Fixed wrongly setting registers to top in Assume; Signed-off-by: Ameer Hamza --- src/crab/interval_domain.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/crab/interval_domain.cpp b/src/crab/interval_domain.cpp index 8805fcd43..7dc66d6c9 100644 --- a/src/crab/interval_domain.cpp +++ b/src/crab/interval_domain.cpp @@ -116,11 +116,19 @@ void interval_domain_t::insert_in_registers(register_t reg, location_t loc, inte void interval_domain_t::insert_in_registers_signed(register_t reg, location_t loc, interval_t interval) { m_signed.insert_in_registers(reg, loc, interval); + auto v = m_unsigned.find_interval_at_loc(reg_with_loc_t{reg, loc}); + if (!v) { + m_unsigned.insert_in_registers(reg, loc, interval_t::top()); + } } void interval_domain_t::insert_in_registers_unsigned(register_t reg, location_t loc, interval_t interval) { m_unsigned.insert_in_registers(reg, loc, interval); + auto v = m_signed.find_interval_at_loc(reg_with_loc_t{reg, loc}); + if (!v) { + m_signed.insert_in_registers(reg, loc, interval_t::top()); + } } void interval_domain_t::store_in_stack(uint64_t key, mock_interval_t interval, int width) { @@ -243,6 +251,7 @@ void interval_domain_t::do_call(const Call& u, const stack_cells_t& store_in_sta //m_unsigned.store_in_stack(offset, interval_t::top(), width); } auto r0 = register_t{R0_RETURN_VALUE}; + // TODO: Check if packet_reallocate() function call needs handling separately if (u.is_map_lookup) { operator-=(r0); } @@ -434,11 +443,11 @@ void interval_domain_t::assume_unsigned_cst(Condition::Op op, bool is64, if (is_lt && (strict ? (lub < rlb) : (lub <= rlb))) { // Left unsigned interval is lower than right unsigned interval. // TODO: verify if setting to top is the correct equivalent of returning linear cst true - set_registers_to_top(); + // set_registers_to_top(); return; } else if (!is_lt && (strict ? (llb > rub) : (llb >= rub))) { // Left unsigned interval is higher than right unsigned interval. - set_registers_to_top(); + // set_registers_to_top(); return; } @@ -788,11 +797,11 @@ void interval_domain_t::assume_signed_cst(Condition::Op op, bool is64, if (is_lt && (strict ? (lub < rlb) : (lub <= rlb))) { // Left unsigned interval is lower than right unsigned interval. // TODO: verify if setting to top is the correct equivalent of returning linear cst true - set_registers_to_top(); + // set_registers_to_top(); return; } else if (!is_lt && (strict ? (llb > rub) : (llb >= rub))) { // Left unsigned interval is higher than right unsigned interval. - set_registers_to_top(); + // set_registers_to_top(); return; } From 7c27531e3e106ae4ec8090ac4b5190627e97d9d1 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 25 Mar 2024 23:26:16 -0400 Subject: [PATCH 157/373] Added a new offset domain An offset domain added based on keeping track of refinements for packet pointers, and for numeric values; Some code fixed related to printing of values; Signed-off-by: Ameer Hamza --- src/crab/offset_domain.cpp | 741 +++++++++++++++---------------------- src/crab/offset_domain.hpp | 221 ++++------- src/crab/region_domain.cpp | 51 +-- src/crab/region_domain.hpp | 2 +- src/crab/type_domain.cpp | 212 ++++++++--- src/crab/type_domain.hpp | 9 +- 6 files changed, 545 insertions(+), 691 deletions(-) diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index 7704bbb8d..0a4c9dcf8 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -5,160 +5,47 @@ namespace crab { -bool dist_t::operator==(const dist_t& d) const { - return (m_dist == d.m_dist && m_slack == d.m_slack); -} - -weight_t dist_t::offset_from_reference() const { - if (is_meta_pointer()) { - return (-m_dist+interval_t{number_t{PACKET_META}}); - } - if (is_backward_pointer()) { - return (m_dist-interval_t{number_t{PACKET_END}}); - } - return m_dist; -} - -void dist_t::write(std::ostream& o) const { - if (m_slack != -1) - o << "s" << m_slack << "+"; - if (is_forward_pointer()) o << "begin+"; - else if (is_meta_pointer()) o << "meta+"; - else if (is_backward_pointer()) o << "end+"; - auto offset = offset_from_reference(); - auto singleton_val = offset.singleton(); - if (singleton_val) o << singleton_val.value(); - else o << offset; -} - -bool dist_t::is_top() const { - if (m_is_bottom) return false; - return (m_slack == -1 && m_dist.is_top()); -} - -bool dist_t::is_bottom() const { - if (m_is_bottom) return true; - return (m_slack == -1 && m_dist.is_bottom()); -} - -void dist_t::set_to_top() { - m_slack = -1; - m_dist = interval_t::top(); - m_is_bottom = false; -} - -void dist_t::set_to_bottom() { - m_is_bottom = true; -} - -bool dist_t::is_meta_pointer() const { - return (m_dist.lb() > number_t{PACKET_END} && m_dist.ub() <= number_t{PACKET_META}); -} -bool dist_t::is_forward_pointer() const { - return (m_dist.lb() >= number_t{PACKET_BEGIN}); -} -bool dist_t::is_backward_pointer() const { - return (m_dist.ub() <= number_t{PACKET_END}); -} - -std::ostream& operator<<(std::ostream& o, const dist_t& d) { - d.write(o); - return o; -} - -bool inequality_t::is_top() const { - if (m_is_bottom) return false; - return (m_slack == -1 && m_value.is_top()); -} - -bool inequality_t::is_bottom() const { - if (m_is_bottom) return true; - return (m_slack == -1 && m_value.is_bottom()); -} - -void inequality_t::set_to_top() { - m_value = interval_t::top(); - m_slack = -1; - m_is_bottom = false; -} - -void inequality_t::set_to_bottom() { - m_is_bottom = true; -} - - -std::ostream& operator<<(std::ostream& o, const inequality_t& ineq) { - ineq.write(o); - return o; -} - -void inequality_t::write(std::ostream& o) const { - o << m_slack << (m_rel == rop_t::R_GT ? ">" : - m_rel == rop_t::R_GE ? ">=" : - m_rel == rop_t::R_LT ? "<" : "<=") - << m_value; -} - -bool equality_t::is_top() const { - if (m_is_bottom) return false; - return (m_lhs.is_top() && m_rhs.is_top()); -} - -bool equality_t::is_bottom() const { - if (m_is_bottom) return true; - return (m_lhs.is_bottom() || m_rhs.is_bottom()); -} - -void equality_t::set_to_top() { - m_lhs.set_to_top(); - m_rhs.set_to_top(); - m_is_bottom = false; -} - -void equality_t::set_to_bottom() { - m_is_bottom = true; -} - -std::ostream& operator<<(std::ostream& o, const equality_t& eq) { - eq.write(o); - return o; -} - -void equality_t::write(std::ostream& o) const { - o << m_lhs << " = " << m_rhs; -} - -void registers_state_t::insert(register_t reg, const location_t& loc, dist_t&& dist) { +void registers_state_t::insert(register_t reg, const location_t& loc, refinement_t&& rf) { reg_with_loc_t reg_with_loc{reg, loc}; - (*m_offset_env)[reg_with_loc] = std::move(dist); + (*m_offset_env)[reg_with_loc] = std::move(rf); m_cur_def[reg] = std::make_shared(reg_with_loc); } -std::optional registers_state_t::find(reg_with_loc_t reg) const { +void registers_state_t::insert_slack_value(symbol_t sym, mock_interval_t in) { + (*m_slacks)[sym] = std::move(in); +} + +std::optional registers_state_t::find(reg_with_loc_t reg) const { auto it = m_offset_env->find(reg); if (it == m_offset_env->end()) return {}; return it->second; } -std::optional registers_state_t::find(register_t key) const { +std::optional registers_state_t::find_slack_value(symbol_t sym) const { + auto it = m_slacks->find(sym); + if (it == m_slacks->end()) return {}; + return it->second; +} + +std::optional registers_state_t::find(register_t key) const { if (m_cur_def[key] == nullptr) return {}; return find(*(m_cur_def[key])); } std::vector stack_state_t::find_overlapping_cells(uint64_t start, int width) const { std::vector overlapping_cells; - auto it = m_slot_dists.begin(); - while (it != m_slot_dists.end() && it->first < start) { + auto it = m_slot_rfs.begin(); + while (it != m_slot_rfs.end() && it->first < start) { it++; } - if (it != m_slot_dists.begin()) { + if (it != m_slot_rfs.begin()) { it--; auto key = it->first; auto width_key = it->second.second; if (key < start && key+width_key > start) overlapping_cells.push_back(key); } - for (; it != m_slot_dists.end(); it++) { + for (; it != m_slot_rfs.end(); it++) { auto key = it->first; if (key >= start && key < start+width) overlapping_cells.push_back(key); if (key >= start+width) break; @@ -168,12 +55,12 @@ std::vector stack_state_t::find_overlapping_cells(uint64_t start, int void registers_state_t::set_to_top() { m_offset_env = std::make_shared(); - m_cur_def = live_registers_t{nullptr}; + m_cur_def = live_refinements_t{nullptr}; m_is_bottom = false; } void registers_state_t::set_to_bottom() { - m_cur_def = live_registers_t{nullptr}; + m_cur_def = live_refinements_t{nullptr}; m_is_bottom = true; } @@ -204,8 +91,7 @@ registers_state_t registers_state_t::operator|(const registers_state_t& other) c return *this; } - auto region_env = std::make_shared(); - registers_state_t joined_state(region_env); + registers_state_t joined_state(m_offset_env, m_slacks); location_t loc = location_t(std::make_pair(label_t(-2, -2), 0)); for (uint8_t i = 0; i < NUM_REGISTERS; i++) { @@ -213,17 +99,65 @@ registers_state_t registers_state_t::operator|(const registers_state_t& other) c auto it1 = find(*(m_cur_def[i])); auto it2 = other.find(*(other.m_cur_def[i])); if (it1 && it2) { - auto dist1 = *it1, dist2 = *it2; - if (dist1.m_slack != dist2.m_slack) continue; - auto dist_joined = dist_t(std::move(dist1.m_dist | dist2.m_dist), dist1.m_slack); - joined_state.insert(register_t{i}, loc, std::move(dist_joined)); + auto rf1 = *it1, rf2 = *it2; + if (rf1.same_type(rf2)) { + expression_t rf1_value = rf1.get_value(); + expression_t rf2_value = rf2.get_value(); + if (rf1_value == rf2_value) { + joined_state.insert(register_t{i}, loc, std::move(rf1)); + } + else { + //joined_state.insert(register_t{i}, loc, std::move(rf1 | rf2)); + symbol_t s = symbol_t::make(); + symbol_terms_t terms; + bool added = false; + auto symbol_terms1 = rf1_value.get_symbol_terms(); + auto symbol_terms2 = rf2_value.get_symbol_terms(); + auto interval_rf1_value = rf1_value.get_interval(); + auto interval_rf2_value = rf2_value.get_interval(); + for (auto it = symbol_terms1.begin(); it != symbol_terms1.end(); it++) { + // assuming slack variables are at same position in both states + if (!added && it->first.is_slack()) { + auto slack1 = it->first; + auto dist = std::distance(symbol_terms1.begin(), it); + auto it2 = std::next(symbol_terms2.begin(), dist); + auto slack2 = it2->first; + auto slack1_value = find_slack_value(slack1); + auto slack2_value = other.find_slack_value(slack2); + if (slack1_value && slack2_value) { + auto interval1 = slack1_value->to_interval() + interval_rf1_value; + auto interval2 = slack2_value->to_interval() + interval_rf2_value; + auto interval = interval1 | interval2; + terms[s] = 1; + (*m_slacks)[s] = mock_interval_t{interval}; + added = true; + continue; + } + } + terms[it->first] = it->second; + } + auto rf = refinement_t(rf1.get_type(), expression_t(terms)); + joined_state.insert(register_t{i}, loc, std::move(rf)); + } + } + } + } + // need special handling for the registers v_begin, v_end, and v_meta + for (uint8_t i = NUM_REGISTERS; i < NUM_REGISTERS+3; i++) { + if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; + auto it1 = find(*(m_cur_def[i])); + auto it2 = other.find(*(other.m_cur_def[i])); + if (it1 && it2) { + auto rf1 = *it1, rf2 = *it2; + auto rf_joined = rf1 | rf2; + joined_state.insert(register_t{i}, loc, std::move(rf_joined)); } } return joined_state; } void registers_state_t::adjust_bb_for_registers(location_t loc) { - for (uint8_t i = 0; i < NUM_REGISTERS; i++) { + for (uint8_t i = 0; i < NUM_REGISTERS+3; i++) { if (auto it = find(register_t{i})) { insert(register_t{i}, loc, std::move(*it)); } @@ -236,25 +170,31 @@ void registers_state_t::scratch_caller_saved_registers() { } } -void registers_state_t::forget_packet_pointers() { +void registers_state_t::forget_packet_pointers(location_t loc) { for (uint8_t r = R0_RETURN_VALUE; r < NUM_REGISTERS; r++) { - operator-=(register_t{r}); + if (auto it = find(register_t{r})) { + if (it->get_type() == data_type_t::PACKET) { + operator-=(register_t{r}); + } + } } + insert(register_t{11}, loc, refinement_t::begin()); + // TODO: verify if this is all needed } void stack_state_t::set_to_top() { - m_slot_dists.clear(); + m_slot_rfs.clear(); m_is_bottom = false; } void stack_state_t::set_to_bottom() { - m_slot_dists.clear(); + m_slot_rfs.clear(); m_is_bottom = true; } bool stack_state_t::is_top() const { if (m_is_bottom) return false; - return m_slot_dists.empty(); + return m_slot_rfs.empty(); } bool stack_state_t::is_bottom() const { @@ -265,21 +205,21 @@ stack_state_t stack_state_t::top() { return stack_state_t(false); } -std::optional stack_state_t::find(uint64_t key) const { - auto it = m_slot_dists.find(key); - if (it == m_slot_dists.end()) return {}; +std::optional stack_state_t::find(uint64_t key) const { + auto it = m_slot_rfs.find(key); + if (it == m_slot_rfs.end()) return {}; return it->second; } -void stack_state_t::store(uint64_t key, dist_t d, int width) { - m_slot_dists[key] = std::make_pair(d, width); +void stack_state_t::store(uint64_t key, refinement_t d, int width) { + m_slot_rfs[key] = std::make_pair(d, width); } void stack_state_t::operator-=(uint64_t to_erase) { if (is_bottom()) { return; } - m_slot_dists.erase(to_erase); + m_slot_rfs.erase(to_erase); } void stack_state_t::operator-=(const std::vector& keys) { @@ -295,145 +235,36 @@ stack_state_t stack_state_t::operator|(const stack_state_t& other) const { return *this; } - stack_slot_dists_t out_stack_dists; - // We do not join dist cells because different dist values different types of offsets - for (auto const&kv: m_slot_dists) { - auto maybe_dist_cells = other.find(kv.first); - if (maybe_dist_cells) { - auto dist_cells1 = kv.second; - auto dist_cells2 = *maybe_dist_cells; - auto dist1 = dist_cells1.first; - auto dist2 = dist_cells2.first; - int width1 = dist_cells1.second; - int width2 = dist_cells2.second; - if (dist1 == dist2 && width1 == width2) { - out_stack_dists.insert(kv); + stack_slot_refinements_t out_stack_rfs; + // We do not join rf cells because different rf values different types of offsets + for (auto const&kv: m_slot_rfs) { + auto maybe_rf_cells = other.find(kv.first); + if (maybe_rf_cells) { + auto rf_cells1 = kv.second; + auto rf_cells2 = *maybe_rf_cells; + auto rf1 = rf_cells1.first; + auto rf2 = rf_cells2.first; + int width1 = rf_cells1.second; + int width2 = rf_cells2.second; + // TODO: for numerical values, the width does not have to be the same + // hence, handle accordingly + if (rf1.same_type(rf2) && width1 == width2) { + out_stack_rfs.insert({kv.first, std::make_pair(rf1 | rf2, width1)}); } } } - return stack_state_t(std::move(out_stack_dists), false); -} - -bool extra_constraints_t::is_top() const { - if (m_is_bottom) return false; - return (m_meta_and_begin.is_top() && m_begin_and_end.is_top()); -} - -bool extra_constraints_t::is_bottom() const { - return m_is_bottom; -} - -void extra_constraints_t::set_to_top() { - m_meta_and_begin.set_to_top(); - m_begin_and_end.set_to_top(); - m_is_bottom = false; -} - -void extra_constraints_t::set_to_bottom() { - m_is_bottom = true; -} - -void extra_constraints_t::add_meta_and_begin_constraint(equality_t&& eq, - inequality_t&& ineq) { - m_meta_and_begin = packet_constraint_t(std::move(eq), std::move(ineq), true); -} - -void extra_constraints_t::add_begin_and_end_constraint(equality_t&& eq, - inequality_t&& ineq) { - m_begin_and_end = packet_constraint_t(std::move(eq), std::move(ineq), false); -} -/* -void extra_constraints_t::normalize() { - weight_t dist_lhs = m_eq.m_lhs.m_dist - m_eq.m_rhs.m_dist - 4099; - weight_t dist_rhs = -4099; - slack_var_t s = m_eq.m_lhs.m_slack; - dist_lhs += m_ineq.m_value; - weight_t ineq_val = 0; - rop_t ineq_rel = m_ineq.m_rel; - - m_eq = equality_t(dist_t(dist_lhs, s), dist_t(dist_rhs)); - m_ineq = inequality_t(s, ineq_rel, ineq_val); -} -*/ - -packet_constraint_t packet_constraint_t::operator|(const packet_constraint_t& other) const { - //normalize(); - //other.normalize(); - - weight_t dist1 = m_eq.m_lhs.m_dist; - weight_t dist2 = other.m_eq.m_lhs.m_dist; - slack_var_t s = m_eq.m_lhs.m_slack; - - dist_t lhs = dist_t(dist1 | dist2, s); - dist_t rhs; - if (m_is_meta_constraint) rhs = dist_t(weight_t{number_t{PACKET_BEGIN}}); - else rhs = dist_t(weight_t{number_t{PACKET_END}}); - - equality_t out_eq(lhs, rhs); - inequality_t out_ineq(s, m_ineq.m_rel, weight_t{number_t{0}}); - return packet_constraint_t(std::move(out_eq), std::move(out_ineq), m_is_meta_constraint); - // have to handle case for different slack vars -} - -std::ostream& operator<<(std::ostream& o, const packet_constraint_t& p) { - p.write(o); - return o; -} - -void packet_constraint_t::write(std::ostream& o) const { - o << m_eq << "\n"; - o << m_ineq << "\n"; -} - -void packet_constraint_t::set_to_top() { - m_eq.set_to_top(); - m_ineq.set_to_top(); - m_is_bottom = false; -} - -void packet_constraint_t::set_to_bottom() { - m_is_bottom = true; -} - -bool packet_constraint_t::is_top() const { - if (m_is_bottom) return false; - return (m_eq.is_top() && m_ineq.is_top()); -} - -bool packet_constraint_t::is_bottom() const { - return m_is_bottom; -} - -std::optional packet_constraint_t::get_limit() const { - // TODO: normalize constraint, if required - auto dist = m_eq.m_lhs.m_dist; - if (dist.is_top()) return {}; - return dist.lb(); -} - -extra_constraints_t extra_constraints_t::operator|(const extra_constraints_t& other) const { - auto meta_and_begin = m_meta_and_begin | other.m_meta_and_begin; - auto begin_and_end = m_begin_and_end | other.m_begin_and_end; - return extra_constraints_t(std::move(meta_and_begin), std::move(begin_and_end), false); -} - -std::optional extra_constraints_t::get_end_limit() const { - return m_begin_and_end.get_limit(); -} - -std::optional extra_constraints_t::get_meta_limit() const { - return m_meta_and_begin.get_limit(); + return stack_state_t(std::move(out_stack_rfs)); } ctx_offsets_t::ctx_offsets_t(const ebpf_context_descriptor_t* desc) { if (desc->data >= 0) { - m_dists[desc->data] = dist_t(weight_t{number_t{PACKET_BEGIN}}); + m_rfs[desc->data] = refinement_t::begin(); } if (desc->end >= 0) { - m_dists[desc->end] = dist_t(weight_t{number_t{PACKET_END}}); + m_rfs[desc->end] = refinement_t::end(); } if (desc->meta >= 0) { - m_dists[desc->meta] = dist_t(weight_t{number_t{PACKET_META}}); + m_rfs[desc->meta] = refinement_t::meta(); } if (desc->size >= 0) { m_size = desc->size; @@ -444,16 +275,17 @@ int ctx_offsets_t::get_size() const { return m_size; } -std::optional ctx_offsets_t::find(int key) const { - auto it = m_dists.find(key); - if (it == m_dists.end()) return {}; +std::optional ctx_offsets_t::find(int key) const { + auto it = m_rfs.find(key); + if (it == m_rfs.end()) return {}; return it->second; } offset_domain_t&& offset_domain_t::setup_entry() { std::shared_ptr ctx = std::make_shared(global_program_info->type.context_descriptor); - registers_state_t regs(std::make_shared()); + registers_state_t regs(std::make_shared(), + std::make_shared(), global_program_info->type.context_descriptor); static offset_domain_t off_d(std::move(regs), stack_state_t::top(), ctx); return std::move(off_d); @@ -468,7 +300,6 @@ offset_domain_t offset_domain_t::bottom() { void offset_domain_t::set_to_top() { m_reg_state.set_to_top(); m_stack_state.set_to_top(); - m_extra_constraints.set_to_top(); m_is_bottom = false; } @@ -476,18 +307,16 @@ void offset_domain_t::set_to_bottom() { m_is_bottom = true; m_reg_state.set_to_bottom(); m_stack_state.set_to_bottom(); - m_extra_constraints.set_to_bottom(); } bool offset_domain_t::is_bottom() const { if (m_is_bottom) return true; - return (m_reg_state.is_bottom() || m_stack_state.is_bottom() - || m_extra_constraints.is_bottom()); + return (m_reg_state.is_bottom() || m_stack_state.is_bottom()); } bool offset_domain_t::is_top() const { if (m_is_bottom) return false; - return (m_reg_state.is_top() && m_stack_state.is_top() && m_extra_constraints.is_top()); + return (m_reg_state.is_top() && m_stack_state.is_top()); } // inclusion @@ -517,8 +346,7 @@ offset_domain_t offset_domain_t::operator|(const offset_domain_t& other) const { return offset_domain_t( m_reg_state | other.m_reg_state, m_stack_state | other.m_stack_state, - m_extra_constraints | other.m_extra_constraints, - m_ctx_dists, std::max(m_slack, other.m_slack) + m_ctx_rfs ); } @@ -532,8 +360,7 @@ offset_domain_t offset_domain_t::operator|(offset_domain_t&& other) const { return offset_domain_t( m_reg_state | std::move(other.m_reg_state), m_stack_state | std::move(other.m_stack_state), - m_extra_constraints | std::move(other.m_extra_constraints), - m_ctx_dists, std::max(m_slack, other.m_slack) + m_ctx_rfs ); } @@ -574,62 +401,81 @@ string_invariant offset_domain_t::to_set() { return string_invariant{}; } void offset_domain_t::operator()(const Assume &b, location_t loc) { Condition cond = b.cond; - if (cond.op == Condition::Op::LE) { - if (std::holds_alternative(cond.right)) { - auto right_reg = std::get(cond.right).v; - auto dist_left = m_reg_state.find(cond.left.v); - auto dist_right = m_reg_state.find(right_reg); - if (!dist_left || !dist_right) { - // this should not happen, comparison between a packet pointer and either - // other region's pointers or numbers; possibly raise type error - m_errors.push_back("one of the pointers being compared isn't packet pointer"); - //std::cout << "type_error: one of the pointers being compared isn't packet pointer\n"; - return; - } - dist_t left_reg_dist = dist_left.value(); - // keep only the upper bound to generate the constraint - auto ub = left_reg_dist.m_dist.ub(); - auto left = weight_t{ub}; - dist_t right_reg_dist = dist_right.value(); - slack_var_t s = m_slack++; - dist_t f = dist_t(left, s); - dist_t b = dist_t(right_reg_dist.m_dist); - auto eq = equality_t(f, b); - auto ineq = inequality_t(s, rop_t::R_GE, weight_t{number_t{0}}); - if (f.is_meta_pointer() && b.is_forward_pointer()) { - m_extra_constraints.add_meta_and_begin_constraint(std::move(eq), std::move(ineq)); + if (std::holds_alternative(cond.right)) { + auto right_reg = std::get(cond.right).v; + auto rf_left = m_reg_state.find(cond.left.v); + auto rf_right = m_reg_state.find(right_reg); + if (!rf_left || !rf_right) { + // this should not happen, comparison between a packet pointer and either + // other region's pointers or numbers; possibly raise type error + m_errors.push_back("one of the pointers being compared isn't packet pointer"); + return; + } + if (cond.op == Condition::Op::LE) { + auto b = m_reg_state.find(register_t{11}); + auto le_rf = *rf_left <= *rf_right; + if (b) { + b->add_constraint(le_rf); + if (rf_right->has_value(refinement_t::end())) { + b->add_constraint(refinement_t::meta() <= refinement_t::begin()); + } + else if (rf_right->has_value(refinement_t::begin())) { + b->add_constraint(refinement_t::begin() <= refinement_t::end()); + } + b->simplify(); + auto b_copy = *b; + if (b_copy.is_bottom(m_reg_state.get_slacks())) { + set_to_bottom(); + } + else { + m_reg_state.insert(register_t{11}, loc, std::move(*b)); + } } - else if (f.is_forward_pointer() && b.is_backward_pointer()) { - m_extra_constraints.add_begin_and_end_constraint(std::move(eq), std::move(ineq)); + } + else if (cond.op == Condition::Op::GT) { + auto b = m_reg_state.find(register_t{11}); + auto gt_rf = *rf_left > *rf_right; + if (b) { + b->add_constraint(gt_rf); + b->simplify(); + auto b_copy = *b; + if (b_copy.is_bottom(m_reg_state.get_slacks())) { + set_to_bottom(); + } + else { + m_reg_state.insert(register_t{11}, loc, std::move(*b)); + } } + } + // other comparisons not supported } - else {} //we do not need to deal with other cases } -void offset_domain_t::update_offset_info(dist_t&& dist, interval_t&& change, - const location_t& loc, register_t reg) { - auto offset = dist.m_dist; - if (dist.is_forward_pointer()) offset += change; - else if (dist.is_backward_pointer()) offset -= change; - else offset -= change; - m_reg_state.insert(reg, loc, dist_t{offset}); +static void create_numeric_refinement(registers_state_t& reg_state, mock_interval_t&& interval, + location_t loc, register_t reg) { + symbol_t s = symbol_t::make(); + refinement_t rf = refinement_t(data_type_t::NUM, expression_t(s)); + reg_state.insert(reg, loc, std::move(rf)); + reg_state.insert_slack_value(s, std::move(interval)); } -interval_t offset_domain_t::do_bin(const Bin& bin, +void offset_domain_t::do_bin(const Bin& bin, const std::optional& src_signed_interval_opt, const std::optional& src_ptr_or_mapfd_opt, const std::optional& dst_signed_interval_opt, - const std::optional& dst_ptr_or_mapfd_opt, location_t loc) { - - // offset domain only handles packet pointers - if (!is_packet_ptr(src_ptr_or_mapfd_opt) && !is_packet_ptr(dst_ptr_or_mapfd_opt)) - return interval_t::bottom(); + const std::optional& dst_ptr_or_mapfd_opt, + mock_interval_t &&interval_result, location_t loc) { using Op = Bin::Op; auto dst_register = register_t{bin.dst.v}; + //if (!is_packet_ptr(src_ptr_or_mapfd_opt) && !is_packet_ptr(dst_ptr_or_mapfd_opt) && + // (!src_signed_interval_opt && bin.op != Op::MOV) && !dst_signed_interval_opt) { + // return interval_t::bottom(); + //} + if (std::holds_alternative(bin.v)) { int64_t imm; if (bin.is64) { @@ -643,38 +489,45 @@ interval_t offset_domain_t::do_bin(const Bin& bin, switch (bin.op) { case Op::MOV: { // ra = imm, we forget the type in the offset domain - m_reg_state -= dst_register; + create_numeric_refinement(m_reg_state, std::move(interval_result), + loc, dst_register); break; } case Op::ADD: { // ra += imm if (imm == 0) break; - if (is_packet_ptr(dst_ptr_or_mapfd_opt)) { - if (auto dst_offset_opt = m_reg_state.find(dst_register)) { - update_offset_info(std::move(*dst_offset_opt), std::move(imm_interval), - loc, dst_register); - return interval_t::bottom(); - } + if (auto dst_rf_opt = m_reg_state.find(dst_register)) { + auto rf = *dst_rf_opt + imm; + m_reg_state.insert(dst_register, loc, std::move(rf)); + } + else { + m_reg_state -= dst_register; } - m_reg_state -= dst_register; break; } case Op::SUB: { // ra -= imm if (imm == 0) break; - if (is_packet_ptr(dst_ptr_or_mapfd_opt)) { - if (auto dst_offset_opt = m_reg_state.find(dst_register)) { - update_offset_info(std::move(*dst_offset_opt), -std::move(imm_interval), - loc, dst_register); - return interval_t::bottom(); - } + if (auto dst_rf_opt = m_reg_state.find(dst_register)) { + auto rf = *dst_rf_opt + (-imm); + m_reg_state.insert(dst_register, loc, std::move(rf)); + } + else { + m_reg_state -= dst_register; } - m_reg_state -= dst_register; break; } default: { - // no other operations supported for offset domain - m_reg_state -= dst_register; + if (dst_signed_interval_opt) { + symbol_t s = symbol_t::make(); + refinement_t rf = refinement_t(data_type_t::NUM, expression_t(s)); + m_reg_state.insert(dst_register, loc, std::move(rf)); + m_reg_state.insert_slack_value(s, interval_result); + } + else { + // no other operations supported for packet pointers in the offset domain + m_reg_state -= dst_register; + } break; } } @@ -684,84 +537,74 @@ interval_t offset_domain_t::do_bin(const Bin& bin, switch (bin.op) { case Op::MOV: { // ra = rb - if (is_packet_ptr(src_ptr_or_mapfd_opt)) { - if (auto src_offset_opt = m_reg_state.find(src.v)) { - m_reg_state.insert(dst_register, loc, std::move(*src_offset_opt)); - return interval_t::bottom(); - } + if (auto src_rf_opt = m_reg_state.find(src.v)) { + auto rf = *src_rf_opt; + m_reg_state.insert(dst_register, loc, std::move(rf)); + } + else { + m_reg_state -= dst_register; } - m_reg_state -= dst_register; break; } case Op::ADD: { // ra += rb - if (is_packet_ptr(dst_ptr_or_mapfd_opt) && src_signed_interval_opt) { - auto src_signed = std::move(*src_signed_interval_opt); - if (auto dst_offset_opt = m_reg_state.find(dst_register)) { - update_offset_info(std::move(*dst_offset_opt), - std::move(src_signed), loc, dst_register); - return interval_t::bottom(); - } - } - else if (is_packet_ptr(src_ptr_or_mapfd_opt) && dst_signed_interval_opt) { - auto dst_signed = std::move(*dst_signed_interval_opt); - if (auto src_offset_opt = m_reg_state.find(src.v)) { - update_offset_info(std::move(*src_offset_opt), - std::move(dst_signed), loc, dst_register); - return interval_t::bottom(); - } - } - else if (dst_signed_interval_opt && src_signed_interval_opt) { - // we do not deal with numbers in offset domain - } - else { + if (is_packet_ptr(src_ptr_or_mapfd_opt) && is_packet_ptr(dst_ptr_or_mapfd_opt)) { // possibly adding two pointers set_to_bottom(); } - m_reg_state -= dst_register; + else { + if (auto src_rf_opt = m_reg_state.find(src.v)) { + if (auto dst_rf_opt = m_reg_state.find(dst_register)) { + auto rf = *dst_rf_opt + *src_rf_opt; + m_reg_state.insert(dst_register, loc, std::move(rf)); + } + else { + m_reg_state -= dst_register; + } + } + else { + m_reg_state -= dst_register; + } + } break; } case Op::SUB: { // ra -= rb - if (is_packet_ptr(dst_ptr_or_mapfd_opt) && src_signed_interval_opt) { - if (auto dst_offset_opt = m_reg_state.find(dst_register)) { - update_offset_info(std::move(*dst_offset_opt), - -std::move(*src_signed_interval_opt), loc, dst_register); - return interval_t::bottom(); - } - } - else if (is_packet_ptr(src_ptr_or_mapfd_opt) && dst_signed_interval_opt) { - m_reg_state -= dst_register; - } - else if (dst_signed_interval_opt && src_signed_interval_opt) { - // we do not deal with numbers in region domain + // TODO: be precise with ptr -= ptr, and possibly assign a slack to the result + if (is_packet_ptr(src_ptr_or_mapfd_opt) && is_packet_ptr(dst_ptr_or_mapfd_opt)) { + create_numeric_refinement(m_reg_state, std::move(interval_result), + loc, dst_register); + return; } else { - // ptr -= ptr - // TODO: be precise with ptr -= ptr, especially for packet end - if (is_packet_ptr(dst_ptr_or_mapfd_opt), is_packet_ptr(src_ptr_or_mapfd_opt)) { - m_reg_state -= dst_register; - return interval_t::top(); - /* - if (auto dst_offset_opt = m_reg_state.find(dst_register)) { - if (auto src_offset_opt = m_reg_state.find(src.v)) { - return (dst_offset_opt->m_dist - src_offset_opt->m_dist); - } + if (auto src_rf_opt = m_reg_state.find(src.v)) { + if (auto dst_rf_opt = m_reg_state.find(dst_register)) { + auto rf = *dst_rf_opt - *src_rf_opt; + m_reg_state.insert(dst_register, loc, std::move(rf)); } - */ + else { + m_reg_state -= dst_register; + } + } + else { + m_reg_state -= dst_register; } } - m_reg_state -= dst_register; break; } default: { - // no other operations supported for offset domain - m_reg_state -= dst_register; + if (dst_ptr_or_mapfd_opt || src_ptr_or_mapfd_opt) { + // no other operations supported for packet pointers in the offset domain + m_reg_state -= dst_register; + } + else { + create_numeric_refinement(m_reg_state, std::move(interval_result), + loc, dst_register); + } break; } } } - return interval_t::bottom(); } void offset_domain_t::operator()(const Bin& bin, location_t loc) { @@ -773,7 +616,16 @@ void offset_domain_t::operator()(const Undefined& u, location_t loc) { } void offset_domain_t::operator()(const Un& u, location_t loc) { - m_reg_state -= u.dst.v; + // nothing to do here +} + +void offset_domain_t::do_un(const Un& u, interval_t interval, location_t loc) { + if (interval == interval_t::bottom()) { + m_reg_state -= u.dst.v; + } + else { + create_numeric_refinement(m_reg_state, std::move(interval), loc, register_t{u.dst.v}); + } } void offset_domain_t::operator()(const LoadMapFd& u, location_t loc) { @@ -782,16 +634,23 @@ void offset_domain_t::operator()(const LoadMapFd& u, location_t loc) { void offset_domain_t::do_call(const Call& u, const stack_cells_t& cells, location_t loc) { for (const auto& kv : cells) { - auto offset = kv.first; + auto rf = kv.first; auto width = kv.second; - auto overlapping_cells - = m_stack_state.find_overlapping_cells(offset, width); + auto overlapping_cells = m_stack_state.find_overlapping_cells(rf, width); m_stack_state -= overlapping_cells; } - m_reg_state -= register_t{R0_RETURN_VALUE}; m_reg_state.scratch_caller_saved_registers(); + register_t r0{R0_RETURN_VALUE}; if (u.reallocate_packet) { - m_reg_state.forget_packet_pointers(); + m_reg_state -= r0; + m_reg_state.forget_packet_pointers(loc); + } + else if (u.is_map_lookup) { + m_reg_state -= r0; + } + else { + // slack needs to be fixed, as it can have any value + create_numeric_refinement(m_reg_state, mock_interval_t::top(), loc, r0); } } @@ -805,7 +664,8 @@ void offset_domain_t::operator()(const Jmp& u, location_t loc) { } void offset_domain_t::operator()(const Packet& u, location_t loc) { - m_reg_state -= register_t{R0_RETURN_VALUE}; + create_numeric_refinement(m_reg_state, mock_interval_t::top(), loc, + register_t{R0_RETURN_VALUE}); m_reg_state.scratch_caller_saved_registers(); } @@ -850,12 +710,12 @@ bool offset_domain_t::upper_bound_satisfied(const dist_t& dist, int offset, int bool offset_domain_t::check_packet_access(const Reg& r, int width, int offset, bool is_comparison_check) const { - auto it = m_reg_state.find(r.v); - if (!it) return false; - dist_t dist = it.value(); - - return (lower_bound_satisfied(dist, offset) - && upper_bound_satisfied(dist, offset, width, is_comparison_check)); + auto begin = m_reg_state.find(register_t{11}); + if (!begin) return false; + auto reg = m_reg_state.find(r.v); + if (!reg) return false; + auto toCheck = *reg + (offset+width); + return toCheck.is_safe_with(*begin, m_reg_state.get_slacks(), is_comparison_check); } void offset_domain_t::check_valid_access(const ValidAccess& s, @@ -865,7 +725,6 @@ void offset_domain_t::check_valid_access(const ValidAccess& s, bool is_comparison_check = s.width == (Value)Imm{0}; if (check_packet_access(s.reg, w, s.offset, is_comparison_check)) return; m_errors.push_back("valid access check failed"); - //std::cout << "type_error: valid access assert fail\n"; } void offset_domain_t::operator()(const Assert &u, location_t loc) { @@ -876,10 +735,14 @@ void offset_domain_t::operator()(const basic_block_t& bb, int print) { // nothing to do here } -void offset_domain_t::do_mem_store(const Mem& b, std::optional maybe_targetreg_type, std::optional& maybe_basereg_type) { +void offset_domain_t::do_mem_store(const Mem& b, + std::optional& maybe_basereg_type) { + auto target_reg = std::get(b.value); + auto rf_info = m_reg_state.find(target_reg.v); + if (!rf_info) return; + int offset = b.access.offset; int width = b.access.width; - if (is_stack_ptr(maybe_basereg_type)) { auto basereg_with_off = std::get(*maybe_basereg_type); auto basereg_off_singleton = basereg_with_off.get_offset().to_interval().singleton(); @@ -888,23 +751,25 @@ void offset_domain_t::do_mem_store(const Mem& b, std::optional m auto overlapping_cells = m_stack_state.find_overlapping_cells(store_at, width); m_stack_state -= overlapping_cells; - if (!is_packet_ptr(maybe_targetreg_type)) return; - auto target_reg = std::get(b.value); - auto offset_info = m_reg_state.find(target_reg.v); - if (!offset_info) { - m_errors.push_back("register is a packet_pointer and no offset info found"); - //std::cout << "type_error: register is a packet_pointer and no offset info found\n"; - return; - } - m_stack_state.store(store_at, *offset_info, width); + m_stack_state.store(store_at, *rf_info, width); } } void offset_domain_t::do_load(const Mem& b, const register_t& target_register, - std::optional basereg_type, location_t loc) { + std::optional basereg_type, interval_t &&interval_result, location_t loc) { bool is_stack_p = is_stack_ptr(basereg_type); bool is_ctx_p = is_ctx_ptr(basereg_type); + bool is_packet_p = is_packet_ptr(basereg_type); + bool is_shared_p = is_shared_ptr(basereg_type); + + if (interval_result != interval_t::bottom()) { + if (is_ctx_p || is_shared_p || is_packet_p) { + create_numeric_refinement(m_reg_state, std::move(interval_result), loc, + target_register); + return; + } + } if (!is_stack_p && !is_ctx_p) { m_reg_state -= target_register; @@ -931,7 +796,7 @@ void offset_domain_t::do_load(const Mem& b, const register_t& target_register, m_reg_state.insert(target_register, loc, std::move(it->first)); } else if (is_ctx_p) { - auto it = m_ctx_dists->find(load_at); + auto it = m_ctx_rfs->find(load_at); if (!it) { m_reg_state -= target_register; return; @@ -944,27 +809,27 @@ void offset_domain_t::operator()(const Mem& b, location_t loc) { // nothing to do here } -std::optional offset_domain_t::find_offset_at_loc(const reg_with_loc_t reg) const { +std::optional offset_domain_t::find_refinement_at_loc(const reg_with_loc_t reg) const { return m_reg_state.find(reg); } -std::optional offset_domain_t::find_in_ctx(int key) const { - return m_ctx_dists->find(key); +std::optional offset_domain_t::find_in_ctx(int key) const { + return m_ctx_rfs->find(key); } -std::optional offset_domain_t::find_in_stack(int key) const { +std::optional offset_domain_t::find_in_stack(int key) const { return m_stack_state.find(key); } -std::optional offset_domain_t::find_offset_info(register_t reg) const { +std::optional offset_domain_t::find_refinement_info(register_t reg) const { return m_reg_state.find(reg); } -void offset_domain_t::insert_in_registers(register_t reg, location_t loc, dist_t dist) { - m_reg_state.insert(reg, loc, std::move(dist)); +void offset_domain_t::insert_in_registers(register_t reg, location_t loc, refinement_t rf) { + m_reg_state.insert(reg, loc, std::move(rf)); } -void offset_domain_t::store_in_stack(uint64_t key, dist_t d, int width) { +void offset_domain_t::store_in_stack(uint64_t key, refinement_t d, int width) { m_stack_state.store(key, d, width); } diff --git a/src/crab/offset_domain.hpp b/src/crab/offset_domain.hpp index e116af30f..1842e4040 100644 --- a/src/crab/offset_domain.hpp +++ b/src/crab/offset_domain.hpp @@ -3,111 +3,48 @@ #pragma once -#include "crab/common.hpp" +#include "crab/refinement.hpp" namespace crab { -constexpr int PACKET_END = -4100; -constexpr int PACKET_META = -1; - -using weight_t = interval_t; -using slack_var_t = int; - -enum class rop_t { - R_GT, - R_GE, - R_LT, - R_LE -}; - -struct dist_t { - slack_var_t m_slack; - weight_t m_dist; - bool m_is_bottom = false; - - dist_t(weight_t d, slack_var_t s = -1, bool bottom = false) - : m_slack(s), m_dist(d), m_is_bottom(bottom) {} - dist_t() : m_slack(-1), m_dist(weight_t::top()), m_is_bottom(false) {} - bool operator==(const dist_t& d) const; - void write(std::ostream&) const; - bool is_top() const; - bool is_bottom() const; - void set_to_top(); - void set_to_bottom(); - friend std::ostream& operator<<(std::ostream& o, const dist_t& d); - bool is_meta_pointer() const; - bool is_forward_pointer() const; - bool is_backward_pointer() const; - weight_t offset_from_reference() const; -}; - -struct inequality_t { - slack_var_t m_slack; - rop_t m_rel; - weight_t m_value; - bool m_is_bottom = false; - - inequality_t(slack_var_t slack, rop_t rel, weight_t val) : m_slack(slack), m_rel(rel) - , m_value(val) {} - inequality_t() : m_slack(-1), m_value(weight_t::top()) {} - bool is_top() const; - bool is_bottom() const; - void set_to_top(); - void set_to_bottom(); - void write(std::ostream&) const; - friend std::ostream& operator<<(std::ostream&, const inequality_t&); -}; // represents `slack rel value;`, e.g., `s >= 0` - -struct equality_t { - dist_t m_lhs; - dist_t m_rhs; - bool m_is_bottom = false; - - equality_t(dist_t lhs, dist_t rhs) : m_lhs(lhs), m_rhs(rhs) {} - equality_t() = default; - bool is_top() const; - bool is_bottom() const; - void set_to_top(); - void set_to_bottom(); - void write(std::ostream&) const; - friend std::ostream& operator<<(std::ostream&, const equality_t&); -}; // represents constraint `p[0] = p[1];`, e.g., `begin+8+s = end` - -struct packet_constraint_t { - equality_t m_eq; - inequality_t m_ineq; - bool m_is_meta_constraint; - bool m_is_bottom = false; - - bool is_bottom() const; - bool is_top() const; - void set_to_bottom(); - void set_to_top(); - std::optional get_limit() const; - packet_constraint_t() = default; - packet_constraint_t(equality_t&& eq, inequality_t&& ineq, bool is_meta_constraint, - bool is_bottom = false) : m_eq(eq), m_ineq(ineq), - m_is_meta_constraint(is_meta_constraint), m_is_bottom(is_bottom) {} - packet_constraint_t operator|(const packet_constraint_t&) const; - void write(std::ostream&) const; - friend std::ostream& operator<<(std::ostream&, const packet_constraint_t&); -}; - -using live_registers_t = std::array, NUM_REGISTERS>; -using global_offset_env_t = std::unordered_map; +using live_refinements_t = std::array, NUM_REGISTERS+3>; +using global_offset_env_t = std::unordered_map; class registers_state_t { - live_registers_t m_cur_def; + live_refinements_t m_cur_def; std::shared_ptr m_offset_env; + std::shared_ptr m_slacks; bool m_is_bottom = false; public: - registers_state_t(bool is_bottom = false) : m_offset_env(nullptr), m_is_bottom(is_bottom) {} - registers_state_t(std::shared_ptr offset_env, bool is_bottom = false) : m_offset_env(offset_env), m_is_bottom(is_bottom) {} - explicit registers_state_t(live_registers_t&& vars, std::shared_ptr - offset_env, bool is_bottom = false) - : m_cur_def(std::move(vars)), m_offset_env(std::move(offset_env)), m_is_bottom(is_bottom) {} + registers_state_t(bool is_bottom = false) : m_offset_env(nullptr), m_slacks(nullptr), + m_is_bottom(is_bottom) {} + registers_state_t(std::shared_ptr offset_env, + std::shared_ptr slacks, bool is_bottom = false) + : m_offset_env(offset_env), m_slacks(slacks), m_is_bottom(is_bottom) {} + registers_state_t(std::shared_ptr offset_env, + std::shared_ptr slacks, const ebpf_context_descriptor_t* desc, + bool is_bottom = false) + : m_offset_env(offset_env), m_slacks(slacks), m_is_bottom(is_bottom) { + + auto loc = std::make_pair(label_t::entry, (unsigned int)0); + if (desc->data >= 0) { + insert(register_t{11}, loc, refinement_t::begin()); + } + if (desc->end >= 0) { + insert(register_t{12}, loc, refinement_t::end()); + } + if (desc->meta >= 0) { + insert(register_t{13}, loc, refinement_t::meta()); + } + } + + explicit registers_state_t(live_refinements_t&& vars, + std::shared_ptr offset_env, + std::shared_ptr slacks, bool is_bottom = false) + : m_cur_def(std::move(vars)), m_offset_env(offset_env), m_slacks(slacks), + m_is_bottom(is_bottom) {} registers_state_t operator|(const registers_state_t&) const; void operator-=(register_t); @@ -115,27 +52,29 @@ class registers_state_t { void set_to_bottom(); bool is_bottom() const; bool is_top() const; - void insert(register_t, const location_t&, dist_t&&); - std::optional find(reg_with_loc_t reg) const; - std::optional find(register_t key) const; + void insert(register_t, const location_t&, refinement_t&&); + void insert_slack_value(symbol_t, mock_interval_t); + std::shared_ptr get_slacks() const { return m_slacks; } + std::optional find_slack_value(symbol_t) const; + std::optional find(reg_with_loc_t reg) const; + std::optional find(register_t key) const; friend std::ostream& operator<<(std::ostream& o, const registers_state_t& p); void adjust_bb_for_registers(location_t); void scratch_caller_saved_registers(); - void forget_packet_pointers(); + void forget_packet_pointers(location_t); }; -using dist_cells_t = std::pair; -using stack_slot_dists_t = std::map; // represents `sp[n] = dist;`, where n \belongs [0,511], e.g., `sp[508] = begin+16` - +using refinement_cells_t = std::pair; +using stack_slot_refinements_t = std::map; class stack_state_t { - stack_slot_dists_t m_slot_dists; + stack_slot_refinements_t m_slot_rfs; bool m_is_bottom = false; public: stack_state_t(bool is_bottom = false) : m_is_bottom(is_bottom) {} - std::optional find(uint64_t) const; - void store(uint64_t, dist_t, int); + std::optional find(uint64_t) const; + void store(uint64_t, refinement_t, int); void operator-=(uint64_t); void operator-=(const std::vector&); void set_to_top(); @@ -144,43 +83,19 @@ class stack_state_t { bool is_top() const; static stack_state_t top(); stack_state_t operator|(const stack_state_t&) const; - explicit stack_state_t(stack_slot_dists_t&& stack_dists, bool is_bottom = false) - : m_slot_dists(std::move(stack_dists)), m_is_bottom(is_bottom) {} + explicit stack_state_t(stack_slot_refinements_t&& stack_rfs, bool is_bottom = false) + : m_slot_rfs(std::move(stack_rfs)), m_is_bottom(is_bottom) {} std::vector find_overlapping_cells(uint64_t, int) const; }; -class extra_constraints_t { - - packet_constraint_t m_meta_and_begin; - packet_constraint_t m_begin_and_end; - bool m_is_bottom = false; - - public: - extra_constraints_t(bool is_bottom = false) : m_is_bottom(is_bottom) {} - bool is_bottom() const; - bool is_top() const; - void set_to_top(); - void set_to_bottom(); - void normalize(); - std::optional get_end_limit() const; - std::optional get_meta_limit() const; - void add_meta_and_begin_constraint(equality_t&&, inequality_t&&); - void add_begin_and_end_constraint(equality_t&&, inequality_t&&); - extra_constraints_t operator|(const extra_constraints_t&) const; - explicit extra_constraints_t(packet_constraint_t&& meta_and_begin, - packet_constraint_t&& begin_and_end, bool is_bottom = false) - : m_meta_and_begin(meta_and_begin), m_begin_and_end(begin_and_end), - m_is_bottom(is_bottom) {} -}; - class ctx_offsets_t { - using ctx_dists_t = std::unordered_map; // represents `cp[n] = dist;` - ctx_dists_t m_dists; + using ctx_refinements_t = std::unordered_map; // represents `cp[n] = rf;` + ctx_refinements_t m_rfs; int m_size; public: ctx_offsets_t(const ebpf_context_descriptor_t* desc); - std::optional find(int) const; + std::optional find(int) const; int get_size() const; }; @@ -189,10 +104,8 @@ class offset_domain_t final { bool m_is_bottom = false; registers_state_t m_reg_state; stack_state_t m_stack_state; - extra_constraints_t m_extra_constraints; - std::shared_ptr m_ctx_dists; + std::shared_ptr m_ctx_rfs; std::vector m_errors; - slack_var_t m_slack = 0; public: offset_domain_t() = default; @@ -201,13 +114,8 @@ class offset_domain_t final { offset_domain_t& operator=(offset_domain_t&& o) = default; offset_domain_t& operator=(const offset_domain_t& o) = default; explicit offset_domain_t(registers_state_t&& reg, stack_state_t&& stack, - extra_constraints_t extra, std::shared_ptr ctx, slack_var_t s = 0) - : m_reg_state(std::move(reg)), m_stack_state(std::move(stack)), - m_extra_constraints(std::move(extra)), m_ctx_dists(ctx), m_slack(s) {} - - explicit offset_domain_t(registers_state_t&& reg, stack_state_t&& stack, - std::shared_ptr ctx, slack_var_t s = 0) : m_reg_state(std::move(reg)), - m_stack_state(std::move(stack)), m_ctx_dists(ctx), m_slack(s) {} + std::shared_ptr ctx) + : m_reg_state(std::move(reg)), m_stack_state(std::move(stack)), m_ctx_rfs(ctx) {} static offset_domain_t&& setup_entry(); // bottom/top @@ -255,25 +163,24 @@ class offset_domain_t final { string_invariant to_set(); void set_require_check(check_require_func_t f) {} - void do_load(const Mem&, const register_t&, std::optional, location_t loc); - void do_mem_store(const Mem&, std::optional, std::optional&); - interval_t do_bin(const Bin&, const std::optional&, + void do_un(const Un&, interval_t, location_t); + void do_load(const Mem&, const register_t&, std::optional, interval_t&&, + location_t); + void do_mem_store(const Mem&, std::optional&); + void do_bin(const Bin&, const std::optional&, const std::optional&, const std::optional&, - const std::optional&, location_t); + const std::optional&, mock_interval_t&&, location_t); void do_call(const Call&, const stack_cells_t&, location_t); - bool upper_bound_satisfied(const dist_t&, int, int, bool) const; - bool lower_bound_satisfied(const dist_t&, int) const; bool check_packet_access(const Reg&, int, int, bool) const; void check_valid_access(const ValidAccess&, std::optional&, int); - std::optional find_in_ctx(int) const; - std::optional find_in_stack(int) const; - std::optional find_offset_at_loc(const reg_with_loc_t) const; - std::optional find_offset_info(register_t reg) const; - void update_offset_info(dist_t&&, interval_t&&, const location_t&, register_t); - void insert_in_registers(register_t, location_t, dist_t); - void store_in_stack(uint64_t, dist_t, int); + std::optional find_in_ctx(int) const; + std::optional find_in_stack(int) const; + std::optional find_refinement_at_loc(const reg_with_loc_t) const; + std::optional find_refinement_info(register_t reg) const; + void insert_in_registers(register_t, location_t, refinement_t); + void store_in_stack(uint64_t, refinement_t, int); void adjust_bb_for_types(location_t); [[nodiscard]] std::vector& get_errors() { return m_errors; } void reset_errors() { m_errors.clear(); } diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index f85b92f14..cc618bc74 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -895,16 +895,21 @@ void region_domain_t::operator()(const Bin& b, location_t loc) { // nothing to do here } -interval_t region_domain_t::do_bin(const Bin& bin, +void region_domain_t::do_bin(const Bin& bin, const std::optional& src_signed_interval_opt, const std::optional& src_ptr_or_mapfd_opt, const std::optional& dst_signed_interval_opt, const std::optional& dst_ptr_or_mapfd_opt, location_t loc) { - using Op = Bin::Op; - auto dst_register = register_t{bin.dst.v}; + if (!dst_ptr_or_mapfd_opt && !src_ptr_or_mapfd_opt) { + m_registers -= dst_register; + return; + } + + using Op = Bin::Op; + if (std::holds_alternative(bin.v)) { int64_t imm; if (bin.is64) { @@ -988,11 +993,11 @@ interval_t region_domain_t::do_bin(const Bin& bin, update_ptr_or_mapfd(std::move(src_ptr_or_mapfd), std::move(dst_signed), loc, dst_register); } - else if (dst_signed_interval_opt && src_signed_interval_opt) { - // we do not deal with numbers in region domain - m_registers -= dst_register; - } - else { + // else if (dst_signed_interval_opt && src_signed_interval_opt) { + // // we do not deal with numbers in region domain + // m_registers -= dst_register; + // } + else if (dst_ptr_or_mapfd_opt && src_ptr_or_mapfd_opt) { // possibly adding two pointers set_to_bottom(); } @@ -1009,31 +1014,13 @@ interval_t region_domain_t::do_bin(const Bin& bin, else if (src_ptr_or_mapfd_opt && dst_signed_interval_opt) { m_registers -= dst_register; } - else if (dst_signed_interval_opt && src_signed_interval_opt) { - // we do not deal with numbers in region domain - m_registers -= dst_register; - } + // else if (dst_signed_interval_opt && src_signed_interval_opt) { + // // we do not deal with numbers in region domain + // m_registers -= dst_register; + // } else { // ptr -= ptr - if (std::holds_alternative(*dst_ptr_or_mapfd_opt) && - std::holds_alternative(*src_ptr_or_mapfd_opt)) { - m_errors.push_back("mapfd registers subtraction not defined"); - } - else if (same_region(*dst_ptr_or_mapfd_opt, *src_ptr_or_mapfd_opt)) { - if (std::holds_alternative(*dst_ptr_or_mapfd_opt) && - std::holds_alternative(*src_ptr_or_mapfd_opt)) { - auto dst_ptr_with_off = std::get(*dst_ptr_or_mapfd_opt); - auto src_ptr_with_off = std::get(*src_ptr_or_mapfd_opt); - return (dst_ptr_with_off.get_offset().to_interval() - - src_ptr_with_off.get_offset().to_interval()); - } - } - else { - // Assertions should make sure we only perform this on - // non-shared pointers, hence this should not happen - m_errors.push_back("subtraction between pointers of different region"); - } - m_registers -= dst_register; + // this case already handled in type domain } break; } @@ -1044,7 +1031,7 @@ interval_t region_domain_t::do_bin(const Bin& bin, } } } - return interval_t::bottom(); + return; } void region_domain_t::do_load(const Mem& b, const register_t& target_register, bool unknown_ptr, diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index cb2f912c0..5fdeb956a 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -165,7 +165,7 @@ class region_domain_t final { void do_load_mapfd(const register_t&, int, location_t); void do_load(const Mem&, const register_t&, bool, location_t); void do_mem_store(const Mem&, location_t); - interval_t do_bin(const Bin&, const std::optional&, + void do_bin(const Bin&, const std::optional&, const std::optional&, const std::optional&, const std::optional&, location_t); diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index c49c2daee..8f5e39345 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -106,14 +106,25 @@ string_invariant type_domain_t::to_set() const { if (is_top()) return string_invariant::top(); std::set result; for (uint8_t i = 0; i < NUM_REGISTERS; i++) { - std::stringstream elem; auto maybe_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(register_t{i}); - // TODO: Only allowing signed intervals for now, because otherwise parsing needs to be - // changed; support unsigned intervals + auto maybe_rf = m_offset.find_refinement_info(register_t{i}); + if (maybe_ptr_or_mapfd.has_value()) { + std::stringstream elem; + print_register(elem, Reg{i}, maybe_ptr_or_mapfd, maybe_rf, std::nullopt, false); + result.insert(elem.str()); + } auto maybe_signed_interval = m_interval.find_signed_interval_value(register_t{i}); - auto maybe_offset = m_offset.find_offset_info(register_t{i}); - if (maybe_ptr_or_mapfd.has_value() || maybe_signed_interval.has_value()) { - print_register(elem, Reg{i}, maybe_ptr_or_mapfd, maybe_offset, maybe_signed_interval); + if (maybe_signed_interval.has_value()) { + std::stringstream elem; + print_register(elem, Reg{i}, maybe_ptr_or_mapfd, maybe_rf, + maybe_signed_interval, true); + result.insert(elem.str()); + } + auto maybe_unsigned_interval = m_interval.find_unsigned_interval_value(register_t{i}); + if (maybe_unsigned_interval.has_value()) { + std::stringstream elem; + print_register(elem, Reg{i}, maybe_ptr_or_mapfd, maybe_rf, + maybe_unsigned_interval, false); result.insert(elem.str()); } } @@ -121,15 +132,15 @@ string_invariant type_domain_t::to_set() const { for (auto const& k : stack_keys_region) { std::stringstream elem; auto maybe_ptr_or_mapfd_cells = m_region.find_in_stack(k); - auto dist = m_offset.find_in_stack(k); + auto rf = m_offset.find_in_stack(k); if (maybe_ptr_or_mapfd_cells.has_value()) { auto ptr_or_mapfd_cells = maybe_ptr_or_mapfd_cells.value(); int width = ptr_or_mapfd_cells.second; auto ptr_or_mapfd = ptr_or_mapfd_cells.first; elem << "stack"; - if (dist) { + if (rf) { print_non_numeric_memory_cell(elem, k, k+width-1, ptr_or_mapfd, - std::optional(dist->first)); + std::optional(rf->first)); } else { print_non_numeric_memory_cell(elem, k, k+width-1, ptr_or_mapfd); @@ -140,17 +151,24 @@ string_invariant type_domain_t::to_set() const { std::vector stack_keys_interval = m_interval.get_stack_keys(); for (auto const& k : stack_keys_interval) { - std::stringstream elem; - // TODO: Only allowing signed intervals for now, because otherwise parsing needs to be - // changed; support unsigned intervals auto maybe_interval_cells_signed = m_interval.find_in_stack_signed(k); if (maybe_interval_cells_signed.has_value()) { - auto interval_cells = maybe_interval_cells_signed.value(); + std::stringstream elem; + auto signed_interval_cells = maybe_interval_cells_signed.value(); elem << "stack"; - print_numeric_memory_cell(elem, k, k+interval_cells.second-1, - interval_cells.first.to_interval()); + print_numeric_memory_cell(elem, k, k+signed_interval_cells.second, + signed_interval_cells.first.to_interval(), true); + result.insert(elem.str()); + } + auto maybe_interval_cells_unsigned = m_interval.find_in_stack_unsigned(k); + if (maybe_interval_cells_unsigned.has_value()) { + std::stringstream elem; + auto unsigned_interval_cells = maybe_interval_cells_unsigned.value(); + elem << "stack"; + print_numeric_memory_cell(elem, k, k+unsigned_interval_cells.second, + unsigned_interval_cells.first.to_interval(), false); + result.insert(elem.str()); } - result.insert(elem.str()); } return string_invariant{result}; } @@ -161,8 +179,12 @@ void type_domain_t::operator()(const Undefined& u, location_t loc) { void type_domain_t::operator()(const Un& u, location_t loc) { m_region(u, loc); - m_offset(u, loc); m_interval(u, loc); + // TODO: check if we need to get signed values in any case + auto mock_interval_opt = m_interval.find_unsigned_interval_value(u.dst.v); + auto interval = mock_interval_opt ? mock_interval_opt->to_interval() + : interval_t::bottom(); + m_offset.do_un(u, interval, loc); } void type_domain_t::operator()(const LoadMapFd& u, location_t loc) { @@ -505,31 +527,68 @@ void type_domain_t::operator()(const Bin& bin, location_t loc) { dst_unsigned_interval = dst_mock_unsigned_interval->to_interval(); } - interval_t subtracted_reg = m_region.do_bin(bin, src_signed_interval, src_ptr_or_mapfd, - dst_signed_interval, dst_ptr_or_mapfd, loc); - interval_t subtracted_off = m_offset.do_bin(bin, src_signed_interval, src_ptr_or_mapfd, - dst_signed_interval, dst_ptr_or_mapfd, loc); - auto subtracted = subtracted_reg.is_bottom() ? subtracted_off : subtracted_reg; + auto dst_register = register_t{bin.dst.v}; + interval_t subtracted = interval_t::bottom(); + using Op = Bin::Op; + // ptr -= ptr + if (std::holds_alternative(bin.v) && bin.op == Op::SUB) { + if (dst_ptr_or_mapfd && src_ptr_or_mapfd) { + auto dst_ptr = *dst_ptr_or_mapfd; + auto src_ptr = *src_ptr_or_mapfd; + if (std::holds_alternative(dst_ptr) + && std::holds_alternative(src_ptr)) { + m_errors.push_back("mapfd registers subtraction not defined"); + } + else if (same_region(dst_ptr, src_ptr)) { + if (std::holds_alternative(dst_ptr)) { + auto dst_ptr_with_off = std::get(dst_ptr); + auto src_ptr_with_off = std::get(src_ptr); + subtracted = dst_ptr_with_off.get_offset().to_interval() - + src_ptr_with_off.get_offset().to_interval(); + } + else if (std::holds_alternative(dst_ptr)) { + subtracted = interval_t::top(); + } + else { + // Assertions should make sure we only perform this on + // non-shared pointers, hence this should not happen + m_errors.push_back("subtraction between pointers of different region"); + } + } + m_region -= dst_register; + m_offset -= dst_register; + } + } + m_interval.do_bin(bin, src_signed_interval, src_unsigned_interval, src_ptr_or_mapfd, dst_signed_interval, dst_unsigned_interval, dst_ptr_or_mapfd, subtracted, loc); + // TODO: check if we need to get signed values in any case + auto mock_interval_result = m_interval.find_unsigned_interval_value(dst_register); + auto interval_result = std::move(mock_interval_result ? *mock_interval_result + : mock_interval_t::top()); + m_region.do_bin(bin, src_signed_interval, src_ptr_or_mapfd, + dst_signed_interval, dst_ptr_or_mapfd, loc); + m_offset.do_bin(bin, src_signed_interval, src_ptr_or_mapfd, + dst_signed_interval, dst_ptr_or_mapfd, std::move(interval_result), loc); } void type_domain_t::do_load(const Mem& b, const Reg& target_reg, bool unknown_ptr, std::optional basereg_opt, location_t loc) { m_region.do_load(b, register_t{target_reg.v}, unknown_ptr, loc); - m_offset.do_load(b, register_t{target_reg.v}, basereg_opt, loc); // TODO: replace with a bool value returned from region do_load auto load_in_region = m_region.find_ptr_or_mapfd_type(target_reg.v).has_value(); m_interval.do_load(b, register_t{target_reg.v}, basereg_opt, load_in_region, loc); + auto mock_interval_opt = m_interval.find_unsigned_interval_value(target_reg.v); + auto interval = mock_interval_opt ? mock_interval_opt->to_interval() + : interval_t::bottom(); + m_offset.do_load(b, register_t{target_reg.v}, basereg_opt, std::move(interval), loc); } -void type_domain_t::do_mem_store(const Mem& b, std::optional target_opt, - std::optional& basereg_opt, location_t loc) { +void type_domain_t::do_mem_store(const Mem& b, std::optional& basereg_opt, + location_t loc) { m_region.do_mem_store(b, loc); m_interval.do_mem_store(b, basereg_opt); - // TODO: replace target_opt with a bool value representing whether we have a packet pointer, - // because that is the case target_opt is needed for - m_offset.do_mem_store(b, target_opt, basereg_opt); + m_offset.do_mem_store(b, basereg_opt); } void type_domain_t::operator()(const Mem& b, location_t loc) { @@ -543,12 +602,11 @@ void type_domain_t::operator()(const Mem& b, location_t loc) { } if (std::holds_alternative(b.value)) { auto targetreg = std::get(b.value); - auto targetreg_type = m_region.find_ptr_or_mapfd_type(targetreg.v); if (b.is_load) do_load(b, targetreg, unknown_ptr, base_ptr_or_mapfd_opt, loc); - else if (!unknown_ptr) do_mem_store(b, targetreg_type, base_ptr_or_mapfd_opt, loc); + else if (!unknown_ptr) do_mem_store(b, base_ptr_or_mapfd_opt, loc); } else if (!unknown_ptr && !b.is_load) { - do_mem_store(b, std::nullopt, base_ptr_or_mapfd_opt, loc); + do_mem_store(b, base_ptr_or_mapfd_opt, loc); } } @@ -581,7 +639,7 @@ void type_domain_t::print_stack() const { std::cout << "\t\t"; if (dist) { print_non_numeric_memory_cell(std::cout, k, k+width-1, ptr_or_mapfd, - std::optional(dist->first)); + std::optional(dist->first)); } else { print_non_numeric_memory_cell(std::cout, k, k+width-1, ptr_or_mapfd); @@ -590,13 +648,20 @@ void type_domain_t::print_stack() const { } } for (auto const& k : stack_keys_interval) { - auto maybe_interval_cells = m_interval.find_in_stack_signed(k); - // TODO: also handle unsigned intervals - if (maybe_interval_cells) { - auto interval_cells = maybe_interval_cells.value(); + auto maybe_signed_interval_cells = m_interval.find_in_stack_signed(k); + if (maybe_signed_interval_cells) { + auto interval_cells = maybe_signed_interval_cells.value(); std::cout << "\t\t"; print_numeric_memory_cell(std::cout, k, k+interval_cells.second-1, - interval_cells.first.to_interval()); + interval_cells.first.to_interval(), true); + std::cout << ",\n"; + } + auto maybe_unsigned_interval_cells = m_interval.find_in_stack_unsigned(k); + if (maybe_unsigned_interval_cells) { + auto interval_cells = maybe_unsigned_interval_cells.value(); + std::cout << "\t\t"; + print_numeric_memory_cell(std::cout, k, k+interval_cells.second-1, + interval_cells.first.to_interval(), false); std::cout << ",\n"; } } @@ -643,9 +708,9 @@ type_domain_t::find_ptr_or_mapfd_at_loc(const crab::reg_with_loc_t& loc) const { return m_region.find_ptr_or_mapfd_at_loc(loc); } -std::optional -type_domain_t::find_offset_at_loc(const crab::reg_with_loc_t& loc) const { - return m_offset.find_offset_at_loc(loc); +std::optional +type_domain_t::find_refinement_at_loc(const crab::reg_with_loc_t& loc) const { + return m_offset.find_refinement_at_loc(loc); } std::optional @@ -700,11 +765,12 @@ void type_domain_t::store_in_stack_in_unsigned_interval_domain(uint64_t key, moc m_interval.store_in_stack_unsigned(key, p, width); } -void type_domain_t::insert_in_registers_in_offset_domain(register_t r, location_t loc, dist_t d) { +void type_domain_t::insert_in_registers_in_offset_domain(register_t r, location_t loc, + refinement_t d) { m_offset.insert_in_registers(r, loc, d); } -void type_domain_t::store_in_stack_in_offset_domain(uint64_t key, dist_t d, int width) { +void type_domain_t::store_in_stack_in_offset_domain(uint64_t key, refinement_t d, int width) { m_offset.store_in_stack(key, d, width); } @@ -719,6 +785,8 @@ void type_domain_t::store_in_stack_in_region_domain(uint64_t key, ptr_or_mapfd_t type_domain_t type_domain_t::from_predefined_types(const std::set& types, bool setup_constraints) { + // TODO: redo the method according to the new offset domain + // also, need to store the intervals in the offset domain using std::regex; using std::regex_match; @@ -730,7 +798,8 @@ type_domain_t type_domain_t::from_predefined_types(const std::set& #define SHARED_PTR "\\s*shared_p(?:" NUMERIC_NUMERIC_ENCLOSED ")?\\s*" #define CTX_OR_STACK_PTR "\\s*(ctx|stack)_p(?:" NUMERIC_ENCLOSED ")?\\s*" #define PACKET_PTR "\\s*packet_p(?:<(begin|end|meta)\\+" NUMERIC ">)?\\s*" - #define NUMBER "\\s*number(?:" NUMERIC_ENCLOSED ")?\\s*" + #define SNUMBER "\\s*snumber(?:" NUMERIC_ENCLOSED ")?\\s*" + #define UNUMBER "\\s*unumber(?:" NUMERIC_ENCLOSED ")?\\s*" #define MAPFD "\\s*(map_fd|map_fd_programs)" NUMERIC "\\s*" auto create_interval = [](std::string lb, std::string ub) { @@ -780,6 +849,7 @@ type_domain_t type_domain_t::from_predefined_types(const std::set& } }; + /* auto create_pkt_offset = [create_interval](std::string offset_type, std::string offset_lb, std::string offset_ub) { auto offset = create_interval(offset_lb, offset_ub).to_interval(); @@ -795,6 +865,7 @@ type_domain_t type_domain_t::from_predefined_types(const std::set& return dist_t{packet_meta - offset}; } }; + */ type_domain_t typ; if (setup_constraints) { @@ -812,22 +883,28 @@ type_domain_t type_domain_t::from_predefined_types(const std::set& typ.insert_in_registers_in_region_domain(reg, loc, ptr); } else if (regex_match(t, m, regex(REG ":" PACKET_PTR))) { + /* auto reg = register_t{static_cast(std::stoul(m[1]))}; auto ptr = packet_ptr_t{}; auto offset = create_pkt_offset(m[2], m[3], m[4]); typ.insert_in_registers_in_region_domain(reg, loc, ptr); typ.insert_in_registers_in_offset_domain(reg, loc, offset); + */ } else if (regex_match(t, m, regex(REG ":" SHARED_PTR))) { auto reg = register_t{static_cast(std::stoul(m[1]))}; auto ptr = create_ptr("shared", m[2], m[3], m[4], m[5]); typ.insert_in_registers_in_region_domain(reg, loc, ptr); } - else if (regex_match(t, m, regex(REG ":" NUMBER))) { + else if (regex_match(t, m, regex(REG ":" SNUMBER))) { auto reg = register_t{static_cast(std::stoul(m[1]))}; auto num = create_interval(m[2], m[3]).to_interval(); - // TODO: support separately for signed and unsigned intervals - typ.insert_in_registers_in_interval_domain(reg, loc, num); + typ.insert_in_registers_in_signed_interval_domain(reg, loc, num); + } + else if (regex_match(t, m, regex(REG ":" UNUMBER))) { + auto reg = register_t{static_cast(std::stoul(m[1]))}; + auto num = create_interval(m[2], m[3]).to_interval(); + typ.insert_in_registers_in_unsigned_interval_domain(reg, loc, num); } else if (regex_match(t, m, regex(REG ":" MAPFD))) { auto reg = register_t{static_cast(std::stoul(m[1]))}; @@ -842,6 +919,7 @@ type_domain_t type_domain_t::from_predefined_types(const std::set& stack_cell_end-stack_cell_start); } else if (regex_match(t, m, regex(STACK_CELL ":" PACKET_PTR))) { + /* auto stack_cell_start = static_cast(std::stoul(m[1])); auto stack_cell_end = std::stoi(m[2]); auto ptr = packet_ptr_t{}; @@ -849,6 +927,7 @@ type_domain_t type_domain_t::from_predefined_types(const std::set& int width = stack_cell_end - stack_cell_start; typ.store_in_stack_in_region_domain(stack_cell_start, ptr, width); typ.store_in_stack_in_offset_domain(stack_cell_start, pkt_offset, width); + */ } else if (regex_match(t, m, regex(STACK_CELL ":" SHARED_PTR))) { auto stack_cell_start = static_cast(std::stoul(m[1])); @@ -857,11 +936,18 @@ type_domain_t type_domain_t::from_predefined_types(const std::set& typ.store_in_stack_in_region_domain(stack_cell_start, ptr, stack_cell_end-stack_cell_start); } - else if (regex_match(t, m, regex(STACK_CELL ":" NUMBER))) { + else if (regex_match(t, m, regex(STACK_CELL ":" SNUMBER))) { + auto stack_cell_start = static_cast(std::stoul(m[1])); + auto stack_cell_end = std::stoi(m[2]); + auto num = create_interval(m[3], m[4]); + typ.store_in_stack_in_signed_interval_domain(stack_cell_start, num, + stack_cell_end-stack_cell_start); + } + else if (regex_match(t, m, regex(STACK_CELL ":" UNUMBER))) { auto stack_cell_start = static_cast(std::stoul(m[1])); auto stack_cell_end = std::stoi(m[2]); auto num = create_interval(m[3], m[4]); - typ.store_in_stack_in_interval_domain(stack_cell_start, num, + typ.store_in_stack_in_unsigned_interval_domain(stack_cell_start, num, stack_cell_end-stack_cell_start); } else if (regex_match(t, m, regex(STACK_CELL ":" MAPFD))) { @@ -910,19 +996,25 @@ void print_annotated(std::ostream& o, const crab::type_domain_t& typ, ++curr_pos; crab::location_t loc = crab::location_t(std::make_pair(bb.label(), curr_pos)); o << " " << curr_pos << "."; + // TODO: print unsigned intervals in a proper way if (std::holds_alternative(statement)) { auto r0_reg = crab::reg_with_loc_t(register_t{R0_RETURN_VALUE}, loc); auto region = typ.find_ptr_or_mapfd_at_loc(r0_reg); - auto interval = typ.find_signed_interval_at_loc(r0_reg); - print_annotated(o, std::get(statement), region, interval); + auto rf = typ.find_refinement_at_loc(r0_reg); + auto signed_interval = typ.find_signed_interval_at_loc(r0_reg); + print_annotated(o, std::get(statement), region, rf, signed_interval, true); + auto unsigned_interval = typ.find_unsigned_interval_at_loc(r0_reg); + //print_annotated(o, std::get(statement), region, unsigned_interval, false); } else if (std::holds_alternative(statement)) { auto b = std::get(statement); auto reg_with_loc = crab::reg_with_loc_t(b.dst.v, loc); auto region = typ.find_ptr_or_mapfd_at_loc(reg_with_loc); - auto offset = typ.find_offset_at_loc(reg_with_loc); - auto interval = typ.find_signed_interval_at_loc(reg_with_loc); - print_annotated(o, b, region, offset, interval); + auto rf = typ.find_refinement_at_loc(reg_with_loc); + auto signed_interval = typ.find_signed_interval_at_loc(reg_with_loc); + print_annotated(o, b, region, rf, signed_interval, true); + auto unsigned_interval = typ.find_unsigned_interval_at_loc(reg_with_loc); + //print_annotated(o, b, region, rf, unsigned_interval, false); } else if (std::holds_alternative(statement)) { auto u = std::get(statement); @@ -930,9 +1022,11 @@ void print_annotated(std::ostream& o, const crab::type_domain_t& typ, auto target_reg = std::get(u.value); auto target_reg_loc = crab::reg_with_loc_t(target_reg.v, loc); auto region = typ.find_ptr_or_mapfd_at_loc(target_reg_loc); - auto offset = typ.find_offset_at_loc(target_reg_loc); - auto interval = typ.find_signed_interval_at_loc(target_reg_loc); - print_annotated(o, u, region, offset, interval); + auto rf = typ.find_refinement_at_loc(target_reg_loc); + auto signed_interval = typ.find_signed_interval_at_loc(target_reg_loc); + print_annotated(o, u, region, rf, signed_interval, true); + auto unsigned_interval = typ.find_unsigned_interval_at_loc(target_reg_loc); + //print_annotated(o, u, region, rf, unsigned_interval, false); } else o << " " << u << "\n"; } @@ -945,8 +1039,10 @@ void print_annotated(std::ostream& o, const crab::type_domain_t& typ, else if (std::holds_alternative(statement)) { auto u = std::get(statement); auto reg = crab::reg_with_loc_t(u.dst.v, loc); - auto interval = typ.find_signed_interval_at_loc(reg); - print_annotated(o, u, interval); + auto signed_interval = typ.find_signed_interval_at_loc(reg); + print_annotated(o, u, signed_interval, true); + auto unsigned_interval = typ.find_unsigned_interval_at_loc(reg); + print_annotated(o, u, unsigned_interval, false); } else o << " " << statement << "\n"; } diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index 79442d52d..d028af4d3 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -86,7 +86,7 @@ class type_domain_t final { void print_ctx() const; void print_stack() const; std::optional find_ptr_or_mapfd_at_loc(const crab::reg_with_loc_t&) const; - std::optional find_offset_at_loc(const crab::reg_with_loc_t&) const; + std::optional find_refinement_at_loc(const crab::reg_with_loc_t&) const; std::optional find_signed_interval_at_loc(const crab::reg_with_loc_t&) const; std::optional find_unsigned_interval_at_loc(const crab::reg_with_loc_t&) const; static type_domain_t from_predefined_types(const std::set&, bool); @@ -98,15 +98,14 @@ class type_domain_t final { void store_in_stack_in_interval_domain(uint64_t, mock_interval_t, int); void store_in_stack_in_signed_interval_domain(uint64_t, mock_interval_t, int); void store_in_stack_in_unsigned_interval_domain(uint64_t, mock_interval_t, int); - void insert_in_registers_in_offset_domain(register_t, location_t, dist_t); - void store_in_stack_in_offset_domain(uint64_t, dist_t, int); + void insert_in_registers_in_offset_domain(register_t, location_t, refinement_t); + void store_in_stack_in_offset_domain(uint64_t, refinement_t, int); private: void do_load(const Mem&, const Reg&, bool, std::optional, location_t); - void do_mem_store(const Mem&, std::optional, std::optional&, - location_t); + void do_mem_store(const Mem&, std::optional&, location_t); void report_type_error(std::string, location_t); void print_registers() const; void adjust_bb_for_types(location_t); From 27bf7cd9e1c62cf9f37185a1ebf7fb8c187b949d Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 25 Mar 2024 23:28:18 -0400 Subject: [PATCH 158/373] Fixed initialization of registers state in domains Domains seemed to not pass the information about global types in the registers state, fixed it; Signed-off-by: Ameer Hamza --- src/crab/region_domain.cpp | 3 +-- src/crab/signed_interval_domain.cpp | 3 +-- src/crab/unsigned_interval_domain.cpp | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index cc618bc74..b803a6bf2 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -100,8 +100,7 @@ register_types_t register_types_t::operator|(const register_types_t& other) cons } else if (other.is_bottom() || is_top()) { return *this; } - auto region_env = std::make_shared(); - register_types_t joined_reg_types(region_env); + register_types_t joined_reg_types(m_region_env); // a hack to store region information at the start of a joined basic block // in join, we do not know the label of the bb, hence we store the information diff --git a/src/crab/signed_interval_domain.cpp b/src/crab/signed_interval_domain.cpp index 73e3f9a05..251af1e76 100644 --- a/src/crab/signed_interval_domain.cpp +++ b/src/crab/signed_interval_domain.cpp @@ -53,8 +53,7 @@ registers_signed_state_t registers_signed_state_t::operator|(const registers_sig } else if (other.is_bottom() || is_top()) { return *this; } - auto intervals_env = std::make_shared(); - registers_signed_state_t intervals_joined(intervals_env); + registers_signed_state_t intervals_joined(m_interval_env); location_t loc = location_t(std::make_pair(label_t(-2, -2), 0)); for (uint8_t i = 0; i < NUM_REGISTERS; i++) { if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; diff --git a/src/crab/unsigned_interval_domain.cpp b/src/crab/unsigned_interval_domain.cpp index b6c105d82..0dfa6c359 100644 --- a/src/crab/unsigned_interval_domain.cpp +++ b/src/crab/unsigned_interval_domain.cpp @@ -53,8 +53,7 @@ registers_unsigned_state_t registers_unsigned_state_t::operator|(const registers } else if (other.is_bottom() || is_top()) { return *this; } - auto intervals_env = std::make_shared(); - registers_unsigned_state_t intervals_joined(intervals_env); + registers_unsigned_state_t intervals_joined(m_interval_env); location_t loc = location_t(std::make_pair(label_t(-2, -2), 0)); for (uint8_t i = 0; i < NUM_REGISTERS; i++) { if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; From 6ec70f6fa025f94c355ed6fc8bc95e1bf7f4fa3c Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Thu, 28 Mar 2024 00:51:00 -0400 Subject: [PATCH 159/373] Activated some run-time checks previously removed Some checks to the type domain were added before, that check for whether a register has value in both interval and region domain, in which case, those should fail; Those checks seemed to be violated before, and resulted in assert errors; Those seem to now work fine, hence activated; Signed-off-by: Ameer Hamza --- src/crab/type_domain.cpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 8f5e39345..2dd80ce19 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -254,12 +254,12 @@ void type_domain_t::operator()(const Assume& s, location_t loc) { Condition cond = s.cond; const auto& maybe_left_type = m_region.find_ptr_or_mapfd_type(cond.left.v); const auto& maybe_left_interval = m_interval.find_interval_value(cond.left.v); - //assert(!maybe_left_type.has_value() || !maybe_left_interval.has_value()); + assert(!maybe_left_type.has_value() || !maybe_left_interval.has_value()); if (std::holds_alternative(cond.right)) { const auto& right_reg = std::get(cond.right); const auto& maybe_right_type = m_region.find_ptr_or_mapfd_type(right_reg.v); const auto& maybe_right_interval = m_interval.find_interval_value(right_reg.v); - //assert(!maybe_right_type.has_value() || !maybe_right_interval.has_value()); + assert(!maybe_right_type.has_value() || !maybe_right_interval.has_value()); if (same_type(maybe_left_type, maybe_right_type, maybe_left_interval, maybe_right_interval)) { if (maybe_left_interval) { @@ -304,7 +304,7 @@ void type_domain_t::operator()(const Assume& s, location_t loc) { void type_domain_t::operator()(const ValidDivisor& u, location_t loc) { auto maybe_ptr_or_mapfd_reg = m_region.find_ptr_or_mapfd_type(u.reg.v); auto maybe_num_type_reg = m_interval.find_unsigned_interval_value(u.reg.v); - //assert(!maybe_ptr_or_mapfd_reg.has_value() || !maybe_num_type_reg.has_value()); + assert(!maybe_ptr_or_mapfd_reg.has_value() || !maybe_num_type_reg.has_value()); if (is_ptr_type(maybe_ptr_or_mapfd_reg)) { m_errors.push_back("Only numbers can be used as divisors"); @@ -361,9 +361,9 @@ void type_domain_t::operator()(const ValidAccess& s, location_t loc) { } void type_domain_t::operator()(const TypeConstraint& s, location_t loc) { - //auto reg_type = m_region.find_ptr_or_mapfd_type(s.reg.v); - //auto mock_interval_type = m_interval.find_interval_value(s.reg.v); - //assert(!reg_type.has_value() || !mock_interval_type.has_value()); + auto reg_type = m_region.find_ptr_or_mapfd_type(s.reg.v); + auto mock_interval_type = m_interval.find_interval_value(s.reg.v); + assert(!reg_type.has_value() || !mock_interval_type.has_value()); m_region(s, loc); } @@ -375,10 +375,10 @@ void type_domain_t::operator()(const Comparable& u, location_t loc) { auto maybe_ptr_or_mapfd1 = m_region.find_ptr_or_mapfd_type(u.r1.v); auto maybe_ptr_or_mapfd2 = m_region.find_ptr_or_mapfd_type(u.r2.v); - //auto maybe_num_type1 = m_interval.find_interval_value(u.r1.v); - //auto maybe_num_type2 = m_interval.find_interval_value(u.r2.v); - //assert(!maybe_ptr_or_mapfd1.has_value() || !maybe_num_type1.has_value()); - //assert(!maybe_ptr_or_mapfd2.has_value() || !maybe_num_type2.has_value()); + auto maybe_num_type1 = m_interval.find_interval_value(u.r1.v); + auto maybe_num_type2 = m_interval.find_interval_value(u.r2.v); + assert(!maybe_ptr_or_mapfd1.has_value() || !maybe_num_type1.has_value()); + assert(!maybe_ptr_or_mapfd2.has_value() || !maybe_num_type2.has_value()); if (maybe_ptr_or_mapfd1 && maybe_ptr_or_mapfd2) { if (is_mapfd_type(maybe_ptr_or_mapfd1) && is_mapfd_type(maybe_ptr_or_mapfd2)) return; if (!is_shared_ptr(maybe_ptr_or_mapfd1) @@ -397,10 +397,10 @@ void type_domain_t::operator()(const Comparable& u, location_t loc) { void type_domain_t::operator()(const Addable& u, location_t loc) { auto maybe_ptr_or_mapfd_ptr = m_region.find_ptr_or_mapfd_type(u.ptr.v); auto maybe_ptr_or_mapfd_num = m_region.find_ptr_or_mapfd_type(u.num.v); - //auto maybe_num_type_ptr = m_interval.find_interval_value(u.ptr.v); + auto maybe_num_type_ptr = m_interval.find_interval_value(u.ptr.v); auto maybe_num_type_num = m_interval.find_interval_value(u.num.v); - //assert(!maybe_ptr_or_mapfd_ptr.has_value() || !maybe_num_type_ptr.has_value()); - //assert(!maybe_ptr_or_mapfd_num.has_value() || !maybe_num_type_num.has_value()); + assert(!maybe_ptr_or_mapfd_ptr.has_value() || !maybe_num_type_ptr.has_value()); + assert(!maybe_ptr_or_mapfd_num.has_value() || !maybe_num_type_num.has_value()); // a -> b <-> !a || b // is_ptr(ptr) -> is_num(num) <-> !is_ptr(ptr) || is_num(num) @@ -414,10 +414,10 @@ void type_domain_t::operator()(const Addable& u, location_t loc) { void type_domain_t::operator()(const ValidStore& u, location_t loc) { auto maybe_ptr_or_mapfd_mem = m_region.find_ptr_or_mapfd_type(u.mem.v); auto maybe_ptr_or_mapfd_val = m_region.find_ptr_or_mapfd_type(u.val.v); - //auto maybe_num_type_mem = m_interval.find_interval_value(u.mem.v); + auto maybe_num_type_mem = m_interval.find_interval_value(u.mem.v); auto maybe_num_type_val = m_interval.find_interval_value(u.val.v); - //assert(!maybe_ptr_or_mapfd_mem.has_value() || !maybe_num_type_mem.has_value()); - //assert(!maybe_ptr_or_mapfd_val.has_value() || !maybe_num_type_val.has_value()); + assert(!maybe_ptr_or_mapfd_mem.has_value() || !maybe_num_type_mem.has_value()); + assert(!maybe_ptr_or_mapfd_val.has_value() || !maybe_num_type_val.has_value()); // a -> b <-> !a || b // !is_stack_ptr(mem) -> is_num(val) <-> is_stack_ptr(mem) || is_num(val) @@ -431,7 +431,7 @@ void type_domain_t::operator()(const ValidStore& u, location_t loc) { void type_domain_t::operator()(const ValidSize& u, location_t loc) { auto maybe_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(u.reg.v); auto maybe_num_type = m_interval.find_interval_value(u.reg.v); - //assert(!maybe_ptr_or_mapfd || !maybe_num_type); + assert(!maybe_ptr_or_mapfd || !maybe_num_type); if (maybe_num_type) { auto reg_value = maybe_num_type.value(); From f54cd8400402dcc71acd00bb835f81dc7d86071c Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Thu, 28 Mar 2024 17:51:43 -0400 Subject: [PATCH 160/373] Support for precise packet pointers subtraction Signed-off-by: Ameer Hamza --- src/crab/constraint.cpp | 4 +++ src/crab/constraint.hpp | 1 + src/crab/expression.cpp | 3 ++ src/crab/offset_domain.cpp | 26 ++++++++++++++-- src/crab/offset_domain.hpp | 1 + src/crab/refinement.cpp | 64 +++++++++++++++++++++++++++++++++++++- src/crab/refinement.hpp | 2 ++ src/crab/type_domain.cpp | 12 ++++--- 8 files changed, 105 insertions(+), 8 deletions(-) diff --git a/src/crab/constraint.cpp b/src/crab/constraint.cpp index 4fd3eba22..294718ef5 100644 --- a/src/crab/constraint.cpp +++ b/src/crab/constraint.cpp @@ -8,6 +8,10 @@ std::ostream &operator<<(std::ostream &o, const constraint_t &c) { return o; } +bool constraint_t::contains(const symbol_t &s) const { + return _expression_lhs.contains(s) || _expression_rhs.contains(s); +} + void constraint_t::simplify(std::shared_ptr slacks) { auto neg = _expression_rhs.negate(); _expression_lhs = _expression_lhs + _expression_rhs.negate(); diff --git a/src/crab/constraint.hpp b/src/crab/constraint.hpp index c70cad2d8..75a6ce9c8 100644 --- a/src/crab/constraint.hpp +++ b/src/crab/constraint.hpp @@ -32,6 +32,7 @@ class constraint_t { constraint_t operator<=(const constraint_t&) const; constraint_t operator>(const constraint_t&) const; bool is_equality() const; + bool contains(const symbol_t&) const; void simplify(std::shared_ptr); [[nodiscard]] constraint_t negate() const; void write(std::ostream&) const; diff --git a/src/crab/expression.cpp b/src/crab/expression.cpp index a47b8df38..136b24c1a 100644 --- a/src/crab/expression.cpp +++ b/src/crab/expression.cpp @@ -42,6 +42,9 @@ symbol_t expression_t::get_singleton() const { static void insert(symbol_terms_t &terms, const std::pair& kv) { if (terms.find(kv.first) != terms.end()) { terms[kv.first] += kv.second; + if (terms[kv.first] == 0) { + terms.erase(kv.first); + } } else { terms[kv.first] = kv.second; } diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index 0a4c9dcf8..623980795 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -452,6 +452,29 @@ void offset_domain_t::operator()(const Assume &b, location_t loc) { } } +interval_t offset_domain_t::compute_packet_subtraction(register_t dst, register_t src) const { + auto dst_rf = m_reg_state.find(dst); + auto src_rf = m_reg_state.find(src); + if (!dst_rf || !src_rf) return interval_t::bottom(); + expression_t dst_expr = dst_rf->get_value(); + expression_t src_expr = src_rf->get_value(); + refinement_t result_rf = *dst_rf - *src_rf; + expression_t result_expr = result_rf.get_value(); + if (result_expr.is_constant()) { + return result_expr.get_interval(); + } + // with non-singleton expressions, we might be able to compute subtraction, + // but it might be complicated + if (!dst_expr.is_singleton() || !src_expr.is_singleton()) return interval_t::top(); + auto dst_symbol = dst_expr.get_singleton(); + auto src_symbol = src_expr.get_singleton(); + std::optional begin_rf = m_reg_state.find(register_t{11}); + if (!begin_rf) return interval_t::top(); + interval_t result_interval = result_rf.simplify_for_subtraction(dst_symbol, src_symbol, + begin_rf->get_constraints(), m_reg_state.get_slacks()); + return result_interval; +} + static void create_numeric_refinement(registers_state_t& reg_state, mock_interval_t&& interval, location_t loc, register_t reg) { symbol_t s = symbol_t::make(); @@ -488,7 +511,7 @@ void offset_domain_t::do_bin(const Bin& bin, auto imm_interval = interval_t{number_t{imm}}; switch (bin.op) { case Op::MOV: { - // ra = imm, we forget the type in the offset domain + // ra = imm create_numeric_refinement(m_reg_state, std::move(interval_result), loc, dst_register); break; @@ -570,7 +593,6 @@ void offset_domain_t::do_bin(const Bin& bin, } case Op::SUB: { // ra -= rb - // TODO: be precise with ptr -= ptr, and possibly assign a slack to the result if (is_packet_ptr(src_ptr_or_mapfd_opt) && is_packet_ptr(dst_ptr_or_mapfd_opt)) { create_numeric_refinement(m_reg_state, std::move(interval_result), loc, dst_register); diff --git a/src/crab/offset_domain.hpp b/src/crab/offset_domain.hpp index 1842e4040..409fa852a 100644 --- a/src/crab/offset_domain.hpp +++ b/src/crab/offset_domain.hpp @@ -174,6 +174,7 @@ class offset_domain_t final { void do_call(const Call&, const stack_cells_t&, location_t); bool check_packet_access(const Reg&, int, int, bool) const; void check_valid_access(const ValidAccess&, std::optional&, int); + interval_t compute_packet_subtraction(register_t, register_t) const; std::optional find_in_ctx(int) const; std::optional find_in_stack(int) const; diff --git a/src/crab/refinement.cpp b/src/crab/refinement.cpp index 6a137bc01..ea9e781f4 100644 --- a/src/crab/refinement.cpp +++ b/src/crab/refinement.cpp @@ -133,8 +133,70 @@ refinement_t refinement_t::operator+(const refinement_t &other) const { return refinement_t(new_type, new_value, new_constraints); } +interval_t refinement_t::simplify_for_subtraction(const symbol_t& dst, const symbol_t& src, + const std::vector& constraints, std::shared_ptr slacks) const { + bound_t max_packet_size = bound_t{number_t{MAX_PACKET_SIZE}}; + bound_t max_meta_size = bound_t{number_t{4098}}; + bound_t zero = bound_t{number_t{0}}; + symbol_t begin = symbol_t::begin(); + symbol_t end = symbol_t::end(); + symbol_t meta = symbol_t::meta(); + + if (src != begin && src != meta && src != end && dst != begin && dst != meta && dst != end) { + // We currently only support subtraction between begin, meta, and end + return interval_t::top(); + } + + // we can do better in case both end and meta are involved, but for now we should be okay + if (dst == end && src == meta) { + return interval_t{zero, max_meta_size + max_packet_size}; + } + else if (dst == meta && src == end) { + return interval_t{-(max_meta_size + max_packet_size), zero}; + } + // we only have cases (begin, meta), and (begin, end) left + bound_t max_size = (dst == meta || src == meta) ? max_meta_size : max_packet_size; + + interval_t result = interval_t::top(); + for (constraint_t c : constraints) { + c.simplify(slacks); + if (c.contains(dst) && c.contains(src)) { + expression_t lhs = c.get_lhs(); + expression_t rhs = c.get_rhs(); + lhs = lhs + rhs.negate(); + rhs = expression_t{0}; + if (lhs.contains(dst) && lhs.contains(src)) { + symbol_terms_t terms = lhs.get_symbol_terms(); + if (terms.size() == 2) { + if (terms[dst] == 1 && terms[src] == -1) { + // dst - src + [lb, ub] <= 0 -> dst - src <= -lb + interval_t lhs_interval = lhs.get_interval(); + result = result & interval_t{-max_size, -lhs_interval.lb()}; + } + else if (terms[dst] == -1 && terms[src] == 1) { + // src - dst + [lb, ub] <= 0 -> dst - src >= -lb + interval_t lhs_interval = lhs.get_interval(); + result = result & interval_t{lhs_interval.lb(), max_size}; + } + } + } + } + } + // If no constraints were found, we can still infer some bounds + if (result == interval_t::top()) { + if (src == end) + result = interval_t{-max_packet_size, zero}; + else if (dst == end) + result = interval_t{zero, max_packet_size}; + else if (src == meta) + result = interval_t{zero, max_meta_size}; + else if (dst == meta) + result = interval_t{-max_meta_size, zero}; + } + return result; +} + refinement_t refinement_t::operator-(const refinement_t &other) const { - // TODO: Handle subtraction between packet pointers expression_t new_value = _value + other._value.negate(); std::vector new_constraints; new_constraints.insert(new_constraints.end(), _constraints.begin(), _constraints.end()); diff --git a/src/crab/refinement.hpp b/src/crab/refinement.hpp index b9ec68ce2..e01083909 100644 --- a/src/crab/refinement.hpp +++ b/src/crab/refinement.hpp @@ -40,6 +40,8 @@ class refinement_t { void add_constraint(const constraint_t&); void simplify(); bool is_safe_with(refinement_t, std::shared_ptr, bool) const; + interval_t simplify_for_subtraction(const symbol_t&, const symbol_t&, + const std::vector&, std::shared_ptr) const; static refinement_t begin() { return refinement_t(data_type_t::PACKET, expression_t::begin()); diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 2dd80ce19..6fc6c3537 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -547,7 +547,9 @@ void type_domain_t::operator()(const Bin& bin, location_t loc) { src_ptr_with_off.get_offset().to_interval(); } else if (std::holds_alternative(dst_ptr)) { - subtracted = interval_t::top(); + register_t src_reg = std::get(bin.v).v; + register_t dst_reg = bin.dst.v; + subtracted = m_offset.compute_packet_subtraction(dst_reg, src_reg); } else { // Assertions should make sure we only perform this on @@ -562,12 +564,12 @@ void type_domain_t::operator()(const Bin& bin, location_t loc) { m_interval.do_bin(bin, src_signed_interval, src_unsigned_interval, src_ptr_or_mapfd, dst_signed_interval, dst_unsigned_interval, dst_ptr_or_mapfd, subtracted, loc); - // TODO: check if we need to get signed values in any case - auto mock_interval_result = m_interval.find_unsigned_interval_value(dst_register); - auto interval_result = std::move(mock_interval_result ? *mock_interval_result - : mock_interval_t::top()); m_region.do_bin(bin, src_signed_interval, src_ptr_or_mapfd, dst_signed_interval, dst_ptr_or_mapfd, loc); + + auto mock_interval_result = m_interval.find_signed_interval_value(dst_register); + auto interval_result = std::move(mock_interval_result ? *mock_interval_result + : mock_interval_t::top()); m_offset.do_bin(bin, src_signed_interval, src_ptr_or_mapfd, dst_signed_interval, dst_ptr_or_mapfd, std::move(interval_result), loc); } From 0d3feae6a4534a713c1d83f6839e78cc867d4181 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Thu, 28 Mar 2024 18:06:22 -0400 Subject: [PATCH 161/373] Better handling for mem_store in offset domain When storing an immediate value, we did not construct a refinement type to store into stack in offset domain, so later when loaded from stack, we have a proper type; This case does not seem to occur in current benchmarks, but good to keep analysis consistent; Signed-off-by: Ameer Hamza --- src/crab/offset_domain.cpp | 32 ++++++++++++++++++++------------ src/crab/region_domain.cpp | 2 +- src/crab/region_domain.hpp | 2 +- src/crab/type_domain.cpp | 9 ++++----- src/crab/type_domain.hpp | 2 +- 5 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index 623980795..764fcc4d4 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -759,22 +759,30 @@ void offset_domain_t::operator()(const basic_block_t& bb, int print) { void offset_domain_t::do_mem_store(const Mem& b, std::optional& maybe_basereg_type) { - auto target_reg = std::get(b.value); - auto rf_info = m_reg_state.find(target_reg.v); + std::optional rf_info = std::nullopt; + if (!is_stack_ptr(maybe_basereg_type)) return; + + if (std::holds_alternative(b.value)) { + auto target_reg = std::get(b.value); + rf_info = m_reg_state.find(target_reg.v); + } + else { + symbol_t s = symbol_t::make(); + rf_info = refinement_t(data_type_t::NUM, expression_t(s)); + auto mock_interval = mock_interval_t{bound_t{(uint64_t)std::get(b.value).v}}; + m_reg_state.insert_slack_value(s, std::move(mock_interval)); + } if (!rf_info) return; int offset = b.access.offset; int width = b.access.width; - if (is_stack_ptr(maybe_basereg_type)) { - auto basereg_with_off = std::get(*maybe_basereg_type); - auto basereg_off_singleton = basereg_with_off.get_offset().to_interval().singleton(); - if (!basereg_off_singleton) return; - auto store_at = (uint64_t)(*basereg_off_singleton + offset); - auto overlapping_cells = m_stack_state.find_overlapping_cells(store_at, width); - m_stack_state -= overlapping_cells; - - m_stack_state.store(store_at, *rf_info, width); - } + auto basereg_with_off = std::get(*maybe_basereg_type); + auto basereg_off_singleton = basereg_with_off.get_offset().to_interval().singleton(); + if (!basereg_off_singleton) return; + auto store_at = (uint64_t)(*basereg_off_singleton + offset); + auto overlapping_cells = m_stack_state.find_overlapping_cells(store_at, width); + m_stack_state -= overlapping_cells; + m_stack_state.store(store_at, *rf_info, width); } void offset_domain_t::do_load(const Mem& b, const register_t& target_register, diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index b803a6bf2..2b29663ed 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -1130,7 +1130,7 @@ void region_domain_t::operator()(const Mem& m, location_t loc) { // nothing to do here } -void region_domain_t::do_mem_store(const Mem& b, location_t loc) { +void region_domain_t::do_mem_store(const Mem& b) { std::optional targetreg_type = {}; bool target_is_reg = std::holds_alternative(b.value); diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index 5fdeb956a..9cdcc151c 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -164,7 +164,7 @@ class region_domain_t final { std::optional get_map_inner_map_fd(const Reg&) const; void do_load_mapfd(const register_t&, int, location_t); void do_load(const Mem&, const register_t&, bool, location_t); - void do_mem_store(const Mem&, location_t); + void do_mem_store(const Mem&); void do_bin(const Bin&, const std::optional&, const std::optional&, const std::optional&, diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 6fc6c3537..6336b61d1 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -586,9 +586,8 @@ void type_domain_t::do_load(const Mem& b, const Reg& target_reg, bool unknown_pt m_offset.do_load(b, register_t{target_reg.v}, basereg_opt, std::move(interval), loc); } -void type_domain_t::do_mem_store(const Mem& b, std::optional& basereg_opt, - location_t loc) { - m_region.do_mem_store(b, loc); +void type_domain_t::do_mem_store(const Mem& b, std::optional& basereg_opt) { + m_region.do_mem_store(b); m_interval.do_mem_store(b, basereg_opt); m_offset.do_mem_store(b, basereg_opt); } @@ -605,10 +604,10 @@ void type_domain_t::operator()(const Mem& b, location_t loc) { if (std::holds_alternative(b.value)) { auto targetreg = std::get(b.value); if (b.is_load) do_load(b, targetreg, unknown_ptr, base_ptr_or_mapfd_opt, loc); - else if (!unknown_ptr) do_mem_store(b, base_ptr_or_mapfd_opt, loc); + else if (!unknown_ptr) do_mem_store(b, base_ptr_or_mapfd_opt); } else if (!unknown_ptr && !b.is_load) { - do_mem_store(b, base_ptr_or_mapfd_opt, loc); + do_mem_store(b, base_ptr_or_mapfd_opt); } } diff --git a/src/crab/type_domain.hpp b/src/crab/type_domain.hpp index d028af4d3..c99645b97 100644 --- a/src/crab/type_domain.hpp +++ b/src/crab/type_domain.hpp @@ -105,7 +105,7 @@ class type_domain_t final { void do_load(const Mem&, const Reg&, bool, std::optional, location_t); - void do_mem_store(const Mem&, std::optional&, location_t); + void do_mem_store(const Mem&, std::optional&); void report_type_error(std::string, location_t); void print_registers() const; void adjust_bb_for_types(location_t); From 78c8072c1c994d73ee5c81f11399f6caaf3ad9e6 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Thu, 28 Mar 2024 18:36:40 -0400 Subject: [PATCH 162/373] Certain case when checking packet access fixed When the packet access check is a comparison check (we check that the access is within MAX_PACKET_SIZE, rather than end), we get constraints like 65535 <= begin + c, which should be easily solvable, as begin is assumed to be at offset 0, fixed it using an extra check; It is possible other cases might also need such handling; Signed-off-by: Ameer Hamza --- src/crab/constraint.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/crab/constraint.cpp b/src/crab/constraint.cpp index 294718ef5..fb4d48268 100644 --- a/src/crab/constraint.cpp +++ b/src/crab/constraint.cpp @@ -33,6 +33,11 @@ void constraint_t::simplify(std::shared_ptr slacks) { bool constraint_t::is_bottom(std::shared_ptr slacks) { simplify(slacks); + symbol_terms_t terms = _expression_lhs.get_symbol_terms(); + if (terms.size() == 1 && terms.begin()->first == symbol_t::begin()) { + // we can sometimes simplify if we have a single symbol begin, since it is always offset 0 + _expression_lhs -= terms.begin()->first; + } if (!_expression_lhs.is_constant()) { return false; } From fbed2cd49a30018c09f35e1d7c7969fc06542418 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Sat, 30 Mar 2024 01:24:27 -0400 Subject: [PATCH 163/373] Added a small heuristic for assume Added a small heuristic for assume transformer, when both intervals are top, then we can skip the computation; There might be a sound analysis required here, but this will do for now; Signed-off-by: Ameer Hamza --- src/crab/interval_domain.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/crab/interval_domain.cpp b/src/crab/interval_domain.cpp index 7dc66d6c9..8ce9cddd5 100644 --- a/src/crab/interval_domain.cpp +++ b/src/crab/interval_domain.cpp @@ -401,6 +401,11 @@ void interval_domain_t::assume_unsigned_cst(Condition::Op op, bool is64, const interval_t& right_signed, const interval_t& right_unsigned, register_t left, Value right, location_t loc) { + if (left_unsigned.is_top() && right_unsigned.is_top()) { + // this is a drastic heuristic + return; + } + auto left_interval = interval_t::bottom(); auto right_interval = interval_t::bottom(); get_unsigned_intervals(is64, left_signed, left_unsigned, right_unsigned, @@ -773,6 +778,11 @@ void interval_domain_t::assume_signed_cst(Condition::Op op, bool is64, const interval_t& right_signed, const interval_t& right_unsigned, register_t left, Value right, location_t loc) { + if (left_unsigned.is_top() && right_unsigned.is_top()) { + // this is a drastic heuristic + return; + } + auto left_interval = interval_t::bottom(); auto right_interval = interval_t::bottom(); get_signed_intervals(is64, left_signed, left_unsigned, right_signed, From 292ce6112a54aea01cf31bd4f2ec21a4b4303181 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Sat, 30 Mar 2024 01:26:21 -0400 Subject: [PATCH 164/373] Fixed assert type constraint for numbers We were assuming that if a register is not a pointer, it is going to be a number, which is not true always: added that check; Fixed small error printing issue in ValidMapKeyValue; Signed-off-by: Ameer Hamza --- src/crab/region_domain.cpp | 8 ++++++-- src/crab/region_domain.hpp | 1 + src/crab/type_domain.cpp | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index 2b29663ed..63eb6c7c4 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -822,6 +822,11 @@ region_domain_t&& region_domain_t::setup_entry(bool init_r1) { } void region_domain_t::operator()(const TypeConstraint& s, location_t loc) { + // nothing to do here +} + +void region_domain_t::check_type(const TypeConstraint& s, + std::optional interval_opt) { auto ptr_or_mapfd_opt = m_registers.find(s.reg.v); if (ptr_or_mapfd_opt) { // it is a pointer or mapfd @@ -862,8 +867,7 @@ void region_domain_t::operator()(const TypeConstraint& s, location_t loc) { } } } - else { - // if we don't know the type, we assume it is a number + else if (interval_opt) { if (s.types == TypeGroup::number || s.types == TypeGroup::ptr_or_num || s.types == TypeGroup::non_map_fd || s.types == TypeGroup::mem_or_num) return; diff --git a/src/crab/region_domain.hpp b/src/crab/region_domain.hpp index 9cdcc151c..1ac9bd1c9 100644 --- a/src/crab/region_domain.hpp +++ b/src/crab/region_domain.hpp @@ -162,6 +162,7 @@ class region_domain_t final { interval_t get_map_key_size(const Reg&) const; std::optional get_map_type(const Reg&) const; std::optional get_map_inner_map_fd(const Reg&) const; + void check_type(const TypeConstraint&, std::optional); void do_load_mapfd(const register_t&, int, location_t); void do_load(const Mem&, const register_t&, bool, location_t); void do_mem_store(const Mem&); diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 6336b61d1..224d32641 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -364,7 +364,7 @@ void type_domain_t::operator()(const TypeConstraint& s, location_t loc) { auto reg_type = m_region.find_ptr_or_mapfd_type(s.reg.v); auto mock_interval_type = m_interval.find_interval_value(s.reg.v); assert(!reg_type.has_value() || !mock_interval_type.has_value()); - m_region(s, loc); + m_region.check_type(s, mock_interval_type); } void type_domain_t::operator()(const Assert& u, location_t loc) { @@ -486,6 +486,7 @@ void type_domain_t::operator()(const ValidMapKeyValue& u, location_t loc) { } else { m_errors.push_back("Only stack or packet can be used as a parameter"); + return; } } } From a3bb3a3aa55c1f369f7a928a8e8d1103f4eb0cf0 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 1 Apr 2024 01:30:55 -0400 Subject: [PATCH 165/373] Removing redundant code from offset domain Some code in offset domain related to the previous version of it was not removed, which is done now; Signed-off-by: Ameer Hamza --- src/crab/offset_domain.cpp | 39 -------------------------------------- 1 file changed, 39 deletions(-) diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index 764fcc4d4..b1ebaf2d1 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -691,45 +691,6 @@ void offset_domain_t::operator()(const Packet& u, location_t loc) { m_reg_state.scratch_caller_saved_registers(); } -bool offset_domain_t::lower_bound_satisfied(const dist_t& dist, int offset) const { - auto meta_limit = m_extra_constraints.get_meta_limit(); - auto end_limit = m_extra_constraints.get_end_limit(); - - dist_t dist1 = dist; - if (dist.is_meta_pointer()) { - dist1 = dist_t(dist.offset_from_reference() + (meta_limit ? - weight_t{*meta_limit-number_t{PACKET_META}} : weight_t{number_t{0}})); - } - if (dist.is_backward_pointer()) { - dist1 = dist_t(dist.offset_from_reference() - + (end_limit ? weight_t{*end_limit} : weight_t{number_t{0}})); - } - - bound_t lb = meta_limit ? *meta_limit-number_t{PACKET_META} : bound_t{number_t{0}}; - return (dist1.m_dist.lb()+number_t{offset} >= lb); -} - -bool offset_domain_t::upper_bound_satisfied(const dist_t& dist, int offset, int width, - bool is_comparison_check) const { - auto meta_limit = m_extra_constraints.get_meta_limit(); - auto end_limit = m_extra_constraints.get_end_limit(); - - dist_t dist1 = dist; - if (dist.is_meta_pointer()) { - dist1 = dist_t(dist.offset_from_reference() + (meta_limit ? - weight_t{*meta_limit-number_t{PACKET_META}} : weight_t{number_t{0}})); - } - if (dist.is_backward_pointer()) { - dist1 = dist_t(dist.offset_from_reference() - + (end_limit ? weight_t{*end_limit} : - weight_t{number_t{is_comparison_check ? MAX_PACKET_SIZE : 0}})); - } - - bound_t ub = is_comparison_check ? bound_t{MAX_PACKET_SIZE} - : (end_limit ? *end_limit : number_t{0}); - return (dist1.m_dist.ub()+number_t{offset+width} <= ub); -} - bool offset_domain_t::check_packet_access(const Reg& r, int width, int offset, bool is_comparison_check) const { auto begin = m_reg_state.find(register_t{11}); From 898494f8462dec25577cd59b334d3eba50c3356d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 6 Apr 2024 16:52:13 +0000 Subject: [PATCH 166/373] Bump ebpf-samples from `ce6d479` to `5d65552` Bumps [ebpf-samples](https://github.com/vbpf/ebpf-samples) from `ce6d479` to `5d65552`. - [Release notes](https://github.com/vbpf/ebpf-samples/releases) - [Commits](https://github.com/vbpf/ebpf-samples/compare/ce6d4793d8b851683a86c6af675a02e39f91f0db...5d6555274119147c6ea69866712db3e4fa964423) --- updated-dependencies: - dependency-name: ebpf-samples dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- ebpf-samples | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebpf-samples b/ebpf-samples index ce6d4793d..5d6555274 160000 --- a/ebpf-samples +++ b/ebpf-samples @@ -1 +1 @@ -Subproject commit ce6d4793d8b851683a86c6af675a02e39f91f0db +Subproject commit 5d6555274119147c6ea69866712db3e4fa964423 From 934951e918ea3c72308b0908445c91c88989bd7a Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Sun, 21 Jan 2024 21:13:28 -0800 Subject: [PATCH 167/373] Stack memory tracking improvements Fixes #566 Signed-off-by: Dave Thaler --- src/crab/array_domain.cpp | 85 ++++++++++++++++++++++++-- src/crab/array_domain.hpp | 4 ++ test-data/stack.yaml | 122 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 207 insertions(+), 4 deletions(-) diff --git a/src/crab/array_domain.cpp b/src/crab/array_domain.cpp index ec5be435d..aedf17f20 100644 --- a/src/crab/array_domain.cpp +++ b/src/crab/array_domain.cpp @@ -110,9 +110,9 @@ class cell_t final { return {number_t{static_cast(o)}, number_t{static_cast(o)} + number_t{static_cast(size - 1)}}; } + public: [[nodiscard]] interval_t to_interval() const { return to_interval(_offset, _size); } - public: [[nodiscard]] bool is_null() const { return _offset == 0 && _size == 0; } [[nodiscard]] offset_t get_offset() const { return _offset; } @@ -447,6 +447,72 @@ std::ostream& operator<<(std::ostream& o, offset_map_t& m) { return o; } +// Create a new cell that is a subset of an existing cell. +void array_domain_t::split_cell(NumAbsDomain& inv, data_kind_t kind, int cell_start_index, unsigned int len) { + assert(kind == data_kind_t::svalues || kind == data_kind_t::uvalues); + + // Get the values from the indicated stack range. + std::optional svalue = load(inv, data_kind_t::svalues, number_t(cell_start_index), len); + std::optional uvalue = load(inv, data_kind_t::uvalues, number_t(cell_start_index), len); + + // Create a new cell for that range. + offset_map_t& offset_map = lookup_array_map(kind); + cell_t new_cell = offset_map.mk_cell(cell_start_index, len); + inv.assign(new_cell.get_scalar(data_kind_t::svalues), svalue); + inv.assign(new_cell.get_scalar(data_kind_t::uvalues), uvalue); +} + +// Prepare to havoc bytes in the middle of a cell by potentially splitting the cell if it is numeric, +// into the part to the left of the havoced portion, and the part to the right of the havoced portion. +void array_domain_t::split_number_var(NumAbsDomain& inv, data_kind_t kind, const linear_expression_t& i, + const linear_expression_t& elem_size) { + assert(kind == data_kind_t::svalues || kind == data_kind_t::uvalues); + offset_map_t& offset_map = lookup_array_map(kind); + interval_t ii = inv.eval_interval(i); + std::optional n = ii.singleton(); + if (!n) { + // We can only split a singleton offset. + return; + } + interval_t i_elem_size = inv.eval_interval(elem_size); + std::optional n_bytes = i_elem_size.singleton(); + if (!n_bytes) { + // We can only split a singleton size. + return; + } + auto size = static_cast(*n_bytes); + offset_t o((uint64_t)*n); + + std::vector cells = offset_map.get_overlap_cells(o, size); + for (cell_t const& c : cells) { + interval_t intv = c.to_interval(); + int cell_start_index = (int)*intv.lb().number(); + int cell_end_index = (int)*intv.ub().number(); + + if (!this->num_bytes.all_num(cell_start_index, cell_end_index + 1) || (cell_end_index + 1 < cell_start_index + sizeof(int64_t))) { + // We can only split numeric cells of size 8 or less. + continue; + } + + std::optional old_number = inv.eval_interval(c.get_scalar(kind)).singleton(); + if (!old_number) { + // We can only split cells with a singleton value. + continue; + } + int64_t old_sint = old_number->cast_to_sint64(); + uint64_t old_uint = old_number->cast_to_uint64(); + + if (cell_start_index < o) { + // Use the bytes to the left of the specified range. + split_cell(inv, kind, cell_start_index, (unsigned int)(o - cell_start_index)); + } + if (o + size - 1 < cell_end_index) { + // Use the bytes to the right of the specified range. + split_cell(inv, kind, (int)(o + size), (unsigned int)(cell_end_index - (o + size - 1))); + } + } +} + // we can only treat this as non-member because we use global state static std::optional> kill_and_find_var(NumAbsDomain& inv, data_kind_t kind, const linear_expression_t& i, const linear_expression_t& elem_size) { @@ -622,11 +688,22 @@ std::optional array_domain_t::load(NumAbsDomain& inv, data_ return {}; } +// We are about to write to a given range of bytes on the stack. +// Any cells covering that range need to be removed, and any cells that only +// partially cover that range can be split such that any non-covered portions become new cells. +static std::optional> split_and_find_var(array_domain_t& array_domain, NumAbsDomain& inv, data_kind_t kind, const linear_expression_t& idx, const linear_expression_t& elem_size) { + if (kind == data_kind_t::svalues || kind == data_kind_t::uvalues) { + array_domain.split_number_var(inv, kind, idx, elem_size); + } + return kill_and_find_var(inv, kind, idx, elem_size); +} + + std::optional array_domain_t::store(NumAbsDomain& inv, data_kind_t kind, const linear_expression_t& idx, const linear_expression_t& elem_size, const linear_expression_t& val) { - auto maybe_cell = kill_and_find_var(inv, kind, idx, elem_size); + auto maybe_cell = split_and_find_var(*this, inv, kind, idx, elem_size); if (maybe_cell) { // perform strong update auto [offset, size] = *maybe_cell; @@ -648,7 +725,7 @@ std::optional array_domain_t::store_type(NumAbsDomain& inv, const linear_expression_t& elem_size, const linear_expression_t& val) { auto kind = data_kind_t::types; - auto maybe_cell = kill_and_find_var(inv, kind, idx, elem_size); + auto maybe_cell = split_and_find_var(*this, inv, kind, idx, elem_size); if (maybe_cell) { // perform strong update auto [offset, size] = *maybe_cell; @@ -671,7 +748,7 @@ std::optional array_domain_t::store_type(NumAbsDomain& inv, } void array_domain_t::havoc(NumAbsDomain& inv, data_kind_t kind, const linear_expression_t& idx, const linear_expression_t& elem_size) { - auto maybe_cell = kill_and_find_var(inv, kind, idx, elem_size); + auto maybe_cell = split_and_find_var(*this, inv, kind, idx, elem_size); if (maybe_cell && kind == data_kind_t::types) { auto [offset, size] = *maybe_cell; num_bytes.havoc(offset, size); diff --git a/src/crab/array_domain.hpp b/src/crab/array_domain.hpp index ee3808e9e..70525cb90 100644 --- a/src/crab/array_domain.hpp +++ b/src/crab/array_domain.hpp @@ -87,6 +87,10 @@ class array_domain_t final { // Perform array stores over an array segment void store_numbers(NumAbsDomain& inv, variable_t _idx, variable_t _width); + void split_number_var(NumAbsDomain& inv, data_kind_t kind, const linear_expression_t& i, + const linear_expression_t& elem_size); + void split_cell(NumAbsDomain& inv, data_kind_t kind, int cell_start_index, unsigned int len); + void initialize_numbers(int lb, int width); }; diff --git a/test-data/stack.yaml b/test-data/stack.yaml index d1c4ab198..5bdaf8da7 100644 --- a/test-data/stack.yaml +++ b/test-data/stack.yaml @@ -1,6 +1,128 @@ # Copyright (c) Prevail Verifier contributors. # SPDX-License-Identifier: MIT --- +test-case: stack load 16 bits across multiple variables + +pre: ["r10.type=stack", "r10.stack_offset=512", + "s[504...504].type=number", "s[504...504].svalue=1", "s[504...504].uvalue=1", + "s[505...505].type=number", "s[505...505].svalue=2", "s[505...505].uvalue=2"] + +code: + : | + r0 = *(u16 *)(r10 - 8) ; load 2 bytes spanning s[504...505]: 0x01 and 0x02 -> 0x0201 + +post: + - r10.type=stack + - r10.stack_offset=512 + - s[504...505].type=number + - s[504].svalue=1 + - s[504].uvalue=1 + - s[505].svalue=2 + - s[505].uvalue=2 + - r0.type=number + - r0.svalue=513 + - r0.uvalue=513 +--- +test-case: stack load 32 bits across multiple variables + +pre: ["r10.type=stack", "r10.stack_offset=512", + "s[504...505].type=number", "s[504...505].svalue=22136", "s[504...505].uvalue=22136", + "s[506...507].type=number", "s[506...507].svalue=4660", "s[506...507].uvalue=4660"] + +code: + : | + r0 = *(u32 *)(r10 - 8) ; load s[504...507]: 0x5678 and 0x1234 -> 0x12345678 on little-endian + +post: + - r10.type=stack + - r10.stack_offset=512 + - s[504...507].type=number + - s[504...505].svalue=22136 + - s[504...505].uvalue=22136 + - s[506...507].svalue=4660 + - s[506...507].uvalue=4660 + - r0.type=number + - r0.svalue=305419896 + - r0.uvalue=305419896 +--- +test-case: stack load 64 bits across multiple variables + +pre: ["r10.type=stack", "r10.stack_offset=512", + "s[504...507].type=number", "s[504...507].svalue=2596069104", "s[504...507].uvalue=2596069104", + "s[508...511].type=number", "s[508...511].svalue=305419896", "s[508...511].uvalue=305419896"] + +code: + : | + r0 = *(u64 *)(r10 - 8) ; load s[504...511]: 0x9abcdef0 and 0x12345678 -> 0x123456789abcdef0 on little-endian + +post: + - r10.type=stack + - r10.stack_offset=512 + - s[504...511].type=number + - s[504...507].svalue=2596069104 + - s[504...507].uvalue=2596069104 + - s[508...511].svalue=305419896 + - s[508...511].uvalue=305419896 + - r0.type=number + - r0.svalue=1311768467463790320 + - r0.uvalue=1311768467463790320 +--- +test-case: stack re-assign immediate first + +pre: ["r10.type=stack", "r10.stack_offset=512", + "s[504...511].type=number", "s[504...511].svalue=1311768467463790320", "s[504...511].uvalue=1311768467463790320"] + +code: + : | + *(u32 *)(r10 - 8) = 0 ; zero the first four bytes of 0x123456789abcdef0 -> 0x12345678 on little-endian machine + +post: + - r10.type=stack + - r10.stack_offset=512 + - s[504...511].type=number + - s[504...507].svalue=0 + - s[504...507].uvalue=0 + - s[508...511].svalue=305419896 + - s[508...511].uvalue=305419896 +--- +test-case: stack re-assign immediate middle + +pre: ["r10.type=stack", "r10.stack_offset=512", + "s[504...511].type=number", "s[504...511].svalue=1311768467463790320", "s[504...511].uvalue=1311768467463790320"] + +code: + : | + *(u32 *)(r10 - 6) = 0 ; zero the first four bytes of 0x123456789abcdef0 + +post: + - r10.type=stack + - r10.stack_offset=512 + - s[504...511].type=number + - s[504...505].svalue=57072 + - s[504...505].uvalue=57072 + - s[506...509].svalue=0 + - s[506...509].uvalue=0 + - s[510...511].svalue=4660 + - s[510...511].uvalue=4660 +--- +test-case: stack re-assign immediate last + +pre: ["r10.type=stack", "r10.stack_offset=512", + "s[504...511].type=number", "s[504...511].svalue=1311768467463790320", "s[504...511].uvalue=1311768467463790320"] + +code: + : | + *(u32 *)(r10 - 4) = 0 ; zero the last four bytes of 0x123456789abcdef0 + +post: + - r10.type=stack + - r10.stack_offset=512 + - s[504...511].type=number + - s[504...507].svalue=2596069104 + - s[504...507].uvalue=2596069104 + - s[508...511].svalue=0 + - s[508...511].uvalue=0 +--- test-case: set numeric size pre: ["r1.type=number", From 28ce3eabf64f5782f1f25fdd49c361544001ec91 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Thu, 11 Apr 2024 13:16:48 +0300 Subject: [PATCH 168/373] Revert "Stack memory tracking improvements" (#613) This reverts commit 934951e918ea3c72308b0908445c91c88989bd7a. ---- Signed-off-by: Elazar Gershuni --- src/crab/array_domain.cpp | 85 ++------------------------ src/crab/array_domain.hpp | 4 -- test-data/stack.yaml | 122 -------------------------------------- 3 files changed, 4 insertions(+), 207 deletions(-) diff --git a/src/crab/array_domain.cpp b/src/crab/array_domain.cpp index aedf17f20..ec5be435d 100644 --- a/src/crab/array_domain.cpp +++ b/src/crab/array_domain.cpp @@ -110,9 +110,9 @@ class cell_t final { return {number_t{static_cast(o)}, number_t{static_cast(o)} + number_t{static_cast(size - 1)}}; } - public: [[nodiscard]] interval_t to_interval() const { return to_interval(_offset, _size); } + public: [[nodiscard]] bool is_null() const { return _offset == 0 && _size == 0; } [[nodiscard]] offset_t get_offset() const { return _offset; } @@ -447,72 +447,6 @@ std::ostream& operator<<(std::ostream& o, offset_map_t& m) { return o; } -// Create a new cell that is a subset of an existing cell. -void array_domain_t::split_cell(NumAbsDomain& inv, data_kind_t kind, int cell_start_index, unsigned int len) { - assert(kind == data_kind_t::svalues || kind == data_kind_t::uvalues); - - // Get the values from the indicated stack range. - std::optional svalue = load(inv, data_kind_t::svalues, number_t(cell_start_index), len); - std::optional uvalue = load(inv, data_kind_t::uvalues, number_t(cell_start_index), len); - - // Create a new cell for that range. - offset_map_t& offset_map = lookup_array_map(kind); - cell_t new_cell = offset_map.mk_cell(cell_start_index, len); - inv.assign(new_cell.get_scalar(data_kind_t::svalues), svalue); - inv.assign(new_cell.get_scalar(data_kind_t::uvalues), uvalue); -} - -// Prepare to havoc bytes in the middle of a cell by potentially splitting the cell if it is numeric, -// into the part to the left of the havoced portion, and the part to the right of the havoced portion. -void array_domain_t::split_number_var(NumAbsDomain& inv, data_kind_t kind, const linear_expression_t& i, - const linear_expression_t& elem_size) { - assert(kind == data_kind_t::svalues || kind == data_kind_t::uvalues); - offset_map_t& offset_map = lookup_array_map(kind); - interval_t ii = inv.eval_interval(i); - std::optional n = ii.singleton(); - if (!n) { - // We can only split a singleton offset. - return; - } - interval_t i_elem_size = inv.eval_interval(elem_size); - std::optional n_bytes = i_elem_size.singleton(); - if (!n_bytes) { - // We can only split a singleton size. - return; - } - auto size = static_cast(*n_bytes); - offset_t o((uint64_t)*n); - - std::vector cells = offset_map.get_overlap_cells(o, size); - for (cell_t const& c : cells) { - interval_t intv = c.to_interval(); - int cell_start_index = (int)*intv.lb().number(); - int cell_end_index = (int)*intv.ub().number(); - - if (!this->num_bytes.all_num(cell_start_index, cell_end_index + 1) || (cell_end_index + 1 < cell_start_index + sizeof(int64_t))) { - // We can only split numeric cells of size 8 or less. - continue; - } - - std::optional old_number = inv.eval_interval(c.get_scalar(kind)).singleton(); - if (!old_number) { - // We can only split cells with a singleton value. - continue; - } - int64_t old_sint = old_number->cast_to_sint64(); - uint64_t old_uint = old_number->cast_to_uint64(); - - if (cell_start_index < o) { - // Use the bytes to the left of the specified range. - split_cell(inv, kind, cell_start_index, (unsigned int)(o - cell_start_index)); - } - if (o + size - 1 < cell_end_index) { - // Use the bytes to the right of the specified range. - split_cell(inv, kind, (int)(o + size), (unsigned int)(cell_end_index - (o + size - 1))); - } - } -} - // we can only treat this as non-member because we use global state static std::optional> kill_and_find_var(NumAbsDomain& inv, data_kind_t kind, const linear_expression_t& i, const linear_expression_t& elem_size) { @@ -688,22 +622,11 @@ std::optional array_domain_t::load(NumAbsDomain& inv, data_ return {}; } -// We are about to write to a given range of bytes on the stack. -// Any cells covering that range need to be removed, and any cells that only -// partially cover that range can be split such that any non-covered portions become new cells. -static std::optional> split_and_find_var(array_domain_t& array_domain, NumAbsDomain& inv, data_kind_t kind, const linear_expression_t& idx, const linear_expression_t& elem_size) { - if (kind == data_kind_t::svalues || kind == data_kind_t::uvalues) { - array_domain.split_number_var(inv, kind, idx, elem_size); - } - return kill_and_find_var(inv, kind, idx, elem_size); -} - - std::optional array_domain_t::store(NumAbsDomain& inv, data_kind_t kind, const linear_expression_t& idx, const linear_expression_t& elem_size, const linear_expression_t& val) { - auto maybe_cell = split_and_find_var(*this, inv, kind, idx, elem_size); + auto maybe_cell = kill_and_find_var(inv, kind, idx, elem_size); if (maybe_cell) { // perform strong update auto [offset, size] = *maybe_cell; @@ -725,7 +648,7 @@ std::optional array_domain_t::store_type(NumAbsDomain& inv, const linear_expression_t& elem_size, const linear_expression_t& val) { auto kind = data_kind_t::types; - auto maybe_cell = split_and_find_var(*this, inv, kind, idx, elem_size); + auto maybe_cell = kill_and_find_var(inv, kind, idx, elem_size); if (maybe_cell) { // perform strong update auto [offset, size] = *maybe_cell; @@ -748,7 +671,7 @@ std::optional array_domain_t::store_type(NumAbsDomain& inv, } void array_domain_t::havoc(NumAbsDomain& inv, data_kind_t kind, const linear_expression_t& idx, const linear_expression_t& elem_size) { - auto maybe_cell = split_and_find_var(*this, inv, kind, idx, elem_size); + auto maybe_cell = kill_and_find_var(inv, kind, idx, elem_size); if (maybe_cell && kind == data_kind_t::types) { auto [offset, size] = *maybe_cell; num_bytes.havoc(offset, size); diff --git a/src/crab/array_domain.hpp b/src/crab/array_domain.hpp index 70525cb90..ee3808e9e 100644 --- a/src/crab/array_domain.hpp +++ b/src/crab/array_domain.hpp @@ -87,10 +87,6 @@ class array_domain_t final { // Perform array stores over an array segment void store_numbers(NumAbsDomain& inv, variable_t _idx, variable_t _width); - void split_number_var(NumAbsDomain& inv, data_kind_t kind, const linear_expression_t& i, - const linear_expression_t& elem_size); - void split_cell(NumAbsDomain& inv, data_kind_t kind, int cell_start_index, unsigned int len); - void initialize_numbers(int lb, int width); }; diff --git a/test-data/stack.yaml b/test-data/stack.yaml index 5bdaf8da7..d1c4ab198 100644 --- a/test-data/stack.yaml +++ b/test-data/stack.yaml @@ -1,128 +1,6 @@ # Copyright (c) Prevail Verifier contributors. # SPDX-License-Identifier: MIT --- -test-case: stack load 16 bits across multiple variables - -pre: ["r10.type=stack", "r10.stack_offset=512", - "s[504...504].type=number", "s[504...504].svalue=1", "s[504...504].uvalue=1", - "s[505...505].type=number", "s[505...505].svalue=2", "s[505...505].uvalue=2"] - -code: - : | - r0 = *(u16 *)(r10 - 8) ; load 2 bytes spanning s[504...505]: 0x01 and 0x02 -> 0x0201 - -post: - - r10.type=stack - - r10.stack_offset=512 - - s[504...505].type=number - - s[504].svalue=1 - - s[504].uvalue=1 - - s[505].svalue=2 - - s[505].uvalue=2 - - r0.type=number - - r0.svalue=513 - - r0.uvalue=513 ---- -test-case: stack load 32 bits across multiple variables - -pre: ["r10.type=stack", "r10.stack_offset=512", - "s[504...505].type=number", "s[504...505].svalue=22136", "s[504...505].uvalue=22136", - "s[506...507].type=number", "s[506...507].svalue=4660", "s[506...507].uvalue=4660"] - -code: - : | - r0 = *(u32 *)(r10 - 8) ; load s[504...507]: 0x5678 and 0x1234 -> 0x12345678 on little-endian - -post: - - r10.type=stack - - r10.stack_offset=512 - - s[504...507].type=number - - s[504...505].svalue=22136 - - s[504...505].uvalue=22136 - - s[506...507].svalue=4660 - - s[506...507].uvalue=4660 - - r0.type=number - - r0.svalue=305419896 - - r0.uvalue=305419896 ---- -test-case: stack load 64 bits across multiple variables - -pre: ["r10.type=stack", "r10.stack_offset=512", - "s[504...507].type=number", "s[504...507].svalue=2596069104", "s[504...507].uvalue=2596069104", - "s[508...511].type=number", "s[508...511].svalue=305419896", "s[508...511].uvalue=305419896"] - -code: - : | - r0 = *(u64 *)(r10 - 8) ; load s[504...511]: 0x9abcdef0 and 0x12345678 -> 0x123456789abcdef0 on little-endian - -post: - - r10.type=stack - - r10.stack_offset=512 - - s[504...511].type=number - - s[504...507].svalue=2596069104 - - s[504...507].uvalue=2596069104 - - s[508...511].svalue=305419896 - - s[508...511].uvalue=305419896 - - r0.type=number - - r0.svalue=1311768467463790320 - - r0.uvalue=1311768467463790320 ---- -test-case: stack re-assign immediate first - -pre: ["r10.type=stack", "r10.stack_offset=512", - "s[504...511].type=number", "s[504...511].svalue=1311768467463790320", "s[504...511].uvalue=1311768467463790320"] - -code: - : | - *(u32 *)(r10 - 8) = 0 ; zero the first four bytes of 0x123456789abcdef0 -> 0x12345678 on little-endian machine - -post: - - r10.type=stack - - r10.stack_offset=512 - - s[504...511].type=number - - s[504...507].svalue=0 - - s[504...507].uvalue=0 - - s[508...511].svalue=305419896 - - s[508...511].uvalue=305419896 ---- -test-case: stack re-assign immediate middle - -pre: ["r10.type=stack", "r10.stack_offset=512", - "s[504...511].type=number", "s[504...511].svalue=1311768467463790320", "s[504...511].uvalue=1311768467463790320"] - -code: - : | - *(u32 *)(r10 - 6) = 0 ; zero the first four bytes of 0x123456789abcdef0 - -post: - - r10.type=stack - - r10.stack_offset=512 - - s[504...511].type=number - - s[504...505].svalue=57072 - - s[504...505].uvalue=57072 - - s[506...509].svalue=0 - - s[506...509].uvalue=0 - - s[510...511].svalue=4660 - - s[510...511].uvalue=4660 ---- -test-case: stack re-assign immediate last - -pre: ["r10.type=stack", "r10.stack_offset=512", - "s[504...511].type=number", "s[504...511].svalue=1311768467463790320", "s[504...511].uvalue=1311768467463790320"] - -code: - : | - *(u32 *)(r10 - 4) = 0 ; zero the last four bytes of 0x123456789abcdef0 - -post: - - r10.type=stack - - r10.stack_offset=512 - - s[504...511].type=number - - s[504...507].svalue=2596069104 - - s[504...507].uvalue=2596069104 - - s[508...511].svalue=0 - - s[508...511].uvalue=0 ---- test-case: set numeric size pre: ["r1.type=number", From 079b18098ae8d2d22ace536cb4365a66df1b595e Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Fri, 19 Apr 2024 12:16:34 -0700 Subject: [PATCH 169/373] Gracefully handle a negative size Prior to this, the verifier would crash due to an assert() inside the all_num() function. Signed-off-by: Dave Thaler --- src/crab/array_domain.cpp | 7 +++++++ test-data/call.yaml | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/crab/array_domain.cpp b/src/crab/array_domain.cpp index ec5be435d..1746144aa 100644 --- a/src/crab/array_domain.cpp +++ b/src/crab/array_domain.cpp @@ -493,6 +493,13 @@ bool array_domain_t::all_num(NumAbsDomain& inv, const linear_expression_t& lb, c auto max_ub = inv.eval_interval(ub).ub().number(); if (!min_lb || !max_ub || !min_lb->fits_sint32() || !max_ub->fits_sint32()) return false; + + // The all_num() call requires a legal range. If we have an illegal range, + // we should have already generated an error about the invalid range so just + // return true now to avoid an extra error about a non-numeric range. + if (*min_lb >= *max_ub) + return true; + return this->num_bytes.all_num((int32_t)*min_lb, (int32_t)*max_ub); } diff --git a/test-data/call.yaml b/test-data/call.yaml index 47beb799f..8c9eeea38 100644 --- a/test-data/call.yaml +++ b/test-data/call.yaml @@ -176,3 +176,18 @@ code: post: - r0.type=number +--- +test-case: bpf_trace_printk with negative size buffer + +pre: ["r1.type=stack", "r1.stack_offset=504", + "r2.type=number", "r2.svalue=-13", "r2.uvalue=18446744073709551603"] + +code: + : | + call 6; bpf_trace_printk + +post: + - r0.type=number + +messages: + - "0: Invalid size (r2.value > 0)" From 9796b15550c91fbfba0539493f43deecce03ed86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Apr 2024 16:48:49 +0000 Subject: [PATCH 170/373] Bump external/libbtf from `6e28ce2` to `8536d31` Bumps [external/libbtf](https://github.com/Alan-Jowett/libbtf) from `6e28ce2` to `8536d31`. - [Release notes](https://github.com/Alan-Jowett/libbtf/releases) - [Commits](https://github.com/Alan-Jowett/libbtf/compare/6e28ce2813e6a9fe7d1448298ba058531dd81adb...8536d31262e5e5c2edb78d9cf26cf74d0b073dd7) --- updated-dependencies: - dependency-name: external/libbtf dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/libbtf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/libbtf b/external/libbtf index 6e28ce281..8536d3126 160000 --- a/external/libbtf +++ b/external/libbtf @@ -1 +1 @@ -Subproject commit 6e28ce2813e6a9fe7d1448298ba058531dd81adb +Subproject commit 8536d31262e5e5c2edb78d9cf26cf74d0b073dd7 From 4944945f8ba9071ef59240aabc37f0edbeae82c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Apr 2024 16:48:46 +0000 Subject: [PATCH 171/373] Bump external/bpf_conformance from `73b6ea3` to `41ec319` Bumps [external/bpf_conformance](https://github.com/Alan-Jowett/bpf_conformance) from `73b6ea3` to `41ec319`. - [Release notes](https://github.com/Alan-Jowett/bpf_conformance/releases) - [Commits](https://github.com/Alan-Jowett/bpf_conformance/compare/73b6ea325b8b48ebdb17f2382c25ec38183154f8...41ec319242a4e598850650158dbaa91397df45a4) --- updated-dependencies: - dependency-name: external/bpf_conformance dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/bpf_conformance | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/bpf_conformance b/external/bpf_conformance index 73b6ea325..41ec31924 160000 --- a/external/bpf_conformance +++ b/external/bpf_conformance @@ -1 +1 @@ -Subproject commit 73b6ea325b8b48ebdb17f2382c25ec38183154f8 +Subproject commit 41ec319242a4e598850650158dbaa91397df45a4 From e107ff701e73bbb2a0aba267390e003a65955456 Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Mon, 22 Apr 2024 16:54:17 -0400 Subject: [PATCH 172/373] Only allow aligned loads from stack Previously, unaligned accesses were allowed in the tool, which resulted in the benchmark build/stackok.o to pass; We allow only aligned accesses, i.e., either the whole cell is loaded, or if part of the cell is to be loaded, it has to be a number, not a pointer; PREVAIL still allows misaligned accesses, hence the benchmark passes the verifier; Signed-off-by: Ameer Hamza --- src/crab/interval_domain.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/crab/interval_domain.cpp b/src/crab/interval_domain.cpp index 8ce9cddd5..139a28f5c 100644 --- a/src/crab/interval_domain.cpp +++ b/src/crab/interval_domain.cpp @@ -909,9 +909,13 @@ bool interval_domain_t::load_from_stack(register_t reg, interval_t load_at, int } } } - if (all_numeric_in_stack(start_offset, width)) { - insert_in_registers(reg, loc, interval_t::top()); - return true; + auto overlapping_cells = find_overlapping_cells_in_stack(start_offset, width); + if (overlapping_cells.size() == 1) { + // only allow loading from a single cell + if (all_numeric_in_stack(start_offset, width)) { + insert_in_registers(reg, loc, interval_t::top()); + return true; + } } return false; } From 67ffc1c263dca47687630998252fe2fe500f39ca Mon Sep 17 00:00:00 2001 From: Ameer Hamza Date: Thu, 25 Apr 2024 10:09:16 -0400 Subject: [PATCH 173/373] Adding support for Atomic operation Atomic operations are supported as a sequence of Bin and Mem operations, as atomicity of the operation is not relevant for verification; Atomic operation requires a pseudo-register (r11), such that Bin and Mem operations update r11, if needed; Signed-off-by: Ameer Hamza --- src/crab/common.hpp | 4 +- src/crab/offset_domain.cpp | 18 +++---- src/crab/offset_domain.hpp | 6 +-- src/crab/region_domain.cpp | 6 +-- src/crab/signed_interval_domain.cpp | 4 +- src/crab/type_domain.cpp | 68 ++++++++++++++++++++++++++- src/crab/unsigned_interval_domain.cpp | 4 +- 7 files changed, 89 insertions(+), 21 deletions(-) diff --git a/src/crab/common.hpp b/src/crab/common.hpp index 1dfbf1148..30f0ca43e 100644 --- a/src/crab/common.hpp +++ b/src/crab/common.hpp @@ -16,7 +16,9 @@ namespace crab { using check_require_func_t = std::function; -constexpr uint8_t NUM_REGISTERS = 11; + +// 11 registers for the eBPF ISA, and 1 pseudo-register for the atomic operations +constexpr uint8_t NUM_REGISTERS = 12; constexpr int STACK_BEGIN = 0; constexpr int CTX_BEGIN = 0; diff --git a/src/crab/offset_domain.cpp b/src/crab/offset_domain.cpp index b1ebaf2d1..46ae20180 100644 --- a/src/crab/offset_domain.cpp +++ b/src/crab/offset_domain.cpp @@ -94,7 +94,7 @@ registers_state_t registers_state_t::operator|(const registers_state_t& other) c registers_state_t joined_state(m_offset_env, m_slacks); location_t loc = location_t(std::make_pair(label_t(-2, -2), 0)); - for (uint8_t i = 0; i < NUM_REGISTERS; i++) { + for (uint8_t i = 0; i < NUM_REGISTERS-1; i++) { if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; auto it1 = find(*(m_cur_def[i])); auto it2 = other.find(*(other.m_cur_def[i])); @@ -171,14 +171,14 @@ void registers_state_t::scratch_caller_saved_registers() { } void registers_state_t::forget_packet_pointers(location_t loc) { - for (uint8_t r = R0_RETURN_VALUE; r < NUM_REGISTERS; r++) { + for (uint8_t r = R0_RETURN_VALUE; r < NUM_REGISTERS-1; r++) { if (auto it = find(register_t{r})) { if (it->get_type() == data_type_t::PACKET) { operator-=(register_t{r}); } } } - insert(register_t{11}, loc, refinement_t::begin()); + insert(register_t{12}, loc, refinement_t::begin()); // TODO: verify if this is all needed } @@ -412,7 +412,7 @@ void offset_domain_t::operator()(const Assume &b, location_t loc) { return; } if (cond.op == Condition::Op::LE) { - auto b = m_reg_state.find(register_t{11}); + auto b = m_reg_state.find(register_t{12}); auto le_rf = *rf_left <= *rf_right; if (b) { b->add_constraint(le_rf); @@ -428,12 +428,12 @@ void offset_domain_t::operator()(const Assume &b, location_t loc) { set_to_bottom(); } else { - m_reg_state.insert(register_t{11}, loc, std::move(*b)); + m_reg_state.insert(register_t{12}, loc, std::move(*b)); } } } else if (cond.op == Condition::Op::GT) { - auto b = m_reg_state.find(register_t{11}); + auto b = m_reg_state.find(register_t{12}); auto gt_rf = *rf_left > *rf_right; if (b) { b->add_constraint(gt_rf); @@ -443,7 +443,7 @@ void offset_domain_t::operator()(const Assume &b, location_t loc) { set_to_bottom(); } else { - m_reg_state.insert(register_t{11}, loc, std::move(*b)); + m_reg_state.insert(register_t{12}, loc, std::move(*b)); } } @@ -468,7 +468,7 @@ interval_t offset_domain_t::compute_packet_subtraction(register_t dst, register_ if (!dst_expr.is_singleton() || !src_expr.is_singleton()) return interval_t::top(); auto dst_symbol = dst_expr.get_singleton(); auto src_symbol = src_expr.get_singleton(); - std::optional begin_rf = m_reg_state.find(register_t{11}); + std::optional begin_rf = m_reg_state.find(register_t{12}); if (!begin_rf) return interval_t::top(); interval_t result_interval = result_rf.simplify_for_subtraction(dst_symbol, src_symbol, begin_rf->get_constraints(), m_reg_state.get_slacks()); @@ -693,7 +693,7 @@ void offset_domain_t::operator()(const Packet& u, location_t loc) { bool offset_domain_t::check_packet_access(const Reg& r, int width, int offset, bool is_comparison_check) const { - auto begin = m_reg_state.find(register_t{11}); + auto begin = m_reg_state.find(register_t{12}); if (!begin) return false; auto reg = m_reg_state.find(r.v); if (!reg) return false; diff --git a/src/crab/offset_domain.hpp b/src/crab/offset_domain.hpp index 409fa852a..f9b2d1793 100644 --- a/src/crab/offset_domain.hpp +++ b/src/crab/offset_domain.hpp @@ -30,13 +30,13 @@ class registers_state_t { auto loc = std::make_pair(label_t::entry, (unsigned int)0); if (desc->data >= 0) { - insert(register_t{11}, loc, refinement_t::begin()); + insert(register_t{12}, loc, refinement_t::begin()); } if (desc->end >= 0) { - insert(register_t{12}, loc, refinement_t::end()); + insert(register_t{13}, loc, refinement_t::end()); } if (desc->meta >= 0) { - insert(register_t{13}, loc, refinement_t::meta()); + insert(register_t{14}, loc, refinement_t::meta()); } } diff --git a/src/crab/region_domain.cpp b/src/crab/region_domain.cpp index 63eb6c7c4..afcb956d3 100644 --- a/src/crab/region_domain.cpp +++ b/src/crab/region_domain.cpp @@ -87,7 +87,7 @@ void register_types_t::scratch_caller_saved_registers() { } void register_types_t::forget_packet_ptrs() { - for (uint8_t r = R0_RETURN_VALUE; r < NUM_REGISTERS; r++) { + for (uint8_t r = R0_RETURN_VALUE; r < NUM_REGISTERS-1; r++) { if (is_packet_ptr(find(register_t{r}))) { operator-=(register_t{r}); } @@ -108,7 +108,7 @@ register_types_t register_types_t::operator|(const register_types_t& other) cons // the bb label, we can fix location_t loc = location_t{std::make_pair(label_t(-2, -2), 0)}; - for (uint8_t i = 0; i < NUM_REGISTERS; i++) { + for (uint8_t i = 0; i < NUM_REGISTERS-1; i++) { if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; auto maybe_ptr1 = find(register_t{i}); auto maybe_ptr2 = other.find(register_t{i}); @@ -184,7 +184,7 @@ std::optional register_types_t::find(register_t key) const { } void register_types_t::adjust_bb_for_registers(location_t loc) { - for (uint8_t i = 0; i < NUM_REGISTERS; i++) { + for (uint8_t i = 0; i < NUM_REGISTERS-1; i++) { if (auto it = find(register_t{i})) { insert(register_t{i}, loc, *it); } diff --git a/src/crab/signed_interval_domain.cpp b/src/crab/signed_interval_domain.cpp index 251af1e76..493be2ffe 100644 --- a/src/crab/signed_interval_domain.cpp +++ b/src/crab/signed_interval_domain.cpp @@ -55,7 +55,7 @@ registers_signed_state_t registers_signed_state_t::operator|(const registers_sig } registers_signed_state_t intervals_joined(m_interval_env); location_t loc = location_t(std::make_pair(label_t(-2, -2), 0)); - for (uint8_t i = 0; i < NUM_REGISTERS; i++) { + for (uint8_t i = 0; i < NUM_REGISTERS-1; i++) { if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; auto it1 = find(*(m_cur_def[i])); auto it2 = other.find(*(other.m_cur_def[i])); @@ -69,7 +69,7 @@ registers_signed_state_t registers_signed_state_t::operator|(const registers_sig } void registers_signed_state_t::adjust_bb_for_registers(location_t loc) { - for (uint8_t i = 0; i < NUM_REGISTERS; i++) { + for (uint8_t i = 0; i < NUM_REGISTERS-1; i++) { if (auto it = find(register_t{i})) { insert(register_t{i}, loc, it->to_interval()); } diff --git a/src/crab/type_domain.cpp b/src/crab/type_domain.cpp index 224d32641..28688933c 100644 --- a/src/crab/type_domain.cpp +++ b/src/crab/type_domain.cpp @@ -105,7 +105,7 @@ crab::bound_t type_domain_t::get_loop_count_upper_bound() { string_invariant type_domain_t::to_set() const { if (is_top()) return string_invariant::top(); std::set result; - for (uint8_t i = 0; i < NUM_REGISTERS; i++) { + for (uint8_t i = 0; i < NUM_REGISTERS-1; i++) { auto maybe_ptr_or_mapfd = m_region.find_ptr_or_mapfd_type(register_t{i}); auto maybe_rf = m_offset.find_refinement_info(register_t{i}); if (maybe_ptr_or_mapfd.has_value()) { @@ -193,8 +193,74 @@ void type_domain_t::operator()(const LoadMapFd& u, location_t loc) { m_interval(u, loc); } +// Construct a Bin operation that does the main operation that a given Atomic operation does atomically. +static Bin atomic_to_bin(const Atomic& a) { + Bin bin{ + .dst = Reg{R11_ATOMIC_SCRATCH}, .v = a.valreg, .is64 = (a.access.width == sizeof(uint64_t)), .lddw = false}; + switch (a.op) { + case Atomic::Op::ADD: bin.op = Bin::Op::ADD; break; + case Atomic::Op::OR: bin.op = Bin::Op::OR; break; + case Atomic::Op::AND: bin.op = Bin::Op::AND; break; + case Atomic::Op::XOR: bin.op = Bin::Op::XOR; break; + case Atomic::Op::XCHG: + case Atomic::Op::CMPXCHG: bin.op = Bin::Op::MOV; break; + default: throw std::exception(); + } + return bin; +} + void type_domain_t::operator()(const Atomic &u, location_t loc) { // WARNING: Not implemented yet + if (is_bottom()) return; + std::optional base_reg_opt + = m_region.find_ptr_or_mapfd_type(u.access.basereg.v); + std::optional value_reg_opt = m_interval.find_interval_value(u.valreg.v); + if (!base_reg_opt || !value_reg_opt) return; + if (is_stack_ptr(base_reg_opt)) { + if (u.op == Atomic::Op::CMPXCHG) { + m_region -= register_t{R0_RETURN_VALUE}; + m_offset -= register_t{R0_RETURN_VALUE}; + insert_in_registers_in_interval_domain(register_t{R0_RETURN_VALUE}, + loc, interval_t::top()); + } + else if (u.fetch) { + insert_in_registers_in_interval_domain(u.valreg.v, loc, interval_t::top()); + } + return; + } + // Fetch the current value into the R11 pseudo-register. + const Reg r11{11}; + (*this)(Mem{.access = u.access, .value = r11, .is_load = true}); + + // Compute the new value in R11. + (*this)(atomic_to_bin(u)); + + if (u.op == Atomic::Op::CMPXCHG) { + // For CMPXCHG, store the original value in r0. + (*this)(Mem{.access = u.access, .value = Reg{R0_RETURN_VALUE}, .is_load = true}); + + // For the destination, there are 3 possibilities: + // 1) dst.value == r0.value : set R11 to valreg + // 2) dst.value != r0.value : don't modify R11 + // 3) dst.value may or may not == r0.value : set R11 to the union of R11 and valreg + // For now we just havoc the value of R11. + m_region -= register_t{11}; + m_offset -= register_t{11}; + insert_in_registers_in_interval_domain(register_t{11}, loc, interval_t::top()); + } else if (u.fetch) { + // For other FETCH operations, store the original value in the src register. + (*this)(Mem{.access = u.access, .value = u.valreg, .is_load = true}); + } + + // Store the new value back in the original shared memory location. + // Note that do_mem_store() currently doesn't track shared memory values, + // but stack memory values are tracked and are legal here. + (*this)(Mem{.access = u.access, .value = r11, .is_load = false}); + + // Clear the R11 pseudo-register. + m_region -= register_t{11}; + m_offset -= register_t{11}; + m_interval -= register_t{11}; } void type_domain_t::operator()(const IncrementLoopCounter &u, location_t loc) { diff --git a/src/crab/unsigned_interval_domain.cpp b/src/crab/unsigned_interval_domain.cpp index 0dfa6c359..ac97663ed 100644 --- a/src/crab/unsigned_interval_domain.cpp +++ b/src/crab/unsigned_interval_domain.cpp @@ -55,7 +55,7 @@ registers_unsigned_state_t registers_unsigned_state_t::operator|(const registers } registers_unsigned_state_t intervals_joined(m_interval_env); location_t loc = location_t(std::make_pair(label_t(-2, -2), 0)); - for (uint8_t i = 0; i < NUM_REGISTERS; i++) { + for (uint8_t i = 0; i < NUM_REGISTERS-1; i++) { if (m_cur_def[i] == nullptr || other.m_cur_def[i] == nullptr) continue; auto it1 = find(*(m_cur_def[i])); auto it2 = other.find(*(other.m_cur_def[i])); @@ -69,7 +69,7 @@ registers_unsigned_state_t registers_unsigned_state_t::operator|(const registers } void registers_unsigned_state_t::adjust_bb_for_registers(location_t loc) { - for (uint8_t i = 0; i < NUM_REGISTERS; i++) { + for (uint8_t i = 0; i < NUM_REGISTERS-1; i++) { if (auto it = find(register_t{i})) { insert(register_t{i}, loc, it->to_interval()); } From 9b37971dacce87f1abef591c3727db15e632d109 Mon Sep 17 00:00:00 2001 From: Alan Jowett Date: Thu, 2 May 2024 08:19:28 -0700 Subject: [PATCH 174/373] Set the /fsanitize=fuzzer for the compiler Signed-off-by: Alan Jowett --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b0f1a6ac..40f43ef5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,8 +30,8 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") FORCE) set(CMAKE_EXE_LINKER_FLAGS_FUZZERDEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG}") set(CMAKE_SHARED_LINKER_FLAGS_FUZZERDEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG}") - set(CMAKE_C_FLAGS_FUZZERDEBUG "${CMAKE_C_FLAGS_DEBUG} /fsanitize-coverage=inline-bool-flag /fsanitize-coverage=edge /fsanitize-coverage=trace-cmp /fsanitize-coverage=trace-div /ZH:SHA_256") - set(CMAKE_CXX_FLAGS_FUZZERDEBUG "${CMAKE_CXX_FLAGS_DEBUG} /fsanitize-coverage=inline-bool-flag /fsanitize-coverage=edge /fsanitize-coverage=trace-cmp /fsanitize-coverage=trace-div /ZH:SHA_256") + set(CMAKE_C_FLAGS_FUZZERDEBUG "${CMAKE_C_FLAGS_DEBUG} /fsanitize=address /fsanitize=fuzzer /fsanitize-coverage=inline-bool-flag /fsanitize-coverage=edge /fsanitize-coverage=trace-cmp /fsanitize-coverage=trace-div /ZH:SHA_256") + set(CMAKE_CXX_FLAGS_FUZZERDEBUG "${CMAKE_CXX_FLAGS_DEBUG} /fsanitize=address /fsanitize=fuzzer /fsanitize-coverage=inline-bool-flag /fsanitize-coverage=edge /fsanitize-coverage=trace-cmp /fsanitize-coverage=trace-div /ZH:SHA_256") find_program(NUGET nuget) if (NOT NUGET) From b8ac6af135297d426c71165eab11774f454998ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 27 Apr 2024 16:56:10 +0000 Subject: [PATCH 175/373] Bump external/libbtf from `8536d31` to `ada4e96` Bumps [external/libbtf](https://github.com/Alan-Jowett/libbtf) from `8536d31` to `ada4e96`. - [Release notes](https://github.com/Alan-Jowett/libbtf/releases) - [Commits](https://github.com/Alan-Jowett/libbtf/compare/8536d31262e5e5c2edb78d9cf26cf74d0b073dd7...ada4e964fb3a8331968ca728d65be5766ccba5e6) --- updated-dependencies: - dependency-name: external/libbtf dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/libbtf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/libbtf b/external/libbtf index 8536d3126..ada4e964f 160000 --- a/external/libbtf +++ b/external/libbtf @@ -1 +1 @@ -Subproject commit 8536d31262e5e5c2edb78d9cf26cf74d0b073dd7 +Subproject commit ada4e964fb3a8331968ca728d65be5766ccba5e6 From 366b208067e487de267c05fab73e6bd4253bcb9e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 4 May 2024 16:41:29 +0000 Subject: [PATCH 176/373] Bump external/libbtf from `ada4e96` to `5ae35c7` Bumps [external/libbtf](https://github.com/Alan-Jowett/libbtf) from `ada4e96` to `5ae35c7`. - [Release notes](https://github.com/Alan-Jowett/libbtf/releases) - [Commits](https://github.com/Alan-Jowett/libbtf/compare/ada4e964fb3a8331968ca728d65be5766ccba5e6...5ae35c7f3495d2aea9f3dad911acc2505acc2747) --- updated-dependencies: - dependency-name: external/libbtf dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/libbtf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/libbtf b/external/libbtf index ada4e964f..5ae35c7f3 160000 --- a/external/libbtf +++ b/external/libbtf @@ -1 +1 @@ -Subproject commit ada4e964fb3a8331968ca728d65be5766ccba5e6 +Subproject commit 5ae35c7f3495d2aea9f3dad911acc2505acc2747 From edf88e2bed6d281657a032dc38fa6e4983ab0478 Mon Sep 17 00:00:00 2001 From: Alan Jowett Date: Tue, 7 May 2024 11:58:19 -0700 Subject: [PATCH 177/373] Pickup latest bpf_conformance (#629) * Pickup latest bpf_conformance * Add test for jump with signed immediate value --------- Signed-off-by: Alan Jowett --- external/bpf_conformance | 2 +- src/test/test_conformance.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/external/bpf_conformance b/external/bpf_conformance index 41ec31924..35201b4f0 160000 --- a/external/bpf_conformance +++ b/external/bpf_conformance @@ -1 +1 @@ -Subproject commit 41ec319242a4e598850650158dbaa91397df45a4 +Subproject commit 35201b4f065683f26d1b4ddab130788135d09b33 diff --git a/src/test/test_conformance.cpp b/src/test/test_conformance.cpp index b1f2dce57..5faca7769 100644 --- a/src/test/test_conformance.cpp +++ b/src/test/test_conformance.cpp @@ -90,6 +90,7 @@ TEST_CONFORMANCE("div64-negative-reg.data") TEST_CONFORMANCE("div64-reg.data") TEST_CONFORMANCE("exit-not-last.data") TEST_CONFORMANCE("exit.data") +TEST_CONFORMANCE("j-signed-imm.data") TEST_CONFORMANCE("ja32.data") TEST_CONFORMANCE("jeq-imm.data") TEST_CONFORMANCE("jeq-reg.data") From e382b39b42bba468145a037fd741c8b8cacb490f Mon Sep 17 00:00:00 2001 From: Alan Jowett Date: Thu, 9 May 2024 12:29:58 -0700 Subject: [PATCH 178/373] Add checks for malformed EbpfHelperPrototype Signed-off-by: Alan Jowett --- src/asm_unmarshal.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/asm_unmarshal.cpp b/src/asm_unmarshal.cpp index d331b40d0..f906db2f7 100644 --- a/src/asm_unmarshal.cpp +++ b/src/asm_unmarshal.cpp @@ -464,11 +464,24 @@ struct Unmarshaller { case EBPF_ARGUMENT_TYPE_PTR_TO_CTX: res.singles.push_back({toArgSingleKind(args[i]), Reg{(uint8_t)i}}); break; - case EBPF_ARGUMENT_TYPE_CONST_SIZE: assert(false); continue; - case EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: assert(false); continue; + case EBPF_ARGUMENT_TYPE_CONST_SIZE: { + // Sanity check: This argument should never be seen in isolation. + throw std::runtime_error(std::string("mismatched EBPF_ARGUMENT_TYPE_PTR_TO* and EBPF_ARGUMENT_TYPE_CONST_SIZE: ") + proto.name); + } + case EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO:{ + // Sanity check: This argument should never be seen in isolation. + throw std::runtime_error(std::string("mismatched EBPF_ARGUMENT_TYPE_PTR_TO* and EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: ") + proto.name); + } case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM_OR_NULL: case EBPF_ARGUMENT_TYPE_PTR_TO_READABLE_MEM: case EBPF_ARGUMENT_TYPE_PTR_TO_WRITABLE_MEM: + // Sanity check: This argument must be followed by EBPF_ARGUMENT_TYPE_CONST_SIZE or EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO. + if (args.size() - i < 2) { + throw std::runtime_error(std::string("missing EBPF_ARGUMENT_TYPE_CONST_SIZE or EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: ") + proto.name); + } + if (args[i + 1] != EBPF_ARGUMENT_TYPE_CONST_SIZE && args[i + 1] != EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO) { + throw std::runtime_error(std::string("Pointer argument not followed by EBPF_ARGUMENT_TYPE_CONST_SIZE or EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO: ") + proto.name); + } bool can_be_zero = (args[i + 1] == EBPF_ARGUMENT_TYPE_CONST_SIZE_OR_ZERO); res.pairs.push_back({toArgPairKind(args[i]), Reg{(uint8_t)i}, Reg{(uint8_t)(i + 1)}, can_be_zero}); i++; From 69d15382a31915c85259a44e438482216e5ae750 Mon Sep 17 00:00:00 2001 From: Alan Jowett Date: Thu, 9 May 2024 16:02:13 -0700 Subject: [PATCH 179/373] Add test for issue620 (#621) Signed-off-by: Alan Jowett --- ebpf-samples | 2 +- src/test/test_verify.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ebpf-samples b/ebpf-samples index 5d6555274..038fa4770 160000 --- a/ebpf-samples +++ b/ebpf-samples @@ -1 +1 @@ -Subproject commit 5d6555274119147c6ea69866712db3e4fa964423 +Subproject commit 038fa47700bf270624b5fc059dea4c17e91a20c6 diff --git a/src/test/test_verify.cpp b/src/test/test_verify.cpp index c5bf67fb5..817744710 100644 --- a/src/test/test_verify.cpp +++ b/src/test/test_verify.cpp @@ -502,6 +502,10 @@ TEST_SECTION_REJECT("build", "ringbuf_uninit.o", ".text"); // If the verifier is later updated to accept them, these should // be changed to TEST_SECTION(). +// Issue: https://github.com/vbpf/ebpf-verifier/issues/620 +// Inserting value from map 1 into map 2 should be supported, but fails. +TEST_SECTION_FAIL("build", "store_map_value_in_map.o", ".text") + // Unsupported: ebpf-function TEST_SECTION_FAIL("prototype-kernel", "xdp_ddos01_blacklist_kern.o", ".text") From 42e9991232a5d9ef7fee79d55130c4bec28919d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Dantas?= Date: Mon, 18 Mar 2024 10:27:42 -0400 Subject: [PATCH 180/373] Add git and ca-certificates to the docker image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vinícius Dantas --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f412529d9..618d605e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,8 @@ FROM ubuntu:20.04 ENV DEBIAN_FRONTEND=noninteractive RUN apt update RUN apt -yq --no-install-suggests --no-install-recommends install build-essential cmake \ - libboost-dev libboost-filesystem-dev libboost-program-options-dev libyaml-cpp-dev + libboost-dev libboost-filesystem-dev libboost-program-options-dev libyaml-cpp-dev git \ + ca-certificates WORKDIR /verifier COPY . /verifier/ RUN mkdir build From 64ab8c034eb2fa563bbf4c6d181a482259b9f8eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Dantas?= Date: Thu, 9 May 2024 19:04:37 -0400 Subject: [PATCH 181/373] Add MacOS instructions to the README file (#606) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add MacOS instructions * Move env vars to an explicit assignment before calling cmake --------- Signed-off-by: Vinícius Dantas --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 372dc1ba8..89d5d6561 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,12 @@ sudo apt install libboost-filesystem-dev libboost-program-options-dev * Install [Visual Studio Build Tools 2022](https://aka.ms/vs/17/release/vs_buildtools.exe) and choose the "C++ build tools" workload (Visual Studio Build Tools 2022 has support for CMake Version 3.25). * Install [nuget.exe](https://www.nuget.org/downloads) +### Dependencies from MacOS +```bash +brew install llvm cmake boost yaml-cpp +``` +The system llvm currently comes with Clang 15, which isn't enough to compile the ebpf-verifier as it depends on C++20. Brew's llvm comes with Clang 17. + ### Installation Clone: ``` @@ -40,6 +46,13 @@ cmake -B build cmake --build build --config Release ``` +Make on MacOS: +``` +export CPATH=$(brew --prefix)/include LIBRARY_PATH=$(brew --prefix)/lib CMAKE_PREFIX_PATH=$(brew --prefix) +cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=$(brew --prefix llvm)/bin/clang -DCMAKE_CXX_COMPILER=$(brew --prefix llvm)/bin/clang++ +cmake --build build +``` + ### Running with Docker Build and run: ```bash From 917fd17099299f2f0eb0fcc5c2d94c8ca4980066 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 11 May 2024 16:14:57 +0000 Subject: [PATCH 182/373] Bump external/bpf_conformance from `35201b4` to `7c2ef8b` Bumps [external/bpf_conformance](https://github.com/Alan-Jowett/bpf_conformance) from `35201b4` to `7c2ef8b`. - [Release notes](https://github.com/Alan-Jowett/bpf_conformance/releases) - [Commits](https://github.com/Alan-Jowett/bpf_conformance/compare/35201b4f065683f26d1b4ddab130788135d09b33...7c2ef8b77679089f3949cf48137ec2246358fb3f) --- updated-dependencies: - dependency-name: external/bpf_conformance dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/bpf_conformance | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/bpf_conformance b/external/bpf_conformance index 35201b4f0..7c2ef8b77 160000 --- a/external/bpf_conformance +++ b/external/bpf_conformance @@ -1 +1 @@ -Subproject commit 35201b4f065683f26d1b4ddab130788135d09b33 +Subproject commit 7c2ef8b77679089f3949cf48137ec2246358fb3f From ef17f7a0fa03748de5d0b0924a2e590083176804 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 11 May 2024 16:14:55 +0000 Subject: [PATCH 183/373] Bump ebpf-samples from `038fa47` to `5d65552` Bumps [ebpf-samples](https://github.com/vbpf/ebpf-samples) from `038fa47` to `5d65552`. - [Release notes](https://github.com/vbpf/ebpf-samples/releases) - [Commits](https://github.com/vbpf/ebpf-samples/compare/038fa47700bf270624b5fc059dea4c17e91a20c6...5d6555274119147c6ea69866712db3e4fa964423) --- updated-dependencies: - dependency-name: ebpf-samples dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- ebpf-samples | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebpf-samples b/ebpf-samples index 038fa4770..5d6555274 160000 --- a/ebpf-samples +++ b/ebpf-samples @@ -1 +1 @@ -Subproject commit 038fa47700bf270624b5fc059dea4c17e91a20c6 +Subproject commit 5d6555274119147c6ea69866712db3e4fa964423 From 78a6c824bfcf7683f1fcea307ae6a6bfc64a51d1 Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Sun, 12 May 2024 10:13:23 -0700 Subject: [PATCH 184/373] Stack memory tracking improvements (#614) * Stack memory tracking improvements, fixes #566 * Expand byteswap tests to test big and little endian platforms * Update conformance test expected results to be more precise --------- Signed-off-by: Dave Thaler --- src/config.cpp | 1 + src/config.hpp | 1 + src/crab/array_domain.cpp | 118 ++++++++++++- src/crab/array_domain.hpp | 4 + src/crab/ebpf_domain.cpp | 56 ++++-- src/ebpf_yaml.cpp | 8 + src/test/test_conformance.cpp | 16 +- test-data/stack.yaml | 316 ++++++++++++++++++++++++++++++++++ test-data/unop.yaml | 196 +++++++++++++++++++-- 9 files changed, 677 insertions(+), 39 deletions(-) diff --git a/src/config.cpp b/src/config.cpp index 3849162c5..00ccab43b 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -13,4 +13,5 @@ const ebpf_verifier_options_t ebpf_verifier_default_options = { .print_line_info = false, .allow_division_by_zero = true, .setup_constraints = true, + .big_endian = false, }; diff --git a/src/config.hpp b/src/config.hpp index cf3065fd9..93d787f3b 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -18,6 +18,7 @@ struct ebpf_verifier_options_t { bool print_line_info; bool allow_division_by_zero; bool setup_constraints; + bool big_endian; bool dump_btf_types_json; }; diff --git a/src/crab/array_domain.cpp b/src/crab/array_domain.cpp index 1746144aa..9a51ba668 100644 --- a/src/crab/array_domain.cpp +++ b/src/crab/array_domain.cpp @@ -8,10 +8,13 @@ #include #include +#include "boost/endian/conversion.hpp" + #include "radix_tree/radix_tree.hpp" #include "crab/array_domain.hpp" #include "asm_ostream.hpp" +#include "config.hpp" #include "dsl_syntax.hpp" #include "spec_type_descriptors.hpp" @@ -110,9 +113,9 @@ class cell_t final { return {number_t{static_cast(o)}, number_t{static_cast(o)} + number_t{static_cast(size - 1)}}; } + public: [[nodiscard]] interval_t to_interval() const { return to_interval(_offset, _size); } - public: [[nodiscard]] bool is_null() const { return _offset == 0 && _size == 0; } [[nodiscard]] offset_t get_offset() const { return _offset; } @@ -447,6 +450,68 @@ std::ostream& operator<<(std::ostream& o, offset_map_t& m) { return o; } +// Create a new cell that is a subset of an existing cell. +void array_domain_t::split_cell(NumAbsDomain& inv, data_kind_t kind, int cell_start_index, unsigned int len) { + assert(kind == data_kind_t::svalues || kind == data_kind_t::uvalues); + + // Get the values from the indicated stack range. + std::optional svalue = load(inv, data_kind_t::svalues, number_t(cell_start_index), len); + std::optional uvalue = load(inv, data_kind_t::uvalues, number_t(cell_start_index), len); + + // Create a new cell for that range. + offset_map_t& offset_map = lookup_array_map(kind); + cell_t new_cell = offset_map.mk_cell(cell_start_index, len); + inv.assign(new_cell.get_scalar(data_kind_t::svalues), svalue); + inv.assign(new_cell.get_scalar(data_kind_t::uvalues), uvalue); +} + +// Prepare to havoc bytes in the middle of a cell by potentially splitting the cell if it is numeric, +// into the part to the left of the havoced portion, and the part to the right of the havoced portion. +void array_domain_t::split_number_var(NumAbsDomain& inv, data_kind_t kind, const linear_expression_t& i, + const linear_expression_t& elem_size) { + assert(kind == data_kind_t::svalues || kind == data_kind_t::uvalues); + offset_map_t& offset_map = lookup_array_map(kind); + interval_t ii = inv.eval_interval(i); + std::optional n = ii.singleton(); + if (!n) { + // We can only split a singleton offset. + return; + } + interval_t i_elem_size = inv.eval_interval(elem_size); + std::optional n_bytes = i_elem_size.singleton(); + if (!n_bytes) { + // We can only split a singleton size. + return; + } + auto size = static_cast(*n_bytes); + offset_t o((uint64_t)*n); + + std::vector cells = offset_map.get_overlap_cells(o, size); + for (cell_t const& c : cells) { + interval_t intv = c.to_interval(); + int cell_start_index = (int)*intv.lb().number(); + int cell_end_index = (int)*intv.ub().number(); + + if (!this->num_bytes.all_num(cell_start_index, cell_end_index + 1) || (cell_end_index + 1 < cell_start_index + sizeof(int64_t))) { + // We can only split numeric cells of size 8 or less. + continue; + } + + if (!inv.eval_interval(c.get_scalar(kind)).is_singleton()) { + // We can only split cells with a singleton value. + continue; + } + if (cell_start_index < o) { + // Use the bytes to the left of the specified range. + split_cell(inv, kind, cell_start_index, (unsigned int)(o - cell_start_index)); + } + if (o + size - 1 < cell_end_index) { + // Use the bytes to the right of the specified range. + split_cell(inv, kind, (int)(o + size), (unsigned int)(cell_end_index - (o + size - 1))); + } + } +} + // we can only treat this as non-member because we use global state static std::optional> kill_and_find_var(NumAbsDomain& inv, data_kind_t kind, const linear_expression_t& i, const linear_expression_t& elem_size) { @@ -522,6 +587,28 @@ std::optional get_value_byte(NumAbsDomain& inv, offset_t o, int width) return {}; } uint64_t n = t->cast_to_uint64(); + + // Convert value to bytes of the appropriate endian-ness. + switch (width) { + case sizeof(uint16_t): + if (thread_local_options.big_endian) + n = boost::endian::native_to_big(n); + else + n = boost::endian::native_to_little(n); + break; + case sizeof(uint32_t): + if (thread_local_options.big_endian) + n = boost::endian::native_to_big(n); + else + n = boost::endian::native_to_little(n); + break; + case sizeof(uint64_t): + if (thread_local_options.big_endian) + n = boost::endian::native_to_big(n); + else + n = boost::endian::native_to_little(n); + break; + } uint8_t* bytes = (uint8_t*)&n; return bytes[o % width]; } @@ -578,14 +665,26 @@ std::optional array_domain_t::load(NumAbsDomain& inv, data_ } if (size == 2) { uint16_t b = *(uint16_t*)result_buffer; + if (thread_local_options.big_endian) + b = boost::endian::native_to_big(b); + else + b = boost::endian::native_to_little(b); return number_t{b}; } if (size == 4) { uint32_t b = *(uint32_t*)result_buffer; + if (thread_local_options.big_endian) + b = boost::endian::native_to_big(b); + else + b = boost::endian::native_to_little(b); return number_t{b}; } if (size == 8) { uint64_t b = *(uint64_t*)result_buffer; + if (thread_local_options.big_endian) + b = boost::endian::native_to_big(b); + else + b = boost::endian::native_to_little(b); return (kind == data_kind_t::uvalues) ? number_t(b) : number_t((int64_t)b); } } @@ -629,11 +728,22 @@ std::optional array_domain_t::load(NumAbsDomain& inv, data_ return {}; } +// We are about to write to a given range of bytes on the stack. +// Any cells covering that range need to be removed, and any cells that only +// partially cover that range can be split such that any non-covered portions become new cells. +static std::optional> split_and_find_var(array_domain_t& array_domain, NumAbsDomain& inv, data_kind_t kind, const linear_expression_t& idx, const linear_expression_t& elem_size) { + if (kind == data_kind_t::svalues || kind == data_kind_t::uvalues) { + array_domain.split_number_var(inv, kind, idx, elem_size); + } + return kill_and_find_var(inv, kind, idx, elem_size); +} + + std::optional array_domain_t::store(NumAbsDomain& inv, data_kind_t kind, const linear_expression_t& idx, const linear_expression_t& elem_size, const linear_expression_t& val) { - auto maybe_cell = kill_and_find_var(inv, kind, idx, elem_size); + auto maybe_cell = split_and_find_var(*this, inv, kind, idx, elem_size); if (maybe_cell) { // perform strong update auto [offset, size] = *maybe_cell; @@ -655,7 +765,7 @@ std::optional array_domain_t::store_type(NumAbsDomain& inv, const linear_expression_t& elem_size, const linear_expression_t& val) { auto kind = data_kind_t::types; - auto maybe_cell = kill_and_find_var(inv, kind, idx, elem_size); + auto maybe_cell = split_and_find_var(*this, inv, kind, idx, elem_size); if (maybe_cell) { // perform strong update auto [offset, size] = *maybe_cell; @@ -678,7 +788,7 @@ std::optional array_domain_t::store_type(NumAbsDomain& inv, } void array_domain_t::havoc(NumAbsDomain& inv, data_kind_t kind, const linear_expression_t& idx, const linear_expression_t& elem_size) { - auto maybe_cell = kill_and_find_var(inv, kind, idx, elem_size); + auto maybe_cell = split_and_find_var(*this, inv, kind, idx, elem_size); if (maybe_cell && kind == data_kind_t::types) { auto [offset, size] = *maybe_cell; num_bytes.havoc(offset, size); diff --git a/src/crab/array_domain.hpp b/src/crab/array_domain.hpp index ee3808e9e..70525cb90 100644 --- a/src/crab/array_domain.hpp +++ b/src/crab/array_domain.hpp @@ -87,6 +87,10 @@ class array_domain_t final { // Perform array stores over an array segment void store_numbers(NumAbsDomain& inv, variable_t _idx, variable_t _width); + void split_number_var(NumAbsDomain& inv, data_kind_t kind, const linear_expression_t& i, + const linear_expression_t& elem_size); + void split_cell(NumAbsDomain& inv, data_kind_t kind, int cell_start_index, unsigned int len); + void initialize_numbers(int lb, int width); }; diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index a8c9e1f01..a6be102ae 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -1447,6 +1447,12 @@ void ebpf_domain_t::operator()(const Assume& s) { void ebpf_domain_t::operator()(const Undefined& a) {} +// Simple truncation function usable with swap_endianness(). +template +BOOST_CONSTEXPR T truncate(T x) BOOST_NOEXCEPT { + return x; +} + void ebpf_domain_t::operator()(const Un& stmt) { auto dst = reg_pack(stmt.dst); auto swap_endianness = [&](variable_t v, auto input, const auto& be_or_le) { @@ -1464,33 +1470,57 @@ void ebpf_domain_t::operator()(const Un& stmt) { havoc(v); havoc_offsets(stmt.dst); }; - // Swap bytes. For 64-bit types we need the weights to fit in a + // Swap bytes if needed. For 64-bit types we need the weights to fit in a // signed int64, but for smaller types we don't want sign extension, // so we use unsigned which still fits in a signed int64. switch (stmt.op) { case Un::Op::BE16: - swap_endianness(dst.svalue, uint16_t(0), boost::endian::native_to_big); - swap_endianness(dst.uvalue, uint16_t(0), boost::endian::native_to_big); + if (!thread_local_options.big_endian) { + swap_endianness(dst.svalue, uint16_t(0), boost::endian::endian_reverse); + swap_endianness(dst.uvalue, uint16_t(0), boost::endian::endian_reverse); + } else { + swap_endianness(dst.svalue, uint16_t(0), truncate); + swap_endianness(dst.uvalue, uint16_t(0), truncate); + } break; case Un::Op::BE32: - swap_endianness(dst.svalue, uint32_t(0), boost::endian::native_to_big); - swap_endianness(dst.uvalue, uint32_t(0), boost::endian::native_to_big); + if (!thread_local_options.big_endian) { + swap_endianness(dst.svalue, uint32_t(0), boost::endian::endian_reverse); + swap_endianness(dst.uvalue, uint32_t(0), boost::endian::endian_reverse); + } else { + swap_endianness(dst.svalue, uint32_t(0), truncate); + swap_endianness(dst.uvalue, uint32_t(0), truncate); + } break; case Un::Op::BE64: - swap_endianness(dst.svalue, int64_t(0), boost::endian::native_to_big); - swap_endianness(dst.uvalue, uint64_t(0), boost::endian::native_to_big); + if (!thread_local_options.big_endian) { + swap_endianness(dst.svalue, int64_t(0), boost::endian::endian_reverse); + swap_endianness(dst.uvalue, uint64_t(0), boost::endian::endian_reverse); + } break; case Un::Op::LE16: - swap_endianness(dst.svalue, uint16_t(0), boost::endian::native_to_little); - swap_endianness(dst.uvalue, uint16_t(0), boost::endian::native_to_little); + if (thread_local_options.big_endian) { + swap_endianness(dst.svalue, uint16_t(0), boost::endian::endian_reverse); + swap_endianness(dst.uvalue, uint16_t(0), boost::endian::endian_reverse); + } else { + swap_endianness(dst.svalue, uint16_t(0), truncate); + swap_endianness(dst.uvalue, uint16_t(0), truncate); + } break; case Un::Op::LE32: - swap_endianness(dst.svalue, uint32_t(0), boost::endian::native_to_little); - swap_endianness(dst.uvalue, uint32_t(0), boost::endian::native_to_little); + if (thread_local_options.big_endian) { + swap_endianness(dst.svalue, uint32_t(0), boost::endian::endian_reverse); + swap_endianness(dst.uvalue, uint32_t(0), boost::endian::endian_reverse); + } else { + swap_endianness(dst.svalue, uint32_t(0), truncate); + swap_endianness(dst.uvalue, uint32_t(0), truncate); + } break; case Un::Op::LE64: - swap_endianness(dst.svalue, int64_t(0), boost::endian::native_to_little); - swap_endianness(dst.svalue, uint64_t(0), boost::endian::native_to_little); + if (thread_local_options.big_endian) { + swap_endianness(dst.svalue, int64_t(0), boost::endian::endian_reverse); + swap_endianness(dst.uvalue, uint64_t(0), boost::endian::endian_reverse); + } break; case Un::Op::SWAP16: swap_endianness(dst.svalue, uint16_t(0), boost::endian::endian_reverse); diff --git a/src/ebpf_yaml.cpp b/src/ebpf_yaml.cpp index 50378f5a8..dfdf6ef3e 100644 --- a/src/ebpf_yaml.cpp +++ b/src/ebpf_yaml.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MIT #include +#include #include #include #include @@ -177,6 +178,9 @@ static ebpf_verifier_options_t raw_options_to_options(const std::set& ra options.no_simplify = true; options.setup_constraints = false; + // Default to the machine's native endianness. + options.big_endian = (std::endian::native == std::endian::big); + for (const string& name : raw_options) { if (name == "!allow_division_by_zero") { options.allow_division_by_zero = false; @@ -184,6 +188,10 @@ static ebpf_verifier_options_t raw_options_to_options(const std::set& ra options.check_termination = true; } else if (name == "strict") { options.strict = true; + } else if (name == "big_endian") { + options.big_endian = true; + } else if (name == "!big_endian") { + options.big_endian = false; } else { throw std::runtime_error("Unknown option: " + name); } diff --git a/src/test/test_conformance.cpp b/src/test/test_conformance.cpp index 5faca7769..0680e0b2d 100644 --- a/src/test/test_conformance.cpp +++ b/src/test/test_conformance.cpp @@ -151,25 +151,25 @@ TEST_CONFORMANCE("le16.data") TEST_CONFORMANCE("le32.data") TEST_CONFORMANCE("le64.data") TEST_CONFORMANCE("lock_add.data") -TEST_CONFORMANCE_RANGE("lock_add32.data", "[0, 1311768467463790321]") +TEST_CONFORMANCE("lock_add32.data") TEST_CONFORMANCE("lock_and.data") -TEST_CONFORMANCE_TOP("lock_and32.data") +TEST_CONFORMANCE("lock_and32.data") TEST_CONFORMANCE_TOP("lock_cmpxchg.data") TEST_CONFORMANCE_TOP("lock_cmpxchg32.data") TEST_CONFORMANCE("lock_fetch_add.data") -TEST_CONFORMANCE_RANGE("lock_fetch_add32.data", "[0, 1311768467463790321]") +TEST_CONFORMANCE("lock_fetch_add32.data") TEST_CONFORMANCE("lock_fetch_and.data") -TEST_CONFORMANCE_TOP("lock_fetch_and32.data") +TEST_CONFORMANCE("lock_fetch_and32.data") TEST_CONFORMANCE("lock_fetch_or.data") -TEST_CONFORMANCE_TOP("lock_fetch_or32.data") +TEST_CONFORMANCE("lock_fetch_or32.data") TEST_CONFORMANCE("lock_fetch_xor.data") -TEST_CONFORMANCE_TOP("lock_fetch_xor32.data") +TEST_CONFORMANCE("lock_fetch_xor32.data") TEST_CONFORMANCE("lock_or.data") -TEST_CONFORMANCE_TOP("lock_or32.data") +TEST_CONFORMANCE("lock_or32.data") TEST_CONFORMANCE("lock_xchg.data") TEST_CONFORMANCE("lock_xchg32.data") TEST_CONFORMANCE("lock_xor.data") -TEST_CONFORMANCE_TOP("lock_xor32.data") +TEST_CONFORMANCE("lock_xor32.data") TEST_CONFORMANCE("lsh32-imm.data") TEST_CONFORMANCE("lsh32-imm-high.data") TEST_CONFORMANCE("lsh32-imm-neg.data") diff --git a/test-data/stack.yaml b/test-data/stack.yaml index d1c4ab198..68fd3e866 100644 --- a/test-data/stack.yaml +++ b/test-data/stack.yaml @@ -1,6 +1,322 @@ # Copyright (c) Prevail Verifier contributors. # SPDX-License-Identifier: MIT --- +test-case: stack load 16 bits little-endian across multiple variables + +pre: ["r10.type=stack", "r10.stack_offset=512", + "s[504...504].type=number", "s[504...504].svalue=1", "s[504...504].uvalue=1", + "s[505...505].type=number", "s[505...505].svalue=2", "s[505...505].uvalue=2"] + +options: ["!big_endian"] + +code: + : | + r0 = *(u16 *)(r10 - 8) ; load 2 bytes spanning s[504...505]: 0x01 and 0x02 -> 0x0201 + +post: + - r10.type=stack + - r10.stack_offset=512 + - s[504...505].type=number + - s[504].svalue=1 + - s[504].uvalue=1 + - s[505].svalue=2 + - s[505].uvalue=2 + - r0.type=number + - r0.svalue=513 + - r0.uvalue=513 +--- +test-case: stack load 16 bits big-endian across multiple variables + +pre: ["r10.type=stack", "r10.stack_offset=512", + "s[504...504].type=number", "s[504...504].svalue=2", "s[504...504].uvalue=2", + "s[505...505].type=number", "s[505...505].svalue=1", "s[505...505].uvalue=1"] + +options: ["big_endian"] + +code: + : | + r0 = *(u16 *)(r10 - 8) ; load 2 bytes spanning s[504...505]: 0x02 and 0x01 -> 0x0201 + +post: + - r10.type=stack + - r10.stack_offset=512 + - s[504...505].type=number + - s[504].svalue=2 + - s[504].uvalue=2 + - s[505].svalue=1 + - s[505].uvalue=1 + - r0.type=number + - r0.svalue=513 + - r0.uvalue=513 +--- +test-case: stack load 32 bits little-endian across multiple variables + +pre: ["r10.type=stack", "r10.stack_offset=512", + "s[504...504].type=number", "s[504...504].svalue=120", "s[504...504].uvalue=120", + "s[505...505].type=number", "s[505...505].svalue=86", "s[505...505].uvalue=86", + "s[506...506].type=number", "s[506...506].svalue=52", "s[506...506].uvalue=52", + "s[507...507].type=number", "s[507...507].svalue=18", "s[507...507].uvalue=18"] + +options: ["!big_endian"] + +code: + : | + r0 = *(u32 *)(r10 - 8) ; load s[504...507]: 0x12345678 on little-endian + +post: + - r10.type=stack + - r10.stack_offset=512 + - s[504...507].type=number + - s[504].svalue=120 + - s[504].uvalue=120 + - s[505].svalue=86 + - s[505].uvalue=86 + - s[506].svalue=52 + - s[506].uvalue=52 + - s[507].svalue=18 + - s[507].uvalue=18 + - r0.type=number + - r0.svalue=305419896 + - r0.uvalue=305419896 +--- +test-case: stack load 32 bits big-endian across multiple variables + +pre: ["r10.type=stack", "r10.stack_offset=512", + "s[504...504].type=number", "s[504...504].svalue=18", "s[504...504].uvalue=18", + "s[505...505].type=number", "s[505...505].svalue=52", "s[505...505].uvalue=52", + "s[506...506].type=number", "s[506...506].svalue=86", "s[506...506].uvalue=86", + "s[507...507].type=number", "s[507...507].svalue=120", "s[507...507].uvalue=120"] + +options: ["big_endian"] + +code: + : | + r0 = *(u32 *)(r10 - 8) ; load s[504...507]: 0x12345678 on big-endian + +post: + - r10.type=stack + - r10.stack_offset=512 + - s[504...507].type=number + - s[504].svalue=18 + - s[504].uvalue=18 + - s[505].svalue=52 + - s[505].uvalue=52 + - s[506].svalue=86 + - s[506].uvalue=86 + - s[507].svalue=120 + - s[507].uvalue=120 + - r0.type=number + - r0.svalue=305419896 + - r0.uvalue=305419896 +--- +test-case: stack load 64 bits little-endian across multiple variables + +pre: ["r10.type=stack", "r10.stack_offset=512", + "s[504...504].type=number", "s[504...504].svalue=240", "s[504...504].uvalue=240", + "s[505...505].type=number", "s[505...505].svalue=222", "s[505...505].uvalue=222", + "s[506...506].type=number", "s[506...506].svalue=188", "s[506...506].uvalue=188", + "s[507...507].type=number", "s[507...507].svalue=154", "s[507...507].uvalue=154", + "s[508...508].type=number", "s[508...508].svalue=120", "s[508...508].uvalue=120", + "s[509...509].type=number", "s[509...509].svalue=86", "s[509...509].uvalue=86", + "s[510...510].type=number", "s[510...510].svalue=52", "s[510...510].uvalue=52", + "s[511...511].type=number", "s[511...511].svalue=18", "s[511...511].uvalue=18"] + +options: ["!big_endian"] + +code: + : | + r0 = *(u64 *)(r10 - 8) ; load s[504...511]: 0x9abcdef0 and 0x12345678 -> 0x123456789abcdef0 on little-endian + +post: + - r10.type=stack + - r10.stack_offset=512 + - s[504...511].type=number + - s[504].svalue=240 + - s[504].uvalue=240 + - s[505].svalue=222 + - s[505].uvalue=222 + - s[506].svalue=188 + - s[506].uvalue=188 + - s[507].svalue=154 + - s[507].uvalue=154 + - s[508].svalue=120 + - s[508].uvalue=120 + - s[509].svalue=86 + - s[509].uvalue=86 + - s[510].svalue=52 + - s[510].uvalue=52 + - s[511].svalue=18 + - s[511].uvalue=18 + - r0.type=number + - r0.svalue=1311768467463790320 + - r0.uvalue=1311768467463790320 +--- +test-case: stack load 64 bits big-endian across multiple variables + +pre: ["r10.type=stack", "r10.stack_offset=512", + "s[504...504].type=number", "s[504...504].svalue=18", "s[504...504].uvalue=18", + "s[505...505].type=number", "s[505...505].svalue=52", "s[505...505].uvalue=52", + "s[506...506].type=number", "s[506...506].svalue=86", "s[506...506].uvalue=86", + "s[507...507].type=number", "s[507...507].svalue=120", "s[507...507].uvalue=120", + "s[508...508].type=number", "s[508...508].svalue=154", "s[508...508].uvalue=154", + "s[509...509].type=number", "s[509...509].svalue=188", "s[509...509].uvalue=188", + "s[510...510].type=number", "s[510...510].svalue=222", "s[510...510].uvalue=222", + "s[511...511].type=number", "s[511...511].svalue=240", "s[511...511].uvalue=240"] + +options: ["big_endian"] + +code: + : | + r0 = *(u64 *)(r10 - 8) ; load s[504...511]: 0x123456789abcdef0 on big-endian + +post: + - r10.type=stack + - r10.stack_offset=512 + - s[504...511].type=number + - s[504].svalue=18 + - s[504].uvalue=18 + - s[505].svalue=52 + - s[505].uvalue=52 + - s[506].svalue=86 + - s[506].uvalue=86 + - s[507].svalue=120 + - s[507].uvalue=120 + - s[508].svalue=154 + - s[508].uvalue=154 + - s[509].svalue=188 + - s[509].uvalue=188 + - s[510].svalue=222 + - s[510].uvalue=222 + - s[511].svalue=240 + - s[511].uvalue=240 + - r0.type=number + - r0.svalue=1311768467463790320 + - r0.uvalue=1311768467463790320 +--- +test-case: stack re-assign little-endian immediate first + +pre: ["r10.type=stack", "r10.stack_offset=512", + "s[504...511].type=number", "s[504...511].svalue=1311768467463790320", "s[504...511].uvalue=1311768467463790320"] + +options: ["!big_endian"] + +code: + : | + *(u32 *)(r10 - 8) = 0 ; zero the first four bytes of 0x123456789abcdef0 -> 0x12345678 on little-endian machine + +post: + - r10.type=stack + - r10.stack_offset=512 + - s[504...511].type=number + - s[504...507].svalue=0 + - s[504...507].uvalue=0 + - s[508...511].svalue=305419896 + - s[508...511].uvalue=305419896 +--- +test-case: stack re-assign big-endian immediate first + +pre: ["r10.type=stack", "r10.stack_offset=512", + "s[504...511].type=number", "s[504...511].svalue=1311768467463790320", "s[504...511].uvalue=1311768467463790320"] + +options: ["big_endian"] + +code: + : | + *(u32 *)(r10 - 8) = 0 ; zero the first four bytes of 0x123456789abcdef0 -> 0x9abcdef0 on big-endian machine + +post: + - r10.type=stack + - r10.stack_offset=512 + - s[504...511].type=number + - s[504...507].svalue=0 + - s[504...507].uvalue=0 + - s[508...511].svalue=2596069104 + - s[508...511].uvalue=2596069104 +--- +test-case: stack re-assign little-endian immediate middle + +pre: ["r10.type=stack", "r10.stack_offset=512", + "s[504...511].type=number", "s[504...511].svalue=1311768467463790320", "s[504...511].uvalue=1311768467463790320"] + +options: ["!big_endian"] + +code: + : | + *(u32 *)(r10 - 6) = 0 ; zero the middle four bytes of 0x123456789abcdef0 + +post: + - r10.type=stack + - r10.stack_offset=512 + - s[504...511].type=number + - s[504...505].svalue=57072 + - s[504...505].uvalue=57072 + - s[506...509].svalue=0 + - s[506...509].uvalue=0 + - s[510...511].svalue=4660 + - s[510...511].uvalue=4660 +--- +test-case: stack re-assign big-endian immediate middle + +pre: ["r10.type=stack", "r10.stack_offset=512", + "s[504...511].type=number", "s[504...511].svalue=1311768467463790320", "s[504...511].uvalue=1311768467463790320"] + +options: ["big_endian"] + +code: + : | + *(u32 *)(r10 - 6) = 0 ; zero the middle four bytes of 0x123456789abcdef0 + +post: + - r10.type=stack + - r10.stack_offset=512 + - s[504...511].type=number + - s[504...505].svalue=4660 + - s[504...505].uvalue=4660 + - s[506...509].svalue=0 + - s[506...509].uvalue=0 + - s[510...511].svalue=57072 + - s[510...511].uvalue=57072 +--- +test-case: stack re-assign little-endian immediate last + +pre: ["r10.type=stack", "r10.stack_offset=512", + "s[504...511].type=number", "s[504...511].svalue=1311768467463790320", "s[504...511].uvalue=1311768467463790320"] + +options: ["!big_endian"] + +code: + : | + *(u32 *)(r10 - 4) = 0 ; zero the last four bytes of 0x123456789abcdef0 -> 0x9abcdef0 + +post: + - r10.type=stack + - r10.stack_offset=512 + - s[504...511].type=number + - s[504...507].svalue=2596069104 + - s[504...507].uvalue=2596069104 + - s[508...511].svalue=0 + - s[508...511].uvalue=0 +--- +test-case: stack re-assign big-endian immediate last + +pre: ["r10.type=stack", "r10.stack_offset=512", + "s[504...511].type=number", "s[504...511].svalue=1311768467463790320", "s[504...511].uvalue=1311768467463790320"] + +options: ["big_endian"] + +code: + : | + *(u32 *)(r10 - 4) = 0 ; zero the last four bytes of 0x123456789abcdef0 -> 0x12345678 + +post: + - r10.type=stack + - r10.stack_offset=512 + - s[504...511].type=number + - s[504...507].svalue=305419896 + - s[504...507].uvalue=305419896 + - s[508...511].svalue=0 + - s[508...511].uvalue=0 +--- test-case: set numeric size pre: ["r1.type=number", diff --git a/test-data/unop.yaml b/test-data/unop.yaml index 180ff41ca..124f33fe6 100644 --- a/test-data/unop.yaml +++ b/test-data/unop.yaml @@ -41,120 +41,288 @@ code: post: ["r1.type=number", "r1.svalue=[-5, 5]"] --- -test-case: be16 singleton +test-case: be16 singleton on little-endian pre: ["r1.type=number", "r1.svalue=6636321", "r1.uvalue=6636321"] +options: ["!big_endian"] + code: : | r1 = be16 r1 ; 0x654321 -> 0x2143 post: ["r1.type=number", "r1.svalue=8515", "r1.uvalue=8515"] --- -test-case: be32 singleton +test-case: be16 singleton on big-endian + +pre: ["r1.type=number", "r1.svalue=6636321", "r1.uvalue=6636321"] + +options: ["big_endian"] + +code: + : | + r1 = be16 r1 ; 0x654321 -> 0x4321 + +post: ["r1.type=number", "r1.svalue=17185", "r1.uvalue=17185"] +--- +test-case: be32 singleton on little-endian pre: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] +options: ["!big_endian"] + code: : | r1 = be32 r1 ; 0x0987654321 -> 0x21436587 post: ["r1.type=number", "r1.svalue=558065031", "r1.uvalue=558065031"] --- -test-case: be64 singleton +test-case: be32 singleton on big-endian + +pre: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] + +options: ["big_endian"] + +code: + : | + r1 = be32 r1 ; 0x0987654321 -> 0x87654321 + +post: ["r1.type=number", "r1.svalue=2271560481", "r1.uvalue=2271560481"] +--- +test-case: be64 singleton on little-endian pre: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] +options: ["!big_endian"] + code: : | r1 = be64 r1 ; 0x0987654321 -> 0x2143658709000000 post: ["r1.type=number", "r1.svalue=2396871057337221120", "r1.uvalue=2396871057337221120"] --- -test-case: be16 range +test-case: be64 singleton on big-endian + +pre: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] + +options: ["big_endian"] + +code: + : | + r1 = be64 r1 ; nop + +post: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] +--- +test-case: be16 range on little-endian pre: ["r1.type=number", "r1.svalue=[0, 2]", "r1.uvalue=[0, 2]", "r1.svalue=r1.uvalue"] +options: ["!big_endian"] + code: : | r1 = be16 r1 ; [0x0000, 0x0002] -> [0x0000, 0x2000] but currently we just lose the range post: ["r1.type=number"] --- -test-case: le16 singleton +test-case: be16 range on big-endian + +pre: ["r1.type=number", "r1.svalue=[0, 2]", "r1.uvalue=[0, 2]", "r1.svalue=r1.uvalue"] + +options: ["big_endian"] + +code: + : | + r1 = be16 r1 ; nop. this could preserve the range but we don't support that yet + +post: ["r1.type=number"] +--- +test-case: le16 singleton on little-endian pre: ["r1.type=number", "r1.svalue=6636321", "r1.uvalue=6636321"] +options: ["!big_endian"] + code: : | r1 = le16 r1 ; 0x654321 -> 0x4321 post: ["r1.type=number", "r1.svalue=17185", "r1.uvalue=17185"] --- -test-case: le32 singleton +test-case: le16 singleton on big-endian + +pre: ["r1.type=number", "r1.svalue=6636321", "r1.uvalue=6636321"] + +options: ["big_endian"] + +code: + : | + r1 = le16 r1 ; 0x654321 -> 0x2143 + +post: ["r1.type=number", "r1.svalue=8515", "r1.uvalue=8515"] +--- +test-case: le32 singleton on little-endian pre: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] +options: ["!big_endian"] + code: : | r1 = le32 r1 ; 0x0987654321 -> 0x87654321 post: ["r1.type=number", "r1.svalue=2271560481", "r1.uvalue=2271560481"] --- -test-case: le64 singleton +test-case: le32 singleton on big-endian pre: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] +options: ["big_endian"] + code: : | - r1 = le64 r1 ; 0x0987654321 -> 0x2143658709000000 + r1 = le32 r1 ; 0x0987654321 -> 0x21436587 + +post: ["r1.type=number", "r1.svalue=558065031", "r1.uvalue=558065031"] +--- +test-case: le64 singleton on little-endian + +pre: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] + +options: ["!big_endian"] + +code: + : | + r1 = le64 r1 ; nop post: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] --- -test-case: le16 range +test-case: le64 singleton on big-endian + +pre: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] + +options: ["big_endian"] + +code: + : | + r1 = le64 r1 ; 0x0987654321 -> 0x2143658709000000 + +post: ["r1.type=number", "r1.svalue=2396871057337221120", "r1.uvalue=2396871057337221120"] +--- +test-case: le16 range on little-endian + +pre: ["r1.type=number", "r1.svalue=[0, 2]", "r1.uvalue=[0, 2]", "r1.svalue=r1.uvalue"] + +options: ["!big_endian"] + +code: + : | + r1 = le16 r1 ; nop. this could preserve the range but we don't support that yet + +post: ["r1.type=number"] +--- +test-case: le16 range on big-endian pre: ["r1.type=number", "r1.svalue=[0, 2]", "r1.uvalue=[0, 2]", "r1.svalue=r1.uvalue"] +options: ["!big_endian"] + code: : | - r1 = le16 r1 ; this could preserve the range but we don't support that yet + r1 = le16 r1 post: ["r1.type=number"] --- -test-case: swap16 singleton +test-case: swap16 singleton on little-endian + +pre: ["r1.type=number", "r1.svalue=6636321", "r1.uvalue=6636321"] + +options: ["!big_endian"] + +code: + : | + r1 = swap16 r1 ; 0x654321 -> 0x2143 + +post: ["r1.type=number", "r1.svalue=8515", "r1.uvalue=8515"] +--- +test-case: swap16 singleton on big-endian pre: ["r1.type=number", "r1.svalue=6636321", "r1.uvalue=6636321"] +options: ["big_endian"] + code: : | r1 = swap16 r1 ; 0x654321 -> 0x2143 post: ["r1.type=number", "r1.svalue=8515", "r1.uvalue=8515"] --- -test-case: swap32 singleton +test-case: swap32 singleton on little-endian pre: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] +options: ["!big_endian"] + code: : | r1 = swap32 r1 ; 0x0987654321 -> 0x21436587 post: ["r1.type=number", "r1.svalue=558065031", "r1.uvalue=558065031"] --- -test-case: swap64 singleton +test-case: swap32 singleton on big-endian pre: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] +options: ["big_endian"] + +code: + : | + r1 = swap32 r1 ; 0x0987654321 -> 0x21436587 + +post: ["r1.type=number", "r1.svalue=558065031", "r1.uvalue=558065031"] +--- +test-case: swap64 singleton on little-endian + +pre: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] + +options: ["!big_endian"] + code: : | r1 = swap64 r1 ; 0x0987654321 -> 0x2143658709000000 post: ["r1.type=number", "r1.svalue=2396871057337221120", "r1.uvalue=2396871057337221120"] --- -test-case: swap16 range +test-case: swap64 singleton on big-endian + +pre: ["r1.type=number", "r1.svalue=40926266145", "r1.uvalue=40926266145"] + +options: ["big_endian"] + +code: + : | + r1 = swap64 r1 ; 0x0987654321 -> 0x2143658709000000 + +post: ["r1.type=number", "r1.svalue=2396871057337221120", "r1.uvalue=2396871057337221120"] +--- +test-case: swap16 range on little-endian pre: ["r1.type=number", "r1.svalue=[0, 2]", "r1.uvalue=[0, 2]", "r1.svalue=r1.uvalue"] +options: ["!big_endian"] + +code: + : | + r1 = swap16 r1 ; [0x0000, 0x0002] -> [0x0000, 0x2000] but currently we just lose the range + +post: ["r1.type=number"] +--- +test-case: swap16 range on big-endian + +pre: ["r1.type=number", "r1.svalue=[0, 2]", "r1.uvalue=[0, 2]", "r1.svalue=r1.uvalue"] + +options: ["big_endian"] + code: : | r1 = swap16 r1 ; [0x0000, 0x0002] -> [0x0000, 0x2000] but currently we just lose the range From 07620ad5d30f2361443579d59185414d965e4ef1 Mon Sep 17 00:00:00 2001 From: Alan Jowett Date: Sun, 12 May 2024 11:13:50 -0700 Subject: [PATCH 185/373] Permit shared region as key or value for map (#623) Signed-off-by: Alan Jowett --- src/assertions.cpp | 2 +- src/crab/ebpf_domain.cpp | 6 + test-data/call.yaml | 211 +++++++++++++++++++++++++++++++- test-data/callx.yaml | 253 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 469 insertions(+), 3 deletions(-) diff --git a/src/assertions.cpp b/src/assertions.cpp index c3e090134..e64a683c4 100644 --- a/src/assertions.cpp +++ b/src/assertions.cpp @@ -70,7 +70,7 @@ class AssertExtractor { case ArgSingle::Kind::PTR_TO_MAP_KEY: case ArgSingle::Kind::PTR_TO_MAP_VALUE: assert(map_fd_reg); - res.emplace_back(TypeConstraint{arg.reg, TypeGroup::stack_or_packet}); + res.emplace_back(TypeConstraint{arg.reg, TypeGroup::mem}); res.emplace_back(ValidMapKeyValue{arg.reg, *map_fd_reg, arg.kind == ArgSingle::Kind::PTR_TO_MAP_KEY}); break; diff --git a/src/crab/ebpf_domain.cpp b/src/crab/ebpf_domain.cpp index a6be102ae..200c6b469 100644 --- a/src/crab/ebpf_domain.cpp +++ b/src/crab/ebpf_domain.cpp @@ -1783,6 +1783,12 @@ void ebpf_domain_t::operator()(const ValidMapKeyValue& s) { linear_expression_t ub = lb + width; check_access_packet(inv, lb, ub, {}); // Packet memory is both readable and writable. + } else if (access_reg_type == T_SHARED) { + variable_t lb = access_reg.shared_offset; + linear_expression_t ub = lb + width; + check_access_shared(inv, lb, ub, access_reg.shared_region_size); + require(inv, access_reg.svalue > 0, "Possible null access"); + // Shared memory is zero-initialized when created so is safe to read and write. } else { require(inv, linear_constraint_t::FALSE(), "Only stack or packet can be used as a parameter"); } diff --git a/test-data/call.yaml b/test-data/call.yaml index 8c9eeea38..e7c81c31b 100644 --- a/test-data/call.yaml +++ b/test-data/call.yaml @@ -20,6 +20,104 @@ post: - r0.svalue=r0.uvalue - s[504...511].type=number --- +test-case: bpf_map_lookup_elem shared key + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=shared", "r2.shared_offset=0", "r2.shared_region_size=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]"] + +code: + : | + call 1; bpf_map_lookup_elem + +post: + - r0.type=shared + - r0.shared_offset=0 + - r0.shared_region_size=4 + - r0.svalue=[0, 2147418112] + - r0.uvalue=[0, 2147418112] + - r0.svalue=r0.uvalue +--- +test-case: bpf_map_lookup_elem packet key + +pre: [ "meta_offset=0", + "r1.type=map_fd", "r1.map_fd=1", + "r2.type=packet", "r2.packet_offset=0", "packet_size=4"] + +code: + : | + call 1; bpf_map_lookup_elem + +post: + - r0.type=shared + - r0.shared_offset=0 + - r0.shared_region_size=4 + - r0.svalue=[0, 2147418112] + - r0.uvalue=[0, 2147418112] + - r0.svalue=r0.uvalue + - meta_offset=0 + - packet_size=4 +--- +test-case: bpf_map_lookup_elem shared key - maybe null + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=shared", "r2.shared_offset=0", "r2.shared_region_size=4", "r2.svalue=[0, 2147418112]", "r2.uvalue=[0, 2147418112]"] + +code: + : | + call 1; bpf_map_lookup_elem + +post: + - r0.type=shared + - r0.shared_offset=0 + - r0.shared_region_size=4 + - r0.svalue=[0, 2147418112] + - r0.uvalue=[0, 2147418112] + - r0.svalue=r0.uvalue + +messages: + - "0: Possible null access (within stack(r2:key_size(r1)))" + +--- +test-case: bpf_map_lookup_elem shared key - too small + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=shared", "r2.shared_offset=0", "r2.shared_region_size=2", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]"] + +code: + : | + call 1; bpf_map_lookup_elem + +post: + - r0.type=shared + - r0.shared_offset=0 + - r0.shared_region_size=4 + - r0.svalue=[0, 2147418112] + - r0.uvalue=[0, 2147418112] + - r0.svalue=r0.uvalue + +messages: + - "0: Upper bound must be at most r2.shared_region_size (within stack(r2:key_size(r1)))" +--- +test-case: bpf_map_lookup_elem shared key - too small + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=shared", "r2.shared_offset=-2", "r2.shared_region_size=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]"] + +code: + : | + call 1; bpf_map_lookup_elem + +post: + - r0.type=shared + - r0.shared_offset=0 + - r0.shared_region_size=4 + - r0.svalue=[0, 2147418112] + - r0.uvalue=[0, 2147418112] + - r0.svalue=r0.uvalue + +messages: + - "0: Lower bound must be at least 0 (within stack(r2:key_size(r1)))" +--- test-case: bpf_map_lookup_elem with non-numeric stack key pre: ["r1.type=map_fd", "r1.map_fd=1", @@ -40,6 +138,117 @@ post: messages: - "0: Illegal map update with a non-numerical value [-oo-oo) (within stack(r2:key_size(r1)))" --- +test-case: bpf_map_update_elem with numeric stack value + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=stack", "r2.stack_offset=504", + "r3.type=stack", "r3.stack_offset=496", + "r4.type=number", + "s[496...511].type=number"] + +code: + : | + call 2; bpf_map_update_elem + +post: + - r0.type=number + - s[496...511].type=number + +--- +test-case: bpf_map_update_elem with shared value + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=stack", "r2.stack_offset=504", + "r3.type=shared", "r3.shared_offset=0", "r3.shared_region_size=4", "r3.svalue=[1, 2147418112]", "r3.uvalue=[1, 2147418112]", + "r4.type=number", + "s[496...511].type=number"] + +code: + : | + call 2; bpf_map_update_elem + +post: + - r0.type=number + - s[496...511].type=number + +--- +test-case: bpf_map_update_elem with shared value too small + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=stack", "r2.stack_offset=504", + "r3.type=shared", "r3.shared_offset=0", "r3.shared_region_size=2", "r3.svalue=[1, 2147418112]", "r3.uvalue=[1, 2147418112]", + "r4.type=number", + "s[496...511].type=number"] + +code: + : | + call 2; bpf_map_update_elem + +post: + - r0.type=number + - s[496...511].type=number + +messages: + - "0: Upper bound must be at most r3.shared_region_size (within stack(r3:value_size(r1)))" + +--- +test-case: bpf_map_update_elem with shared value larger than target region + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=stack", "r2.stack_offset=504", + "r3.type=shared", "r3.shared_offset=0", "r3.shared_region_size=64", "r3.svalue=[1, 2147418112]", "r3.uvalue=[1, 2147418112]", + "r4.type=number", + "s[496...511].type=number"] + +code: + : | + call 2; bpf_map_update_elem + +post: + - r0.type=number + - s[496...511].type=number + +--- +test-case: bpf_map_update_elem with shared value may be null + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=stack", "r2.stack_offset=504", + "r3.type=shared", "r3.shared_offset=0", "r3.shared_region_size=4", "r3.svalue=[0, 2147418112]", "r3.uvalue=[0, 2147418112]", + "r4.type=number", + "s[496...511].type=number"] + +code: + : | + call 2; bpf_map_update_elem + +post: + - r0.type=number + - s[496...511].type=number + +messages: + - "0: Possible null access (within stack(r3:value_size(r1)))" + +--- +test-case: bpf_map_update_elem with packet value + +pre: ["meta_offset=0", + "r1.type=map_fd", "r1.map_fd=1", + "r2.type=stack", "r2.stack_offset=504", + "r3.type=packet", "r3.packet_offset=0", "packet_size=4", + "r4.type=number", + "s[496...511].type=number"] + +code: + : | + call 2; bpf_map_update_elem + +post: + - r0.type=number + - s[496...511].type=number + - meta_offset=0 + - packet_size=4 + +--- test-case: bpf_map_update_elem with non-numeric stack value pre: ["r1.type=map_fd", "r1.map_fd=1", @@ -76,7 +285,7 @@ post: - s[504...511].type=number messages: - - "0: Invalid type (r3.type in {stack, packet})" + - "0: Invalid type (r3.type in {stack, packet, shared})" - "0: Only stack or packet can be used as a parameter (within stack(r3:value_size(r1)))" --- test-case: bpf_ringbuf_output with numeric stack value diff --git a/test-data/callx.yaml b/test-data/callx.yaml index d262aafde..45da0f9a9 100644 --- a/test-data/callx.yaml +++ b/test-data/callx.yaml @@ -24,6 +24,123 @@ post: - r8.uvalue=1 - s[504...511].type=number --- +test-case: callx bpf_map_lookup_elem shared key + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=shared", "r2.shared_offset=0", "r2.shared_region_size=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", + "r8.type=number", "r8.svalue=1", "r8.uvalue=1"] + +code: + : | + callx r8; bpf_map_lookup_elem + +post: + - r0.type=shared + - r0.shared_offset=0 + - r0.shared_region_size=4 + - r0.svalue=[0, 2147418112] + - r0.uvalue=[0, 2147418112] + - r0.svalue=r0.uvalue + - r8.type=number + - r8.svalue=1 + - r8.uvalue=1 +--- +test-case: callx bpf_map_lookup_elem packet key + +pre: [ "meta_offset=0", + "r1.type=map_fd", "r1.map_fd=1", + "r2.type=packet", "r2.packet_offset=0", "packet_size=4", + "r8.type=number", "r8.svalue=1", "r8.uvalue=1"] + +code: + : | + callx r8; bpf_map_lookup_elem + +post: + - r0.type=shared + - r0.shared_offset=0 + - r0.shared_region_size=4 + - r0.svalue=[0, 2147418112] + - r0.uvalue=[0, 2147418112] + - r0.svalue=r0.uvalue + - meta_offset=0 + - packet_size=4 + - r8.type=number + - r8.svalue=1 + - r8.uvalue=1 +--- +test-case: callx bpf_map_lookup_elem shared key - maybe null + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=shared", "r2.shared_offset=0", "r2.shared_region_size=4", "r2.svalue=[0, 2147418112]", "r2.uvalue=[0, 2147418112]", + "r8.type=number", "r8.svalue=1", "r8.uvalue=1"] + +code: + : | + callx r8; bpf_map_lookup_elem + +post: + - r0.type=shared + - r0.shared_offset=0 + - r0.shared_region_size=4 + - r0.svalue=[0, 2147418112] + - r0.uvalue=[0, 2147418112] + - r0.svalue=r0.uvalue + - r8.type=number + - r8.svalue=1 + - r8.uvalue=1 + +messages: + - "0: Possible null access (within stack(r2:key_size(r1)))" +--- +test-case: callx bpf_map_lookup_elem shared key - too small + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=shared", "r2.shared_offset=0", "r2.shared_region_size=2", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", + "r8.type=number", "r8.svalue=1", "r8.uvalue=1"] + +code: + : | + callx r8; bpf_map_lookup_elem + +post: + - r0.type=shared + - r0.shared_offset=0 + - r0.shared_region_size=4 + - r0.svalue=[0, 2147418112] + - r0.uvalue=[0, 2147418112] + - r0.svalue=r0.uvalue + - r8.type=number + - r8.svalue=1 + - r8.uvalue=1 + +messages: + - "0: Upper bound must be at most r2.shared_region_size (within stack(r2:key_size(r1)))" +--- +test-case: callx bpf_map_lookup_elem shared key - too small + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=shared", "r2.shared_offset=-2", "r2.shared_region_size=4", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", + "r8.type=number", "r8.svalue=1", "r8.uvalue=1"] + +code: + : | + callx r8; bpf_map_lookup_elem + +post: + - r0.type=shared + - r0.shared_offset=0 + - r0.shared_region_size=4 + - r0.svalue=[0, 2147418112] + - r0.uvalue=[0, 2147418112] + - r0.svalue=r0.uvalue + - r8.type=number + - r8.svalue=1 + - r8.uvalue=1 + +messages: + - "0: Lower bound must be at least 0 (within stack(r2:key_size(r1)))" +--- test-case: callx 0 pre: ["r8.type=number", "r8.svalue=1000", "r8.uvalue=1000"] @@ -96,6 +213,140 @@ post: messages: - "0: Illegal map update with a non-numerical value [-oo-oo) (within stack(r2:key_size(r1)))" --- +test-case: callx bpf_map_update_elem with numeric stack value + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=stack", "r2.stack_offset=504", + "r3.type=stack", "r3.stack_offset=496", + "r4.type=number", + "r8.type=number", "r8.svalue=2", "r8.uvalue=2", + "s[496...511].type=number"] + +code: + : | + callx r8; bpf_map_update_elem + +post: + - r0.type=number + - s[496...511].type=number + - r8.type=number + - r8.svalue=2 + - r8.uvalue=2 + +--- +test-case: callx bpf_map_update_elem with shared value + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=stack", "r2.stack_offset=504", + "r3.type=shared", "r3.shared_offset=0", "r3.shared_region_size=4", "r3.svalue=[1, 2147418112]", "r3.uvalue=[1, 2147418112]", + "r4.type=number", + "r8.type=number", "r8.svalue=2", "r8.uvalue=2", + "s[496...511].type=number"] + +code: + : | + callx r8; bpf_map_update_elem + +post: + - r0.type=number + - s[496...511].type=number + - r8.type=number + - r8.svalue=2 + - r8.uvalue=2 + +--- +test-case: callx bpf_map_update_elem with shared value too small + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=stack", "r2.stack_offset=504", + "r3.type=shared", "r3.shared_offset=0", "r3.shared_region_size=2", "r3.svalue=[1, 2147418112]", "r3.uvalue=[1, 2147418112]", + "r4.type=number", + "r8.type=number", "r8.svalue=2", "r8.uvalue=2", + "s[496...511].type=number"] + +code: + : | + callx r8; bpf_map_update_elem + +post: + - r0.type=number + - s[496...511].type=number + - r8.type=number + - r8.svalue=2 + - r8.uvalue=2 + +messages: + - "0: Upper bound must be at most r3.shared_region_size (within stack(r3:value_size(r1)))" + +--- +test-case: callx bpf_map_update_elem with shared value larger than target region + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=stack", "r2.stack_offset=504", + "r3.type=shared", "r3.shared_offset=0", "r3.shared_region_size=64", "r3.svalue=[1, 2147418112]", "r3.uvalue=[1, 2147418112]", + "r4.type=number", + "r8.type=number", "r8.svalue=2", "r8.uvalue=2", + "s[496...511].type=number"] + +code: + : | + callx r8; bpf_map_update_elem + +post: + - r0.type=number + - s[496...511].type=number + - r8.type=number + - r8.svalue=2 + - r8.uvalue=2 + +--- +test-case: callx bpf_map_update_elem with shared value may be null + +pre: ["r1.type=map_fd", "r1.map_fd=1", + "r2.type=stack", "r2.stack_offset=504", + "r3.type=shared", "r3.shared_offset=0", "r3.shared_region_size=4", "r3.svalue=[0, 2147418112]", "r3.uvalue=[0, 2147418112]", + "r4.type=number", + "r8.type=number", "r8.svalue=2", "r8.uvalue=2", + "s[496...511].type=number"] + +code: + : | + callx r8; bpf_map_update_elem + +post: + - r0.type=number + - s[496...511].type=number + - r8.type=number + - r8.svalue=2 + - r8.uvalue=2 + +messages: + - "0: Possible null access (within stack(r3:value_size(r1)))" + +--- +test-case: callx bpf_map_update_elem with packet value + +pre: ["meta_offset=0", + "r1.type=map_fd", "r1.map_fd=1", + "r2.type=stack", "r2.stack_offset=504", + "r3.type=packet", "r3.packet_offset=0", "packet_size=4", + "r4.type=number", + "r8.type=number", "r8.svalue=2", "r8.uvalue=2", + "s[496...511].type=number"] + +code: + : | + callx r8; bpf_map_update_elem + +post: + - r0.type=number + - s[496...511].type=number + - meta_offset=0 + - packet_size=4 + - r8.type=number + - r8.svalue=2 + - r8.uvalue=2 +--- test-case: callx bpf_map_update_elem with non-numeric stack value pre: ["r1.type=map_fd", "r1.map_fd=1", @@ -140,7 +391,7 @@ post: - r8.uvalue=2 messages: - - "0: Invalid type (r3.type in {stack, packet})" + - "0: Invalid type (r3.type in {stack, packet, shared})" - "0: Only stack or packet can be used as a parameter (within stack(r3:value_size(r1)))" --- test-case: callx bpf_ringbuf_output with numeric stack value From 400f0a747c7aa15ac089da24962dd22f2bdb3bcf Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Sun, 12 May 2024 18:30:26 -0700 Subject: [PATCH 186/373] Add CI/CD workflow to validate YAML files (#618) * Add CI/CD workflow to validate YAML files * Fix YAML issues found by yamllint --------- Signed-off-by: Dave Thaler --- .github/workflows/codeql-analysis.yml | 70 +++++------ .github/workflows/coverage.yml | 14 +-- .github/workflows/validate-yaml.yml | 29 +++++ .yamllint.yml | 10 ++ test-data/atomic.yaml | 74 +++++------ test-data/full64.yaml | 42 +++---- test-data/packet.yaml | 172 ++++++++++---------------- test-data/unsigned.yaml | 26 ++-- 8 files changed, 218 insertions(+), 219 deletions(-) create mode 100644 .github/workflows/validate-yaml.yml create mode 100644 .yamllint.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 0522fc057..3a8a2f613 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,10 +13,10 @@ name: "CodeQL" on: push: - branches: [ main ] + branches: [main] pull_request: # The branches below must be a subset of the branches above - branches: [ main ] + branches: [main] schedule: - cron: '43 12 * * 2' @@ -28,46 +28,46 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'cpp' ] + language: ['cpp'] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - submodules: 'recursive' + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: 'recursive' - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - # - name: Autobuild - # uses: github/codeql-action/autobuild@v1 + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + # - name: Autobuild + # uses: github/codeql-action/autobuild@v1 - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - name: Install dependencies - run: | - sudo apt install libboost-dev libboost-filesystem-dev libboost-program-options-dev libyaml-cpp-dev - - - run: | - mkdir build - cmake -B build -DCMAKE_BUILD_TYPE=Debug - cmake --build build -j $(nproc) + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + - name: Install dependencies + run: | + sudo apt install libboost-dev libboost-filesystem-dev libboost-program-options-dev libyaml-cpp-dev - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + - run: | + mkdir build + cmake -B build -DCMAKE_BUILD_TYPE=Debug + cmake --build build -j $(nproc) + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 5874a3ae1..2765caabb 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -5,9 +5,9 @@ name: CPP Code Coverage on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] jobs: build_ubuntu: @@ -55,8 +55,8 @@ jobs: needs: build_ubuntu runs-on: ubuntu-20.04 steps: - - name: Coveralls Finished - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.github_token }} - parallel-finished: true + - name: Coveralls Finished + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.github_token }} + parallel-finished: true diff --git a/.github/workflows/validate-yaml.yml b/.github/workflows/validate-yaml.yml new file mode 100644 index 000000000..7f7fc048e --- /dev/null +++ b/.github/workflows/validate-yaml.yml @@ -0,0 +1,29 @@ +# Copyright (c) Prevail Verifier contributors. +# SPDX-License-Identifier: MIT +# +# For documentation on the github environment, see +# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners +# +# For documentation on the syntax of this file, see +# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions +name: Validate-YAML + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + validate-yaml: + runs-on: ubuntu-latest + + steps: + - name: Checkout GEDCOM.io + uses: actions/checkout@v4 + + - name: Validate YAML + run: yamllint . .github .github/workflows test-data diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 000000000..9b63738a3 --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,10 @@ +# Copyright (c) Prevail Verifier contributors. +# SPDX-License-Identifier: MIT +# +# Configuration file for yamllint. For documentation, see +# https://yamllint.readthedocs.io/en/stable/configuration.html + +extends: default + +rules: + line-length: disable diff --git a/test-data/atomic.yaml b/test-data/atomic.yaml index 2e4787ef3..d0e4e15ab 100644 --- a/test-data/atomic.yaml +++ b/test-data/atomic.yaml @@ -271,8 +271,8 @@ code: lock *(u32 *)(r2 + 4) += r1 post: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", - "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", - "s[4...7].type=number", "s[4...7].svalue=3", "s[4...7].uvalue=3"] + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=3", "s[4...7].uvalue=3"] --- test-case: atomic 32-bit ADD and fetch stack @@ -285,8 +285,8 @@ code: lock *(u32 *)(r2 + 4) += r1 fetch post: ["r1.type=number", "r1.svalue=1", "r1.uvalue=1", - "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", - "s[4...7].type=number", "s[4...7].svalue=3", "s[4...7].uvalue=3"] + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=3", "s[4...7].uvalue=3"] --- test-case: atomic 64-bit ADD stack @@ -299,8 +299,8 @@ code: lock *(u64 *)(r2 + 4) += r1 post: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", - "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", - "s[4...11].type=number", "s[4...11].svalue=3", "s[4...11].uvalue=3"] + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=3", "s[4...11].uvalue=3"] --- test-case: atomic 64-bit ADD and fetch stack @@ -313,8 +313,8 @@ code: lock *(u64 *)(r2 + 4) += r1 fetch post: ["r1.type=number", "r1.svalue=1", "r1.uvalue=1", - "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", - "s[4...11].type=number", "s[4...11].svalue=3", "s[4...11].uvalue=3"] + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=3", "s[4...11].uvalue=3"] --- test-case: atomic 32-bit AND stack @@ -327,8 +327,8 @@ code: lock *(u32 *)(r2 + 4) &= r1 post: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", - "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", - "s[4...7].type=number", "s[4...7].svalue=1", "s[4...7].uvalue=1"] + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=1", "s[4...7].uvalue=1"] --- test-case: atomic 32-bit AND and fetch stack @@ -341,8 +341,8 @@ code: lock *(u32 *)(r2 + 4) &= r1 fetch post: ["r1.type=number", "r1.svalue=5", "r1.uvalue=5", - "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", - "s[4...7].type=number", "s[4...7].svalue=1", "s[4...7].uvalue=1"] + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=1", "s[4...7].uvalue=1"] --- test-case: atomic 64-bit AND stack @@ -355,8 +355,8 @@ code: lock *(u64 *)(r2 + 4) &= r1 post: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", - "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", - "s[4...11].type=number", "s[4...11].svalue=1", "s[4...11].uvalue=1"] + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=1", "s[4...11].uvalue=1"] --- test-case: atomic 64-bit AND and fetch stack @@ -369,8 +369,8 @@ code: lock *(u64 *)(r2 + 4) &= r1 fetch post: ["r1.type=number", "r1.svalue=5", "r1.uvalue=5", - "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", - "s[4...11].type=number", "s[4...11].svalue=1", "s[4...11].uvalue=1"] + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=1", "s[4...11].uvalue=1"] --- test-case: atomic 32-bit OR stack @@ -383,8 +383,8 @@ code: lock *(u32 *)(r2 + 4) |= r1 post: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", - "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", - "s[4...7].type=number", "s[4...7].svalue=7", "s[4...7].uvalue=7"] + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=7", "s[4...7].uvalue=7"] --- test-case: atomic 32-bit OR and fetch stack @@ -397,8 +397,8 @@ code: lock *(u32 *)(r2 + 4) |= r1 fetch post: ["r1.type=number", "r1.svalue=5", "r1.uvalue=5", - "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", - "s[4...7].type=number", "s[4...7].svalue=7", "s[4...7].uvalue=7"] + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=7", "s[4...7].uvalue=7"] --- test-case: atomic 64-bit OR stack @@ -411,8 +411,8 @@ code: lock *(u64 *)(r2 + 4) |= r1 post: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", - "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", - "s[4...11].type=number", "s[4...11].svalue=7", "s[4...11].uvalue=7"] + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=7", "s[4...11].uvalue=7"] --- test-case: atomic 64-bit OR and fetch stack @@ -425,8 +425,8 @@ code: lock *(u64 *)(r2 + 4) |= r1 fetch post: ["r1.type=number", "r1.svalue=5", "r1.uvalue=5", - "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", - "s[4...11].type=number", "s[4...11].svalue=7", "s[4...11].uvalue=7"] + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=7", "s[4...11].uvalue=7"] --- test-case: atomic 32-bit XOR stack @@ -439,8 +439,8 @@ code: lock *(u32 *)(r2 + 4) ^= r1 post: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", - "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", - "s[4...7].type=number", "s[4...7].svalue=6", "s[4...7].uvalue=6"] + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=6", "s[4...7].uvalue=6"] --- test-case: atomic 32-bit XOR and fetch stack @@ -453,8 +453,8 @@ code: lock *(u32 *)(r2 + 4) ^= r1 fetch post: ["r1.type=number", "r1.svalue=5", "r1.uvalue=5", - "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", - "s[4...7].type=number", "s[4...7].svalue=6", "s[4...7].uvalue=6"] + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=6", "s[4...7].uvalue=6"] --- test-case: atomic 64-bit XOR stack @@ -467,8 +467,8 @@ code: lock *(u64 *)(r2 + 4) ^= r1 post: ["r1.type=number", "r1.svalue=3", "r1.uvalue=3", - "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", - "s[4...11].type=number", "s[4...11].svalue=6", "s[4...11].uvalue=6"] + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=6", "s[4...11].uvalue=6"] --- test-case: atomic 64-bit XOR and fetch stack @@ -481,8 +481,8 @@ code: lock *(u64 *)(r2 + 4) ^= r1 fetch post: ["r1.type=number", "r1.svalue=5", "r1.uvalue=5", - "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", - "s[4...11].type=number", "s[4...11].svalue=6", "s[4...11].uvalue=6"] + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=6", "s[4...11].uvalue=6"] --- test-case: atomic 32-bit XCHG stack @@ -495,8 +495,8 @@ code: lock *(u32 *)(r2 + 4) x= r1 post: ["r1.type=number", "r1.svalue=5", "r1.uvalue=5", - "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", - "s[4...7].type=number", "s[4...7].svalue=2", "s[4...7].uvalue=2"] + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...7].type=number", "s[4...7].svalue=2", "s[4...7].uvalue=2"] --- test-case: atomic 64-bit XCHG stack @@ -509,8 +509,8 @@ code: lock *(u64 *)(r2 + 4) x= r1 post: ["r1.type=number", "r1.svalue=5", "r1.uvalue=5", - "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", - "s[4...11].type=number", "s[4...11].svalue=2", "s[4...11].uvalue=2"] + "r2.type=stack", "r2.stack_offset=0", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue", + "s[4...11].type=number", "s[4...11].svalue=2", "s[4...11].uvalue=2"] --- test-case: atomic 32-bit CMPXCHG stack @@ -554,7 +554,7 @@ code: lock *(u32 *)(r2 + 4) += r1 post: ["r1.type=number", "r1.svalue=2", "r1.uvalue=2", - "r2.type=number", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] + "r2.type=number", "r2.svalue=[1, 2147418112]", "r2.uvalue=[1, 2147418112]", "r2.svalue=r2.uvalue"] messages: - "0: Invalid type (r2.type in {ctx, stack, packet, shared})" diff --git a/test-data/full64.yaml b/test-data/full64.yaml index bd4642c18..b004151d9 100644 --- a/test-data/full64.yaml +++ b/test-data/full64.yaml @@ -174,9 +174,9 @@ code: assume r1 == 1 post: - - r1.type=number - - r1.svalue=1 - - r1.uvalue=1 + - r1.type=number + - r1.svalue=1 + - r1.uvalue=1 --- test-case: equals reg @@ -187,12 +187,12 @@ code: assume r1 == 1 post: - - r1.type=number - - r1.svalue=1 - - r1.uvalue=1 - - r2.type=number - - r2.svalue=1 - - r2.uvalue=1 + - r1.type=number + - r1.svalue=1 + - r1.uvalue=1 + - r2.type=number + - r2.svalue=1 + - r2.uvalue=1 --- test-case: equals positive interval @@ -204,15 +204,15 @@ code: assume r1 == r2 post: - - r1.type=number - - r1.svalue=[1, 2] - - r1.uvalue=[1, 2] - - r1.svalue=r1.uvalue - - r2.type=number - - r2.svalue=[1, 2] - - r2.uvalue=[1, 2] - - r2.svalue=r2.uvalue - - r1.svalue=r2.svalue - - r1.svalue=r2.uvalue - - r1.uvalue=r2.svalue - - r1.uvalue=r2.uvalue + - r1.type=number + - r1.svalue=[1, 2] + - r1.uvalue=[1, 2] + - r1.svalue=r1.uvalue + - r2.type=number + - r2.svalue=[1, 2] + - r2.uvalue=[1, 2] + - r2.svalue=r2.uvalue + - r1.svalue=r2.svalue + - r1.svalue=r2.uvalue + - r1.uvalue=r2.svalue + - r1.uvalue=r2.uvalue diff --git a/test-data/packet.yaml b/test-data/packet.yaml index 57c31c203..682665dee 100644 --- a/test-data/packet.yaml +++ b/test-data/packet.yaml @@ -3,56 +3,46 @@ --- test-case: simple invalid write -pre: [ - "meta_offset=0", - "r1.type=packet", "r1.packet_offset=0", "r1.svalue=[4098, 2147418112]", "r1.uvalue=[4098, 2147418112]", - "r2.type=packet", "r2.packet_offset=[0, 65534]", "packet_size=r2.packet_offset", "packet_size=[0, 65534]" -] +pre: ["meta_offset=0", + "r1.type=packet", "r1.packet_offset=0", "r1.svalue=[4098, 2147418112]", "r1.uvalue=[4098, 2147418112]", + "r2.type=packet", "r2.packet_offset=[0, 65534]", "packet_size=r2.packet_offset", "packet_size=[0, 65534]"] code: : | r4 = 0 *(u64 *)(r1 + 0) = r4 -post: [ - "meta_offset=0", - "r1.type=packet", "r1.packet_offset=0", "r1.svalue=[4098, 2147418112]", "r1.uvalue=[4098, 2147418112]", - "r2.type=packet", "r2.packet_offset=[0, 65534]", "packet_size=r2.packet_offset", - "packet_size=[0, 65534]", "r4.type=number", "r4.svalue=0", "r4.uvalue=0" -] +post: ["meta_offset=0", + "r1.type=packet", "r1.packet_offset=0", "r1.svalue=[4098, 2147418112]", "r1.uvalue=[4098, 2147418112]", + "r2.type=packet", "r2.packet_offset=[0, 65534]", "packet_size=r2.packet_offset", + "packet_size=[0, 65534]", "r4.type=number", "r4.svalue=0", "r4.uvalue=0"] messages: - "1: Upper bound must be at most packet_size (valid_access(r1.offset, width=8) for write)" --- test-case: simple invalid read -pre: [ - "meta_offset=0", - "r1.type=packet", "r1.packet_offset=0", "r1.svalue=[4098, 2147418112]", "r1.uvalue=[4098, 2147418112]", - "r2.type=packet", "r2.packet_offset=[0, 65534]", "packet_size=r2.packet_offset", "packet_size=[0, 65534]" -] +pre: ["meta_offset=0", + "r1.type=packet", "r1.packet_offset=0", "r1.svalue=[4098, 2147418112]", "r1.uvalue=[4098, 2147418112]", + "r2.type=packet", "r2.packet_offset=[0, 65534]", "packet_size=r2.packet_offset", "packet_size=[0, 65534]"] code: : | r4 = *(u64 *)(r1 + 0) -post: [ - "meta_offset=0", - "r1.type=packet", "r1.packet_offset=0", "r1.svalue=[4098, 2147418112]", "r1.uvalue=[4098, 2147418112]", - "r2.type=packet", "r2.packet_offset=[0, 65534]", "packet_size=r2.packet_offset", - "packet_size=[0, 65534]", "r4.type=number" -] +post: ["meta_offset=0", + "r1.type=packet", "r1.packet_offset=0", "r1.svalue=[4098, 2147418112]", "r1.uvalue=[4098, 2147418112]", + "r2.type=packet", "r2.packet_offset=[0, 65534]", "packet_size=r2.packet_offset", + "packet_size=[0, 65534]", "r4.type=number"] messages: - "0: Upper bound must be at most packet_size (valid_access(r1.offset, width=8) for read)" --- test-case: writing 8 bytes when packet size is 4 -pre: [ - "meta_offset=0", - "r1.type=packet", "r1.packet_offset=0", "r1.svalue=[4098, 2147418112]", "r1.uvalue=[4098, 2147418112]", - "r2.type=packet", "r2.packet_offset=[0, 65534]", "packet_size=r2.packet_offset" -] +pre: ["meta_offset=0", + "r1.type=packet", "r1.packet_offset=0", "r1.svalue=[4098, 2147418112]", "r1.uvalue=[4098, 2147418112]", + "r2.type=packet", "r2.packet_offset=[0, 65534]", "packet_size=r2.packet_offset"] code: : | @@ -64,27 +54,23 @@ code: : | r5=r5 -post: [ - "meta_offset=0", - "r1.type=packet", "r1.packet_offset=0", "r1.svalue=[4098, 2147418112]", "r1.uvalue=[4098, 2147418112]", - "r2.type=packet", "r2.packet_offset=[0, 65534]", "packet_size=r2.packet_offset", - "r3.type=packet", "r3.packet_offset=4", "r3.svalue=[4102, 2147418116]", "r3.uvalue=[4102, 2147418116]", - "r3.svalue=r3.uvalue", - "packet_size-r3.packet_offset<=65530", "packet_size=[0, 65534]", - "r3.packet_offset-packet_size<=4", "r3.svalue=r1.svalue+4", "r3.uvalue=r1.svalue+4", - "r2.packet_offset-r3.packet_offset<=65530", "r3.packet_offset-r2.packet_offset<=4" -] +post: ["meta_offset=0", + "r1.type=packet", "r1.packet_offset=0", "r1.svalue=[4098, 2147418112]", "r1.uvalue=[4098, 2147418112]", + "r2.type=packet", "r2.packet_offset=[0, 65534]", "packet_size=r2.packet_offset", + "r3.type=packet", "r3.packet_offset=4", "r3.svalue=[4102, 2147418116]", "r3.uvalue=[4102, 2147418116]", + "r3.svalue=r3.uvalue", + "packet_size-r3.packet_offset<=65530", "packet_size=[0, 65534]", + "r3.packet_offset-packet_size<=4", "r3.svalue=r1.svalue+4", "r3.uvalue=r1.svalue+4", + "r2.packet_offset-r3.packet_offset<=65530", "r3.packet_offset-r2.packet_offset<=4"] messages: - "4: Upper bound must be at most packet_size (valid_access(r1.offset, width=8) for write)" --- test-case: simple valid access -pre: [ - "meta_offset=0", - "r1.type=packet", "r1.packet_offset=0", "r1.svalue=[4098, 2147418112]", "r1.uvalue=[4098, 2147418112]", - "r2.type=packet", "r2.packet_offset=[0, 65534]", "packet_size=r2.packet_offset", -] +pre: ["meta_offset=0", + "r1.type=packet", "r1.packet_offset=0", "r1.svalue=[4098, 2147418112]", "r1.uvalue=[4098, 2147418112]", + "r2.type=packet", "r2.packet_offset=[0, 65534]", "packet_size=r2.packet_offset"] code: : | @@ -96,115 +82,89 @@ code: : | r5=r5 -post: [ - "meta_offset=0", - "r1.type=packet", "r1.packet_offset=0", "r1.svalue=[4098, 2147418112]", "r1.uvalue=[4098, 2147418112]", - "r2.type=packet", "r2.packet_offset=[0, 65534]", "packet_size=r2.packet_offset", - "r3.type=packet", "r3.packet_offset=8", "r3.svalue=[4106, 2147418120]", "r3.uvalue=[4106, 2147418120]", - "packet_size-r3.packet_offset<=65526", "packet_size=[0, 65534]", - "r3.packet_offset-packet_size<=8", "r3.svalue=r1.svalue+8", "r3.uvalue=r1.svalue+8", - "r3.svalue=r3.uvalue", - "r2.packet_offset-r3.packet_offset<=65526", "r3.packet_offset-r2.packet_offset<=8" -] +post: ["meta_offset=0", + "r1.type=packet", "r1.packet_offset=0", "r1.svalue=[4098, 2147418112]", "r1.uvalue=[4098, 2147418112]", + "r2.type=packet", "r2.packet_offset=[0, 65534]", "packet_size=r2.packet_offset", + "r3.type=packet", "r3.packet_offset=8", "r3.svalue=[4106, 2147418120]", "r3.uvalue=[4106, 2147418120]", + "packet_size-r3.packet_offset<=65526", "packet_size=[0, 65534]", + "r3.packet_offset-packet_size<=8", "r3.svalue=r1.svalue+8", "r3.uvalue=r1.svalue+8", + "r3.svalue=r3.uvalue", + "r2.packet_offset-r3.packet_offset<=65526", "r3.packet_offset-r2.packet_offset<=8"] --- test-case: legacy 1 byte packet access imm -pre: [ - "r1.type=number", - "r6.type=ctx", "r6.ctx_offset=0" -] +pre: ["r1.type=number", + "r6.type=ctx", "r6.ctx_offset=0"] code: : | r0 = *(u8 *)skb[23] -post: [ - "r0.type=number", - "r6.type=ctx", "r6.ctx_offset=0" -] +post: ["r0.type=number", + "r6.type=ctx", "r6.ctx_offset=0"] --- test-case: legacy 2 byte packet access imm -pre: [ - "r1.type=number", - "r6.type=ctx", "r6.ctx_offset=0" -] +pre: ["r1.type=number", + "r6.type=ctx", "r6.ctx_offset=0"] code: : | r0 = *(u16 *)skb[23] -post: [ - "r0.type=number", - "r6.type=ctx", "r6.ctx_offset=0" -] +post: ["r0.type=number", + "r6.type=ctx", "r6.ctx_offset=0"] --- test-case: legacy 4 byte packet access imm -pre: [ - "r1.type=number", - "r6.type=ctx", "r6.ctx_offset=0" -] +pre: ["r1.type=number", + "r6.type=ctx", "r6.ctx_offset=0"] code: : | r0 = *(u32 *)skb[23] -post: [ - "r0.type=number", - "r6.type=ctx", "r6.ctx_offset=0" -] +post: ["r0.type=number", + "r6.type=ctx", "r6.ctx_offset=0"] --- test-case: legacy 1 byte packet access reg -pre: [ - "r1.type=number", - "r6.type=ctx", "r6.ctx_offset=0", - "r7.type=number", "r7.svalue=23", "r7.uvalue=23" -] +pre: ["r1.type=number", + "r6.type=ctx", "r6.ctx_offset=0", + "r7.type=number", "r7.svalue=23", "r7.uvalue=23"] code: : | r0 = *(u8 *)skb[r7] -post: [ - "r0.type=number", - "r6.type=ctx", "r6.ctx_offset=0", - "r7.type=number", "r7.svalue=23", "r7.uvalue=23" -] +post: ["r0.type=number", + "r6.type=ctx", "r6.ctx_offset=0", + "r7.type=number", "r7.svalue=23", "r7.uvalue=23"] --- test-case: legacy 2 byte packet access reg -pre: [ - "r1.type=number", - "r6.type=ctx", "r6.ctx_offset=0", - "r7.type=number", "r7.svalue=23", "r7.uvalue=23" -] +pre: ["r1.type=number", + "r6.type=ctx", "r6.ctx_offset=0", + "r7.type=number", "r7.svalue=23", "r7.uvalue=23"] code: : | r0 = *(u16 *)skb[r7] -post: [ - "r0.type=number", - "r6.type=ctx", "r6.ctx_offset=0", - "r7.type=number", "r7.svalue=23", "r7.uvalue=23" -] +post: ["r0.type=number", + "r6.type=ctx", "r6.ctx_offset=0", + "r7.type=number", "r7.svalue=23", "r7.uvalue=23"] --- test-case: legacy 4 byte packet access reg -pre: [ - "r1.type=number", - "r6.type=ctx", "r6.ctx_offset=0", - "r7.type=number", "r7.svalue=23", "r7.uvalue=23" -] +pre: ["r1.type=number", + "r6.type=ctx", "r6.ctx_offset=0", + "r7.type=number", "r7.svalue=23", "r7.uvalue=23"] code: : | r0 = *(u32 *)skb[r7] -post: [ - "r0.type=number", - "r6.type=ctx", "r6.ctx_offset=0", - "r7.type=number", "r7.svalue=23", "r7.uvalue=23" -] +post: ["r0.type=number", + "r6.type=ctx", "r6.ctx_offset=0", + "r7.type=number", "r7.svalue=23", "r7.uvalue=23"] diff --git a/test-data/unsigned.yaml b/test-data/unsigned.yaml index 159aa0a48..d2b9b0521 100644 --- a/test-data/unsigned.yaml +++ b/test-data/unsigned.yaml @@ -360,7 +360,7 @@ messages: test-case: "assume INT_MIN+1 < 0 implies bottom" pre: [] code: - : | + : | r1 = -9223372036854775807ll assume r1 < 0 post: [] @@ -370,7 +370,7 @@ messages: test-case: "assume INT_MIN+1 w< 0 implies bottom" pre: [] code: - : | + : | r1 = -9223372036854775807ll assume w1 < 0 post: [] @@ -380,7 +380,7 @@ messages: test-case: "assume INT_MIN+1 w< 2 nop" pre: [] code: - : | + : | r1 = -9223372036854775807ll assume w1 < 2 post: ["r1.type=number", "r1.svalue=-9223372036854775807", "r1.uvalue=9223372036854775809"] @@ -396,7 +396,7 @@ messages: test-case: "assume [INT32_MIN, -1] w< 0 implies bottom" pre: ["r1.type=number", "r1.svalue=[-2147483648, -1]", "r1.uvalue=[18446744071562067968, 18446744073709551615]"] code: - : | + : | assume w1 < 0 post: [] messages: @@ -405,7 +405,7 @@ messages: test-case: "assume something w< 0 implies bottom" pre: ["r1.type=number", "r1.svalue=-4294967298", "r1.uvalue=18446744069414584318"] code: - : | + : | assume w1 < 0 post: [] messages: @@ -703,11 +703,11 @@ code: r2 = 9223372036854775803ll assume r1 >= r2 post: ["r1.type=number", "r1.svalue=9223372036854775803", "r1.uvalue=9223372036854775803", "r1.svalue=r1.uvalue", - "r2.type=number", "r2.svalue=9223372036854775803", "r2.uvalue=9223372036854775803", - "r2.uvalue-r1.svalue<=0", # missing: r1.svalue-r2.uvalue<=0; probably avoiding overflow in SplitDBM - "r2.svalue-r1.svalue<=0", # missing: r1.svalue-r2.svalue<=0; probably avoiding overflow in SplitDBM - "r2.svalue-r1.uvalue<=0", # missing: r1.uvalue-r2.svalue<=0; probably avoiding overflow in SplitDBM - "r2.uvalue-r1.uvalue<=0"] # missing: r1.uvalue-r2.uvalue<=0; probably avoiding overflow in SplitDBM + "r2.type=number", "r2.svalue=9223372036854775803", "r2.uvalue=9223372036854775803", + "r2.uvalue-r1.svalue<=0", # missing: r1.svalue-r2.uvalue<=0; probably avoiding overflow in SplitDBM + "r2.svalue-r1.svalue<=0", # missing: r1.svalue-r2.svalue<=0; probably avoiding overflow in SplitDBM + "r2.svalue-r1.uvalue<=0", # missing: r1.uvalue-r2.svalue<=0; probably avoiding overflow in SplitDBM + "r2.uvalue-r1.uvalue<=0"] # missing: r1.uvalue-r2.uvalue<=0; probably avoiding overflow in SplitDBM --- test-case: "assume [0, INT32_MAX] w>= INT32_MAX narrows to INT32_MAX" pre: ["r1.type=number", "r1.svalue=[0, 2147483647]", "r1.uvalue=[0, 2147483647]", "r1.svalue=r1.uvalue"] @@ -716,9 +716,9 @@ code: r2 = 2147483647 assume w1 >= r2 post: ["r1.type=number", "r1.svalue=2147483647", "r1.uvalue=2147483647", "r1.svalue=r1.uvalue", - "r2.type=number", "r2.svalue=2147483647", "r2.uvalue=2147483647", - "r2.uvalue-r1.svalue<=0", # missing: r1.svalue-r2.uvalue<=0; probably avoiding overflow in SplitDBM - "r2.uvalue-r1.uvalue<=0"] # missing: r1.uvalue-r2.uvalue<=0; probably avoiding overflow in SplitDBM + "r2.type=number", "r2.svalue=2147483647", "r2.uvalue=2147483647", + "r2.uvalue-r1.svalue<=0", # missing: r1.svalue-r2.uvalue<=0; probably avoiding overflow in SplitDBM + "r2.uvalue-r1.uvalue<=0"] # missing: r1.uvalue-r2.uvalue<=0; probably avoiding overflow in SplitDBM --- test-case: "assume 4294967301 w>= 5 nop" pre: ["r1.type=number", "r1.svalue=4294967301", "r1.uvalue=4294967301"] From ad7a861f165829f807a05db989b4582fa76158f8 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Mon, 13 May 2024 19:51:04 +0300 Subject: [PATCH 187/373] Fix YAML lint errors and warnings (#636) * fix yaml lint errors and warnings * quote "on" --------- Signed-off-by: Elazar Gershuni --- .github/dependabot.yml | 2 +- .github/workflows/build.yml | 3 ++- .github/workflows/codeql-analysis.yml | 3 ++- .github/workflows/coverage.yml | 4 ++-- .github/workflows/validate-yaml.yml | 3 ++- .yamllint.yml | 2 +- test-data/call.yaml | 4 ++-- test-data/callx.yaml | 4 ++-- test-data/loop.yaml | 6 +++--- test-data/parse.yaml | 2 +- test-data/unsigned.yaml | 2 +- test-schema.yaml | 1 + 12 files changed, 20 insertions(+), 16 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index dc618205c..f2be7f8a5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,7 +3,7 @@ # # For documentation on the format of this file, see # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file - +--- version: 2 updates: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 85fb55f56..e063a9ee5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,7 @@ +--- name: CPP CI -on: +"on": pull_request: paths-ignore: - '**.md' diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3a8a2f613..841d3125f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -9,9 +9,10 @@ # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # +--- name: "CodeQL" -on: +"on": push: branches: [main] pull_request: diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 2765caabb..d9ebe3dc6 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,9 +1,9 @@ # Copyright (c) Prevail Verifier contributors. # SPDX-License-Identifier: MIT - +--- name: CPP Code Coverage -on: +"on": push: branches: [main] pull_request: diff --git a/.github/workflows/validate-yaml.yml b/.github/workflows/validate-yaml.yml index 7f7fc048e..591f36b4a 100644 --- a/.github/workflows/validate-yaml.yml +++ b/.github/workflows/validate-yaml.yml @@ -6,9 +6,10 @@ # # For documentation on the syntax of this file, see # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions +--- name: Validate-YAML -on: +"on": push: branches: [main] pull_request: diff --git a/.yamllint.yml b/.yamllint.yml index 9b63738a3..31a22e235 100644 --- a/.yamllint.yml +++ b/.yamllint.yml @@ -3,7 +3,7 @@ # # Configuration file for yamllint. For documentation, see # https://yamllint.readthedocs.io/en/stable/configuration.html - +--- extends: default rules: diff --git a/test-data/call.yaml b/test-data/call.yaml index e7c81c31b..235951b3e 100644 --- a/test-data/call.yaml +++ b/test-data/call.yaml @@ -39,7 +39,7 @@ post: --- test-case: bpf_map_lookup_elem packet key -pre: [ "meta_offset=0", +pre: ["meta_offset=0", "r1.type=map_fd", "r1.map_fd=1", "r2.type=packet", "r2.packet_offset=0", "packet_size=4"] @@ -226,7 +226,7 @@ post: - s[496...511].type=number messages: - - "0: Possible null access (within stack(r3:value_size(r1)))" + - "0: Possible null access (within stack(r3:value_size(r1)))" --- test-case: bpf_map_update_elem with packet value diff --git a/test-data/callx.yaml b/test-data/callx.yaml index 45da0f9a9..44096aefc 100644 --- a/test-data/callx.yaml +++ b/test-data/callx.yaml @@ -47,7 +47,7 @@ post: --- test-case: callx bpf_map_lookup_elem packet key -pre: [ "meta_offset=0", +pre: ["meta_offset=0", "r1.type=map_fd", "r1.map_fd=1", "r2.type=packet", "r2.packet_offset=0", "packet_size=4", "r8.type=number", "r8.svalue=1", "r8.uvalue=1"] @@ -321,7 +321,7 @@ post: - r8.uvalue=2 messages: - - "0: Possible null access (within stack(r3:value_size(r1)))" + - "0: Possible null access (within stack(r3:value_size(r1)))" --- test-case: callx bpf_map_update_elem with packet value diff --git a/test-data/loop.yaml b/test-data/loop.yaml index 643cb4b16..10e5fd116 100644 --- a/test-data/loop.yaml +++ b/test-data/loop.yaml @@ -121,7 +121,7 @@ post: - "pc[1]=r0.uvalue" --- test-case: while loop, eq -#options: ["termination"] +# options: ["termination"] pre: [] code: @@ -143,7 +143,7 @@ post: # - "pc[1]=r0.value" --- test-case: until loop, neq -#options: ["termination"] +# options: ["termination"] pre: [] @@ -182,7 +182,7 @@ post: - "r0.type=number" - "r0.svalue=9" - "r0.uvalue=9" -# - "r0.svalue=r0.uvalue" + # - "r0.svalue=r0.uvalue" - "pc[1]=[1, +oo]" messages: diff --git a/test-data/parse.yaml b/test-data/parse.yaml index 359370453..49278e01b 100644 --- a/test-data/parse.yaml +++ b/test-data/parse.yaml @@ -55,7 +55,7 @@ code: : r1 = 9223372036854775801ll post: ["r1.type=number", "r1.svalue=9223372036854775801", "r1.uvalue=9223372036854775801"] --- -test-case: "parse INT_MAX code" # largest number that does not overflow +test-case: "parse INT_MAX code" # largest number that does not overflow pre: [] code: : r1 = 9223372036854775807ll diff --git a/test-data/unsigned.yaml b/test-data/unsigned.yaml index d2b9b0521..d05033357 100644 --- a/test-data/unsigned.yaml +++ b/test-data/unsigned.yaml @@ -1125,7 +1125,7 @@ test-case: "assume [UINT32_MAX+1, UINT32_MAX+5] w< 2" pre: ["r1.type=number", "r1.uvalue=[4294967296, 4294967300]"] code: : assume w1 < 2 -post: ["r1.type=number", "r1.uvalue=[4294967296, 4294967300]"] # could be narrowed in future +post: ["r1.type=number", "r1.uvalue=[4294967296, 4294967300]"] # could be narrowed in future --- test-case: "assume [1, 4] w< INT32_MAX+5" pre: ["r1.type=number", "r1.uvalue=[1, 4]", diff --git a/test-schema.yaml b/test-schema.yaml index f38eed1ea..39ccd0449 100644 --- a/test-schema.yaml +++ b/test-schema.yaml @@ -1,5 +1,6 @@ # Copyright (c) Prevail Verifier contributors. # SPDX-License-Identifier: MIT +--- id: https://spec.openapis.org/oas/4.0/schema/2021-08-12 $schema: http://json-schema.org/draft-04/schema# description: Validation schema for ebpf-verifier tests From f7f871da5197b337bb04f4acdb1a166e8e5ce0c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 16:51:32 +0000 Subject: [PATCH 188/373] Bump external/bpf_conformance from `7c2ef8b` to `ce95710` Bumps [external/bpf_conformance](https://github.com/Alan-Jowett/bpf_conformance) from `7c2ef8b` to `ce95710`. - [Release notes](https://github.com/Alan-Jowett/bpf_conformance/releases) - [Commits](https://github.com/Alan-Jowett/bpf_conformance/compare/7c2ef8b77679089f3949cf48137ec2246358fb3f...ce9571054cb6e60ed88bddcfc8cbff19dc31dab6) --- updated-dependencies: - dependency-name: external/bpf_conformance dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/bpf_conformance | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/bpf_conformance b/external/bpf_conformance index 7c2ef8b77..ce9571054 160000 --- a/external/bpf_conformance +++ b/external/bpf_conformance @@ -1 +1 @@ -Subproject commit 7c2ef8b77679089f3949cf48137ec2246358fb3f +Subproject commit ce9571054cb6e60ed88bddcfc8cbff19dc31dab6 From e3e1efa3433d47afc08c27efdf2cc5fd71a54f8b Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Wed, 15 May 2024 00:50:08 -0600 Subject: [PATCH 189/373] Support multiple programs per section (#638) Signed-off-by: Dave Thaler --- src/asm_files.cpp | 179 +++++++++++++++++++++------------- src/main/check.cpp | 36 +++++-- src/spec_type_descriptors.hpp | 3 +- src/test/test_marshal.cpp | 20 ++-- src/test/test_verify.cpp | 30 +++++- 5 files changed, 176 insertions(+), 92 deletions(-) diff --git a/src/asm_files.cpp b/src/asm_files.cpp index b05928991..274a71c16 100644 --- a/src/asm_files.cpp +++ b/src/asm_files.cpp @@ -21,15 +21,20 @@ using std::string; using std::vector; template -static vector vector_of(const ELFIO::section& sec) { - auto data = sec.get_data(); - auto size = sec.get_size(); +static vector vector_of(const char* data, ELFIO::Elf_Xword size) { if ((size % sizeof(T) != 0) || size > UINT32_MAX || !data) { throw std::runtime_error("Invalid argument to vector_of"); } return {(T*)data, (T*)(data + size)}; } +template +static vector vector_of(const ELFIO::section& sec) { + auto data = sec.get_data(); + auto size = sec.get_size(); + return vector_of(data, size); +} + int create_map_crab(const EbpfMapType& map_type, uint32_t key_size, uint32_t value_size, uint32_t max_entries, ebpf_verifier_options_t options) { EquivalenceKey equiv{map_type.value_type, key_size, value_size, map_type.is_array ? max_entries : 0}; if (!global_program_info->cache.count(equiv)) { @@ -66,7 +71,7 @@ std::tuple get_symbol_name_and_section_index(ELFIO::con return {symbol_name, section_index}; } -ELFIO::Elf64_Addr get_value(ELFIO::const_symbol_section_accessor& symbols, ELFIO::Elf_Word index) { +std::tuple get_value(ELFIO::const_symbol_section_accessor& symbols, ELFIO::Elf_Word index) { string symbol_name; ELFIO::Elf64_Addr value{}; ELFIO::Elf_Xword size{}; @@ -75,7 +80,7 @@ ELFIO::Elf64_Addr get_value(ELFIO::const_symbol_section_accessor& symbols, ELFIO ELFIO::Elf_Half section_index{}; unsigned char other{}; symbols.get_symbol(index, symbol_name, value, size, bind, type, section_index, other); - return value; + return {value, type}; } // parse_maps_sections processes all maps sections in the provided ELF file by calling the platform-specific maps @@ -123,6 +128,30 @@ vector read_elf(const std::string& path, const std::string& desired throw std::runtime_error(string("Can't process ELF file ") + path); } +std::tuple get_program_name_and_size(ELFIO::section& sec, ELFIO::Elf_Xword start, ELFIO::const_symbol_section_accessor& symbols) { + ELFIO::Elf_Xword symbol_count = symbols.get_symbols_num(); + ELFIO::Elf_Half section_index = sec.get_index(); + string program_name = sec.get_name(); + ELFIO::Elf_Xword size = sec.get_size() - start; + for (ELFIO::Elf_Xword index = 0; index < symbol_count; index++) { + auto [symbol_name, symbol_section_index] = get_symbol_name_and_section_index(symbols, index); + if (symbol_section_index == section_index && !symbol_name.empty()) { + auto [relocation_offset, relocation_type] = get_value(symbols, index); + if (relocation_type != ELFIO::STT_FUNC) { + continue; + } + if (relocation_offset == start) { + // We found the program name for this program. + program_name = symbol_name; + } else if (relocation_offset > start && relocation_offset < start + size) { + // We found another program that follows, so truncate the size of this program. + size = relocation_offset - start; + } + } + } + return {program_name, size}; +} + vector 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; @@ -225,80 +254,90 @@ vector read_elf(std::istream& input_stream, const std::string& path if ((section->get_size() == 0) || (section->get_data() == nullptr)) continue; info.type = platform->get_program_type(name, path); - raw_program prog{path, name, vector_of(*section), info}; - auto prelocs = reader.sections[string(".rel") + name]; - if (!prelocs) - prelocs = reader.sections[string(".rela") + name]; - - if (prelocs) { - if (!prelocs->get_data()) { - throw std::runtime_error("Malformed relocation data"); - } - ELFIO::const_relocation_section_accessor reloc{reader, prelocs}; - - // Fetch and store relocation count locally to permit static - // analysis tools to correctly reason about the code below. - ELFIO::Elf_Xword relocation_count = reloc.get_entries_num(); - - for (ELFIO::Elf_Xword i = 0; i < relocation_count; i++) { - ELFIO::Elf64_Addr offset{}; - ELFIO::Elf_Word index{}; - unsigned type{}; - ELFIO::Elf_Sxword addend{}; - if (!reloc.get_entry(i, offset, index, type, addend)) { - continue; - } - if ((offset / sizeof(ebpf_inst)) >= prog.prog.size()) { - throw std::runtime_error("Invalid relocation data"); - } - ebpf_inst& inst = prog.prog[offset / sizeof(ebpf_inst)]; - auto [symbol_name, symbol_section_index] = get_symbol_name_and_section_index(symbols, index); + for (ELFIO::Elf_Xword program_offset = 0; program_offset < section->get_size();) { + auto [program_name, program_size] = get_program_name_and_size(*section, program_offset, symbols); + raw_program prog{path, name, program_name, vector_of(section->get_data() + program_offset, program_size), info}; + auto prelocs = reader.sections[string(".rel") + name]; + if (!prelocs) + prelocs = reader.sections[string(".rela") + name]; - // Only perform relocation for symbols located in the maps section. - if (!map_section_indices.contains(symbol_section_index)) { - std::string unresolved_symbol = "Unresolved external symbol " + symbol_name + - " in section " + name + " at location " + std::to_string(offset / sizeof(ebpf_inst)); - unresolved_symbols.push_back(unresolved_symbol); - continue; + if (prelocs) { + if (!prelocs->get_data()) { + throw std::runtime_error("Malformed relocation data"); } + ELFIO::const_relocation_section_accessor reloc{reader, prelocs}; + + // Fetch and store relocation count locally to permit static + // analysis tools to correctly reason about the code below. + ELFIO::Elf_Xword relocation_count = reloc.get_entries_num(); + + for (ELFIO::Elf_Xword i = 0; i < relocation_count; i++) { + ELFIO::Elf64_Addr offset{}; + ELFIO::Elf_Word index{}; + unsigned type{}; + ELFIO::Elf_Sxword addend{}; + if (!reloc.get_entry(i, offset, index, type, addend)) { + continue; + } + if (offset < program_offset || offset >= program_offset + program_size) { + // Relocation is not for this program. + continue; + } + offset -= program_offset; + if ((offset / sizeof(ebpf_inst)) >= prog.prog.size()) { + throw std::runtime_error("Invalid relocation data"); + } + ebpf_inst& inst = prog.prog[offset / sizeof(ebpf_inst)]; - // Only permit loading the address of the map. - if ((inst.opcode & INST_CLS_MASK) != INST_CLS_LD) { - throw std::runtime_error("Illegal operation on symbol " + symbol_name + - " at location " + std::to_string(offset / sizeof(ebpf_inst))); - } - inst.src = 1; // magic number for LoadFd - - // Relocation value is an offset into the "maps" or ".maps" section. - size_t relocation_offset = get_value(symbols, index); - if (map_record_size_or_map_offsets.index() == 0) { - // The older maps section format uses a single map_record_size value, so we can - // calculate the map descriptor index directly. - size_t reloc_value = relocation_offset / std::get<0>(map_record_size_or_map_offsets); - if (reloc_value >= info.map_descriptors.size()) { - throw std::runtime_error("Bad reloc value (" + std::to_string(reloc_value) + "). " - + "Make sure to compile with -O2."); + auto [symbol_name, symbol_section_index] = get_symbol_name_and_section_index(symbols, index); + + // Only perform relocation for symbols located in the maps section. + if (!map_section_indices.contains(symbol_section_index)) { + std::string unresolved_symbol = "Unresolved external symbol " + symbol_name + + " in section " + name + " at location " + std::to_string(offset / sizeof(ebpf_inst)); + unresolved_symbols.push_back(unresolved_symbol); + continue; } - inst.imm = info.map_descriptors.at(reloc_value).original_fd; - } - else { - // The newer .maps section format uses a variable-length map descriptor array, - // so we need to look up the map descriptor index in a map. - auto& map_descriptors_offsets = std::get<1>(map_record_size_or_map_offsets); - auto it = map_descriptors_offsets.find(symbol_name); - - if (it == map_descriptors_offsets.end()) { - throw std::runtime_error("Bad reloc value (" + std::to_string(index) + "). " - + "Make sure to compile with -O2."); + // Only permit loading the address of the map. + if ((inst.opcode & INST_CLS_MASK) != INST_CLS_LD) { + throw std::runtime_error("Illegal operation on symbol " + symbol_name + + " at location " + std::to_string(offset / sizeof(ebpf_inst))); + } + inst.src = 1; // magic number for LoadFd + + // Relocation value is an offset into the "maps" or ".maps" section. + auto [relocation_offset, relocation_type] = get_value(symbols, index); + if (map_record_size_or_map_offsets.index() == 0) { + // The older maps section format uses a single map_record_size value, so we can + // calculate the map descriptor index directly. + size_t reloc_value = relocation_offset / std::get<0>(map_record_size_or_map_offsets); + if (reloc_value >= info.map_descriptors.size()) { + throw std::runtime_error("Bad reloc value (" + std::to_string(reloc_value) + "). " + + "Make sure to compile with -O2."); + } + + inst.imm = info.map_descriptors.at(reloc_value).original_fd; + } + else { + // The newer .maps section format uses a variable-length map descriptor array, + // so we need to look up the map descriptor index in a map. + auto& map_descriptors_offsets = std::get<1>(map_record_size_or_map_offsets); + auto it = map_descriptors_offsets.find(symbol_name); + + if (it == map_descriptors_offsets.end()) { + throw std::runtime_error("Bad reloc value (" + std::to_string(index) + "). " + + "Make sure to compile with -O2."); + } + inst.imm = info.map_descriptors.at(it->second).original_fd; } - inst.imm = info.map_descriptors.at(it->second).original_fd; } } + prog.line_info.resize(prog.prog.size()); + res.push_back(prog); + program_offset += program_size; } - prog.line_info.resize(prog.prog.size()); - res.push_back(prog); } // Below, only relocations of symbols located in the map sections are allowed, @@ -314,7 +353,7 @@ vector read_elf(std::istream& input_stream, const std::string& path if (btf != nullptr && btf_ext != nullptr) { std::map segment_to_program; for (auto& program : res) { - segment_to_program.insert({program.section, program}); + segment_to_program.insert({program.function_name, program}); } auto visitor = [&](const std::string& section, uint32_t instruction_offset, const std::string& file_name, diff --git a/src/main/check.cpp b/src/main/check.cpp index 902eb3b96..25e571293 100644 --- a/src/main/check.cpp +++ b/src/main/check.cpp @@ -69,6 +69,19 @@ static std::vector get_string_vector(std::string list) { return string_vector; } +static std::optional find_program(vector& raw_progs, std::string desired_program) { + if (desired_program.empty() && raw_progs.size() == 1) { + // Select the last program section. + return raw_progs.back(); + } + for (raw_program current_program : raw_progs) { + if (current_program.function_name == desired_program) { + return current_program; + } + } + return {}; +} + int main(int argc, char** argv) { // Always call ebpf_verifier_clear_thread_local_state on scope exit. at_scope_exit clear_thread_local_state; @@ -86,9 +99,13 @@ int main(int argc, char** argv) { std::string desired_section; - app.add_option("section", desired_section, "Section to analyze")->type_name("SECTION"); + app.add_option("--section,section", desired_section, "Section to analyze")->type_name("SECTION"); + + std::string desired_program; + + app.add_option("--function,function", desired_program, "Function to analyze")->type_name("FUNCTION"); bool list = false; - app.add_flag("-l", list, "List sections"); + app.add_flag("-l", list, "List programs"); std::string domain = "zoneCrab"; std::set doms{"stats", "linux", "zoneCrab", "cfg"}; @@ -184,25 +201,24 @@ int main(int argc, char** argv) { return 1; } - if (list || raw_progs.size() != 1) { + std::optional found_prog = find_program(raw_progs, desired_program); + if (list || !found_prog) { if (!list) { - std::cout << "please specify a section\n"; - std::cout << "available sections:\n"; + std::cout << "please specify a program\n"; + std::cout << "available programs:\n"; } if (!desired_section.empty() && raw_progs.empty()) { - // We could not find the desired section, so get the full list + // We could not find the desired program, so get the full list // of possibilities. raw_progs = read_elf(filename, string(), &ebpf_verifier_options, &platform); } for (const raw_program& raw_prog : raw_progs) { - std::cout << raw_prog.section << " "; + std::cout << "section=" << raw_prog.section_name << " function=" << raw_prog.function_name << std::endl; } std::cout << "\n"; return list ? 0 : 64; } - - // Select the last program section. - raw_program raw_prog = raw_progs.back(); + raw_program raw_prog = *found_prog; // Convert the raw program section to a set of instructions. std::variant prog_or_error = unmarshal(raw_prog); diff --git a/src/spec_type_descriptors.hpp b/src/spec_type_descriptors.hpp index cad589fa7..50576ac92 100644 --- a/src/spec_type_descriptors.hpp +++ b/src/spec_type_descriptors.hpp @@ -66,7 +66,8 @@ struct btf_line_info_t { struct raw_program { std::string filename{}; - std::string section{}; + std::string section_name{}; + std::string function_name{}; std::vector prog{}; program_info info{}; std::vector line_info{}; diff --git a/src/test/test_marshal.cpp b/src/test/test_marshal.cpp index 06508d25a..cf7c84f6f 100644 --- a/src/test/test_marshal.cpp +++ b/src/test/test_marshal.cpp @@ -207,7 +207,7 @@ static const ebpf_instruction_template_t instruction_template[] = { static void check_unmarshal_succeed(const ebpf_inst& ins, const ebpf_platform_t& platform = g_ebpf_platform_linux) { program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; const ebpf_inst exit{.opcode = INST_OP_EXIT}; - InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", {ins, exit, exit}, info})); + InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", "", {ins, exit, exit}, info})); REQUIRE(parsed.size() == 3); } @@ -215,7 +215,7 @@ static void check_unmarshal_succeed(const ebpf_inst& ins, const ebpf_platform_t& static void check_unmarshal_succeed(ebpf_inst inst1, ebpf_inst inst2, const ebpf_platform_t& platform = g_ebpf_platform_linux) { program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; const ebpf_inst exit{.opcode = INST_OP_EXIT}; - InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", {inst1, inst2, exit, exit}, info})); + InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", "", {inst1, inst2, exit, exit}, info})); REQUIRE(parsed.size() == 3); } @@ -225,7 +225,7 @@ static void compare_unmarshal_marshal(const ebpf_inst& ins, const ebpf_inst& exp program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; const ebpf_inst exit{.opcode = INST_OP_EXIT}; - InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", {ins, exit, exit}, info})); + InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", "", {ins, exit, exit}, info})); REQUIRE(parsed.size() == 3); auto [_, single, _2] = parsed.front(); (void)_; // unused @@ -242,7 +242,7 @@ static void compare_unmarshal_marshal(const ebpf_inst& ins1, const ebpf_inst& in program_info info{.platform = &g_ebpf_platform_linux, .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")}; const ebpf_inst exit{.opcode = INST_OP_EXIT}; - InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", {ins1, ins2, exit, exit}, info})); + InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", "", {ins1, ins2, exit, exit}, info})); REQUIRE(parsed.size() == 3); auto [_, single, _2] = parsed.front(); (void)_; // unused @@ -260,7 +260,7 @@ static void compare_unmarshal_marshal(const ebpf_inst& ins1, const ebpf_inst& in program_info info{.platform = &g_ebpf_platform_linux, .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")}; const ebpf_inst exit{.opcode = INST_OP_EXIT}; - InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", {ins1, ins2, exit, exit}, info})); + InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", "", {ins1, ins2, exit, exit}, info})); REQUIRE(parsed.size() == 3); auto [_, single, _2] = parsed.front(); (void)_; // unused @@ -277,7 +277,7 @@ static void compare_unmarshal_marshal(const ebpf_inst& ins1, const ebpf_inst& in // we get the original. static void compare_marshal_unmarshal(const Instruction& ins, bool double_cmd = false, const ebpf_platform_t& platform = g_ebpf_platform_linux) { program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; - InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", marshal(ins, 0), info})); + InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", "", marshal(ins, 0), info})); REQUIRE(parsed.size() == 1); auto [_, single, _2] = parsed.back(); (void)_; // unused @@ -287,14 +287,14 @@ static void compare_marshal_unmarshal(const Instruction& ins, bool double_cmd = static void check_marshal_unmarshal_fail(const Instruction& ins, std::string expected_error_message, const ebpf_platform_t& platform = g_ebpf_platform_linux) { program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; - std::string error_message = std::get(unmarshal(raw_program{"", "", marshal(ins, 0), info})); + std::string error_message = std::get(unmarshal(raw_program{"", "", "", marshal(ins, 0), info})); REQUIRE(error_message == expected_error_message); } static void check_unmarshal_fail(ebpf_inst inst, std::string expected_error_message, const ebpf_platform_t& platform = g_ebpf_platform_linux) { program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; std::vector insns = {inst}; - auto result = unmarshal(raw_program{"", "", insns, info}); + auto result = unmarshal(raw_program{"", "", "", insns, info}); REQUIRE(std::holds_alternative(result)); std::string error_message = std::get(result); REQUIRE(error_message == expected_error_message); @@ -304,7 +304,7 @@ static void check_unmarshal_fail_goto(ebpf_inst inst, const std::string& expecte program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; const ebpf_inst exit{.opcode = INST_OP_EXIT}; std::vector insns{inst, exit, exit}; - auto result = unmarshal(raw_program{"", "", insns, info}); + auto result = unmarshal(raw_program{"", "", "", insns, info}); REQUIRE(std::holds_alternative(result)); std::string error_message = std::get(result); REQUIRE(error_message == expected_error_message); @@ -314,7 +314,7 @@ static void check_unmarshal_fail_goto(ebpf_inst inst, const std::string& expecte static void check_unmarshal_fail(ebpf_inst inst1, ebpf_inst inst2, std::string expected_error_message, const ebpf_platform_t& platform = g_ebpf_platform_linux) { program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; std::vector insns = {inst1, inst2}; - auto result = unmarshal(raw_program{"", "", insns, info}); + auto result = unmarshal(raw_program{"", "", "", insns, info}); REQUIRE(std::holds_alternative(result)); std::string error_message = std::get(result); REQUIRE(error_message == expected_error_message); diff --git a/src/test/test_verify.cpp b/src/test/test_verify.cpp index 817744710..e5303871e 100644 --- a/src/test/test_verify.cpp +++ b/src/test/test_verify.cpp @@ -32,6 +32,7 @@ FAIL_LOAD_ELF("invalid", "badsymsize.o", "xdp_redirect_map") FAIL_UNMARSHAL("build", "wronghelper.o", "xdp") FAIL_UNMARSHAL("invalid", "invalid-lddw.o", ".text") +// Verify a section with only one program in it. #define VERIFY_SECTION(dirname, filename, sectionname, options, platform, pass) \ do { \ auto raw_progs = read_elf("ebpf-samples/" dirname "/" filename, sectionname, nullptr, platform); \ @@ -47,11 +48,34 @@ FAIL_UNMARSHAL("invalid", "invalid-lddw.o", ".text") REQUIRE(!res); \ } while (0) +// Verify a program in a section that may have multiple programs in it. +#define VERIFY_PROGRAM(dirname, filename, section_name, program_name, options, platform, pass) \ + do { \ + auto raw_progs = read_elf("ebpf-samples/" dirname "/" filename, section_name, nullptr, platform); \ + for (auto& raw_prog : raw_progs) { \ + if (raw_prog.function_name == program_name) { \ + std::variant prog_or_error = unmarshal(raw_prog); \ + REQUIRE(std::holds_alternative(prog_or_error)); \ + auto& prog = std::get(prog_or_error); \ + bool res = ebpf_verify_program(std::cout, prog, raw_prog.info, options, nullptr); \ + if (pass) \ + REQUIRE(res); \ + else \ + REQUIRE(!res); \ + } \ + } \ + } while (0) + #define TEST_SECTION(project, filename, section) \ TEST_CASE("./check ebpf-samples/" project "/" filename " " section, "[verify][samples][" project "]") { \ VERIFY_SECTION(project, filename, section, nullptr, &g_ebpf_platform_linux, true); \ } +#define TEST_PROGRAM(project, filename, section_name, program_name) \ + TEST_CASE("./check ebpf-samples/" project "/" filename " " program_name, "[verify][samples][" project "]") { \ + VERIFY_PROGRAM(project, filename, section_name, program_name, nullptr, &g_ebpf_platform_linux, true); \ + } + #define TEST_SECTION_REJECT(project, filename, section) \ TEST_CASE("./check ebpf-samples/" project "/" filename " " section, "[verify][samples][" project "]") { \ VERIFY_SECTION(project, filename, section, nullptr, &g_ebpf_platform_linux, false); \ @@ -482,7 +506,11 @@ TEST_SECTION("build", "map_in_map_legacy.o", ".text") TEST_SECTION("build", "twomaps.o", ".text"); TEST_SECTION("build", "twostackvars.o", ".text"); TEST_SECTION("build", "twotypes.o", ".text"); -TEST_SECTION("build", "prog_array.o", ".text"); +TEST_PROGRAM("build", "prog_array.o", ".text", "func"); +TEST_PROGRAM("build", "prog_array.o", ".text", "func0"); +TEST_PROGRAM("build", "prog_array.o", ".text", "func1"); +TEST_PROGRAM("build", "prog_array.o", ".text", "func2"); +TEST_PROGRAM("build", "prog_array.o", ".text", "func3"); // Test some programs that ought to fail verification. TEST_SECTION_REJECT("build", "badhelpercall.o", ".text") From 97a55ddba6079431455d500d4df469b044373e99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 18 May 2024 16:47:00 +0000 Subject: [PATCH 190/373] Bump ebpf-samples from `5d65552` to `b6e7d26` Bumps [ebpf-samples](https://github.com/vbpf/ebpf-samples) from `5d65552` to `b6e7d26`. - [Release notes](https://github.com/vbpf/ebpf-samples/releases) - [Commits](https://github.com/vbpf/ebpf-samples/compare/5d6555274119147c6ea69866712db3e4fa964423...b6e7d2618359f605cdde7771b713d2cd74988c0d) --- updated-dependencies: - dependency-name: ebpf-samples dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- ebpf-samples | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebpf-samples b/ebpf-samples index 5d6555274..b6e7d2618 160000 --- a/ebpf-samples +++ b/ebpf-samples @@ -1 +1 @@ -Subproject commit 5d6555274119147c6ea69866712db3e4fa964423 +Subproject commit b6e7d2618359f605cdde7771b713d2cd74988c0d From 01d26e5c359e380fe7f3f0561ac9e877bc1f9ba3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 18 May 2024 16:46:58 +0000 Subject: [PATCH 191/373] Bump external/libbtf from `5ae35c7` to `ab8d59b` Bumps [external/libbtf](https://github.com/Alan-Jowett/libbtf) from `5ae35c7` to `ab8d59b`. - [Release notes](https://github.com/Alan-Jowett/libbtf/releases) - [Commits](https://github.com/Alan-Jowett/libbtf/compare/5ae35c7f3495d2aea9f3dad911acc2505acc2747...ab8d59bf9e2ca1ec3b9ab0f29996ae24cfd4c79a) --- updated-dependencies: - dependency-name: external/libbtf dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- external/libbtf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/libbtf b/external/libbtf index 5ae35c7f3..ab8d59bf9 160000 --- a/external/libbtf +++ b/external/libbtf @@ -1 +1 @@ -Subproject commit 5ae35c7f3495d2aea9f3dad911acc2505acc2747 +Subproject commit ab8d59bf9e2ca1ec3b9ab0f29996ae24cfd4c79a From df3418141c99bf1fe48a3b038cf0870734c5250b Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Thu, 23 May 2024 11:07:35 -0700 Subject: [PATCH 192/373] Fix BTF line info for sections with multiple programs (#642) Signed-off-by: Dave Thaler --- src/asm_files.cpp | 27 ++++++++++++--------------- src/spec_type_descriptors.hpp | 1 + src/test/test_marshal.cpp | 20 ++++++++++---------- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/asm_files.cpp b/src/asm_files.cpp index 274a71c16..bf6cd4b00 100644 --- a/src/asm_files.cpp +++ b/src/asm_files.cpp @@ -257,7 +257,7 @@ vector read_elf(std::istream& input_stream, const std::string& path for (ELFIO::Elf_Xword program_offset = 0; program_offset < section->get_size();) { auto [program_name, program_size] = get_program_name_and_size(*section, program_offset, symbols); - raw_program prog{path, name, program_name, vector_of(section->get_data() + program_offset, program_size), info}; + raw_program prog{path, name, program_offset, program_name, vector_of(section->get_data() + program_offset, program_size), info}; auto prelocs = reader.sections[string(".rel") + name]; if (!prelocs) prelocs = reader.sections[string(".rela") + name]; @@ -351,28 +351,25 @@ vector read_elf(std::istream& input_stream, const std::string& path } if (btf != nullptr && btf_ext != nullptr) { - std::map segment_to_program; - for (auto& program : res) { - segment_to_program.insert({program.function_name, program}); - } - auto visitor = [&](const std::string& section, uint32_t instruction_offset, const std::string& file_name, const std::string& source, uint32_t line_number, uint32_t column_number) { - auto program_iter = segment_to_program.find(section); - if (program_iter == segment_to_program.end()) { - return; - } - auto& program = program_iter->second; - if ((instruction_offset / sizeof(ebpf_inst)) >= program.line_info.size()) { - throw std::runtime_error("Invalid BTF data"); + for (auto& program : res) { + if ((program.section_name == section) && (instruction_offset >= program.insn_off) && + (instruction_offset < program.insn_off + program.prog.size() * sizeof(ebpf_inst))) { + size_t inst_index = (instruction_offset - program.insn_off) / sizeof(ebpf_inst); + if (inst_index >= program.line_info.size()) { + throw std::runtime_error("Invalid BTF data"); + } + program.line_info[inst_index] = {file_name, source, line_number, column_number}; + return; + } } - program.line_info[instruction_offset / sizeof(ebpf_inst)] = {file_name, source, line_number, column_number}; }; libbtf::btf_parse_line_information(vector_of(*btf), vector_of(*btf_ext), visitor); // BTF doesn't include line info for every instruction, only on the first instruction per source line. - for (auto& [name, program] : segment_to_program) { + for (auto& program : res) { for (size_t i = 1; i < program.line_info.size(); i++) { // If the previous PC has line info, copy it. if ((program.line_info[i].line_number == 0) && (program.line_info[i - 1].line_number != 0)) { diff --git a/src/spec_type_descriptors.hpp b/src/spec_type_descriptors.hpp index 50576ac92..b827f17db 100644 --- a/src/spec_type_descriptors.hpp +++ b/src/spec_type_descriptors.hpp @@ -67,6 +67,7 @@ struct btf_line_info_t { struct raw_program { std::string filename{}; std::string section_name{}; + uint32_t insn_off{}; // Byte offset in section of first instruction in this program. std::string function_name{}; std::vector prog{}; program_info info{}; diff --git a/src/test/test_marshal.cpp b/src/test/test_marshal.cpp index cf7c84f6f..d1babcaf3 100644 --- a/src/test/test_marshal.cpp +++ b/src/test/test_marshal.cpp @@ -207,7 +207,7 @@ static const ebpf_instruction_template_t instruction_template[] = { static void check_unmarshal_succeed(const ebpf_inst& ins, const ebpf_platform_t& platform = g_ebpf_platform_linux) { program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; const ebpf_inst exit{.opcode = INST_OP_EXIT}; - InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", "", {ins, exit, exit}, info})); + InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", 0, "", {ins, exit, exit}, info})); REQUIRE(parsed.size() == 3); } @@ -215,7 +215,7 @@ static void check_unmarshal_succeed(const ebpf_inst& ins, const ebpf_platform_t& static void check_unmarshal_succeed(ebpf_inst inst1, ebpf_inst inst2, const ebpf_platform_t& platform = g_ebpf_platform_linux) { program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; const ebpf_inst exit{.opcode = INST_OP_EXIT}; - InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", "", {inst1, inst2, exit, exit}, info})); + InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", 0, "", {inst1, inst2, exit, exit}, info})); REQUIRE(parsed.size() == 3); } @@ -225,7 +225,7 @@ static void compare_unmarshal_marshal(const ebpf_inst& ins, const ebpf_inst& exp program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; const ebpf_inst exit{.opcode = INST_OP_EXIT}; - InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", "", {ins, exit, exit}, info})); + InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", 0, "", {ins, exit, exit}, info})); REQUIRE(parsed.size() == 3); auto [_, single, _2] = parsed.front(); (void)_; // unused @@ -242,7 +242,7 @@ static void compare_unmarshal_marshal(const ebpf_inst& ins1, const ebpf_inst& in program_info info{.platform = &g_ebpf_platform_linux, .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")}; const ebpf_inst exit{.opcode = INST_OP_EXIT}; - InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", "", {ins1, ins2, exit, exit}, info})); + InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", 0, "", {ins1, ins2, exit, exit}, info})); REQUIRE(parsed.size() == 3); auto [_, single, _2] = parsed.front(); (void)_; // unused @@ -260,7 +260,7 @@ static void compare_unmarshal_marshal(const ebpf_inst& ins1, const ebpf_inst& in program_info info{.platform = &g_ebpf_platform_linux, .type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")}; const ebpf_inst exit{.opcode = INST_OP_EXIT}; - InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", "", {ins1, ins2, exit, exit}, info})); + InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", 0, "", {ins1, ins2, exit, exit}, info})); REQUIRE(parsed.size() == 3); auto [_, single, _2] = parsed.front(); (void)_; // unused @@ -277,7 +277,7 @@ static void compare_unmarshal_marshal(const ebpf_inst& ins1, const ebpf_inst& in // we get the original. static void compare_marshal_unmarshal(const Instruction& ins, bool double_cmd = false, const ebpf_platform_t& platform = g_ebpf_platform_linux) { program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; - InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", "", marshal(ins, 0), info})); + InstructionSeq parsed = std::get(unmarshal(raw_program{"", "", 0, "", marshal(ins, 0), info})); REQUIRE(parsed.size() == 1); auto [_, single, _2] = parsed.back(); (void)_; // unused @@ -287,14 +287,14 @@ static void compare_marshal_unmarshal(const Instruction& ins, bool double_cmd = static void check_marshal_unmarshal_fail(const Instruction& ins, std::string expected_error_message, const ebpf_platform_t& platform = g_ebpf_platform_linux) { program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; - std::string error_message = std::get(unmarshal(raw_program{"", "", "", marshal(ins, 0), info})); + std::string error_message = std::get(unmarshal(raw_program{"", "", 0, "", marshal(ins, 0), info})); REQUIRE(error_message == expected_error_message); } static void check_unmarshal_fail(ebpf_inst inst, std::string expected_error_message, const ebpf_platform_t& platform = g_ebpf_platform_linux) { program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; std::vector insns = {inst}; - auto result = unmarshal(raw_program{"", "", "", insns, info}); + auto result = unmarshal(raw_program{"", "", 0, "", insns, info}); REQUIRE(std::holds_alternative(result)); std::string error_message = std::get(result); REQUIRE(error_message == expected_error_message); @@ -304,7 +304,7 @@ static void check_unmarshal_fail_goto(ebpf_inst inst, const std::string& expecte program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; const ebpf_inst exit{.opcode = INST_OP_EXIT}; std::vector insns{inst, exit, exit}; - auto result = unmarshal(raw_program{"", "", "", insns, info}); + auto result = unmarshal(raw_program{"", "", 0, "", insns, info}); REQUIRE(std::holds_alternative(result)); std::string error_message = std::get(result); REQUIRE(error_message == expected_error_message); @@ -314,7 +314,7 @@ static void check_unmarshal_fail_goto(ebpf_inst inst, const std::string& expecte static void check_unmarshal_fail(ebpf_inst inst1, ebpf_inst inst2, std::string expected_error_message, const ebpf_platform_t& platform = g_ebpf_platform_linux) { program_info info{.platform = &platform, .type = platform.get_program_type("unspec", "unspec")}; std::vector insns = {inst1, inst2}; - auto result = unmarshal(raw_program{"", "", "", insns, info}); + auto result = unmarshal(raw_program{"", "", 0, "", insns, info}); REQUIRE(std::holds_alternative(result)); std::string error_message = std::get(result); REQUIRE(error_message == expected_error_message); From 2018df951bae707437441ccdad21961857274675 Mon Sep 17 00:00:00 2001 From: Elazar Gershuni Date: Tue, 28 May 2024 23:31:48 +0300 Subject: [PATCH 193/373] bump CLI11 to v2.4.2 and organize usage (#644) * Update CLI11 to v2.4.2 (from v1.9.1). * Use its built-in validator * Reorganize options into categories * Add invertible flags * --include_groups and --exclude_groups can now use space separator * Update README.md --------- Signed-off-by: Elazar Gershuni --- README.md | 45 +- external/CLI11.hpp | 11543 +++++++++++++++++++------------ src/config.cpp | 2 +- src/config.hpp | 2 +- src/crab_verifier.cpp | 4 +- src/ebpf_yaml.cpp | 8 +- src/main/check.cpp | 152 +- src/main/run_yaml.cpp | 5 +- src/test/conformance_check.cpp | 4 +- 9 files changed, 7259 insertions(+), 4506 deletions(-) diff --git a/README.md b/README.md index 89d5d6561..fd832a9da 100644 --- a/README.md +++ b/README.md @@ -75,31 +75,50 @@ The output is three comma-separated values: ## Usage: ``` -A new eBPF verifier -Usage: ./check [OPTIONS] path [section] +PREVAIL is a new eBPF verifier based on abstract interpretation. +Usage: ./check [OPTIONS] path [section] [function] Positionals: - path FILE REQUIRED Elf file to analyze + path TEXT:FILE REQUIRED Elf file to analyze section SECTION Section to analyze + function FUNCTION Function to analyze Options: -h,--help Print this help message and exit - -l List sections - -d,--dom,--domain DOMAIN:{cfg,linux,stats,zoneCrab} + --section SECTION Section to analyze + --function FUNCTION Function to analyze + -l List programs + --domain DOMAIN:{stats,linux,zoneCrab,cfg} [zoneCrab] Abstract domain - --termination Verify termination - --assume-assert Assume assertions + + +Features: + --termination,--no-verify-termination{false} + Verify termination. Default: ignore + --allow-division-by-zero,--no-division-by-zero{false} + Handling potential division by zero. Default: allow + -s,--strict Apply additional checks that would cause runtime failures + --include_groups GROUPS:{atomic32,atomic64,base32,base64,callx,divmul32,divmul64,packet} + Include conformance groups + --exclude_groups GROUPS:{atomic32,atomic64,base32,base64,callx,divmul32,divmul64,packet} + Exclude conformance groups + + +Verbosity: + --simplify,--no-simplify{false} + Simplify the CFG before analysis by merging chains of instructions into a single basic block. Default: enabled + --line-info Print line information + --print-btf-types Print BTF types + --assume-assert,--no-assume-assert{false} + Assume assertions (useful for debugging verification failures). Default: disabled -i Print invariants -f Print verifier's failure logs - -s Apply additional checks that would cause runtime failures -v Print both invariants and failures - --no-division-by-zero Do not allow division by zero - --no-simplify Do not simplify - --line-info Print line information + + +CFG output: --asm FILE Print disassembly to FILE --dot FILE Export control-flow graph to dot FILE - -You can use @headers as the path to instead just show the output field headers. ``` A standard alternative to the --asm flag is `llvm-objdump -S FILE`. diff --git a/external/CLI11.hpp b/external/CLI11.hpp index f6edf92e4..8a5b4c544 100644 --- a/external/CLI11.hpp +++ b/external/CLI11.hpp @@ -1,16 +1,11 @@ -// SPDX-License-Identifier: BSD-3-Clause -#pragma once - -// CLI11: Version 1.9.1 +// CLI11: Version 2.4.2 // Originally designed by Henry Schreiner // https://github.com/CLIUtils/CLI11 // // This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts -// from: v1.9.1 +// from: v2.4.2 // -// From LICENSE: -// -// CLI11 1.8 Copyright (c) 2017-2019 University of Cincinnati, developed by Henry +// CLI11 2.4.2 Copyright (c) 2017-2024 University of Cincinnati, developed by Henry // Schreiner under NSF AWARD 1414736. All rights reserved. // // Redistribution and use in source and binary forms of CLI11, with or without @@ -36,12 +31,18 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#pragma once // Standard combined includes: - #include +#include +#include +#include #include #include +#include +#include +#include #include #include #include @@ -63,21 +64,15 @@ #include -// Verbatim copy from Version.hpp: - - -#define CLI11_VERSION_MAJOR 1 -#define CLI11_VERSION_MINOR 9 -#define CLI11_VERSION_PATCH 1 -#define CLI11_VERSION "1.9.1" - +#define CLI11_VERSION_MAJOR 2 +#define CLI11_VERSION_MINOR 4 +#define CLI11_VERSION_PATCH 2 +#define CLI11_VERSION "2.4.2" -// Verbatim copy from Macros.hpp: - -// The following version macro is very similar to the one in PyBind11 +// The following version macro is very similar to the one in pybind11 #if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) #if __cplusplus >= 201402L #define CLI11_CPP14 @@ -95,7 +90,7 @@ #define CLI11_CPP14 #if _MSVC_LANG > 201402L && _MSC_VER >= 1910 #define CLI11_CPP17 -#if __MSVC_LANG > 201703L && _MSC_VER >= 1910 +#if _MSVC_LANG > 201703L && _MSC_VER >= 1910 #define CLI11_CPP20 #endif #endif @@ -110,19 +105,40 @@ #define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) #endif +// GCC < 10 doesn't ignore this in unevaluated contexts +#if !defined(CLI11_CPP17) || \ + (defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 10 && __GNUC__ > 4) +#define CLI11_NODISCARD +#else +#define CLI11_NODISCARD [[nodiscard]] +#endif +/** detection of rtti */ +#ifndef CLI11_USE_STATIC_RTTI +#if(defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI) +#define CLI11_USE_STATIC_RTTI 1 +#elif defined(__cpp_rtti) +#if(defined(_CPPRTTI) && _CPPRTTI == 0) +#define CLI11_USE_STATIC_RTTI 1 +#else +#define CLI11_USE_STATIC_RTTI 0 +#endif +#elif(defined(__GCC_RTTI) && __GXX_RTTI) +#define CLI11_USE_STATIC_RTTI 0 +#else +#define CLI11_USE_STATIC_RTTI 1 +#endif +#endif - -// Verbatim copy from Validators.hpp: - - -// C standard library -// Only needed for existence checking +/** availability */ #if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM #if __has_include() // Filesystem cannot be used if targeting macOS < 10.15 #if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 #define CLI11_HAS_FILESYSTEM 0 +#elif defined(__wasi__) +// As of wasi-sdk-14, filesystem is not implemented +#define CLI11_HAS_FILESYSTEM 0 #else #include #if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 @@ -141,6 +157,44 @@ #endif #endif +/** availability */ +#if defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 5 +#define CLI11_HAS_CODECVT 0 +#else +#define CLI11_HAS_CODECVT 1 +#include +#endif + +/** disable deprecations */ +#if defined(__GNUC__) // GCC or clang +#define CLI11_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") +#define CLI11_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") + +#elif defined(_MSC_VER) +#define CLI11_DIAGNOSTIC_PUSH __pragma(warning(push)) +#define CLI11_DIAGNOSTIC_POP __pragma(warning(pop)) + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED __pragma(warning(disable : 4996)) + +#else +#define CLI11_DIAGNOSTIC_PUSH +#define CLI11_DIAGNOSTIC_POP + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED + +#endif + +/** Inline macro **/ +#ifdef CLI11_COMPILE +#define CLI11_INLINE +#else +#define CLI11_INLINE inline +#endif + + + #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 #include // NOLINT(build/include) #else @@ -150,18 +204,244 @@ -// From Version.hpp: +#ifdef CLI11_CPP17 +#include +#endif // CLI11_CPP17 +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include +#include // NOLINT(build/include) +#endif // CLI11_HAS_FILESYSTEM + + + +#if defined(_WIN32) +#if !(defined(_AMD64_) || defined(_X86_) || defined(_ARM_)) +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || \ + defined(_M_AMD64) +#define _AMD64_ +#elif defined(i386) || defined(__i386) || defined(__i386__) || defined(__i386__) || defined(_M_IX86) +#define _X86_ +#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARMT) +#define _ARM_ +#elif defined(__aarch64__) || defined(_M_ARM64) +#define _ARM64_ +#elif defined(_M_ARM64EC) +#define _ARM64EC_ +#endif +#endif -// From Macros.hpp: - +// first +#ifndef NOMINMAX +// if NOMINMAX is already defined we don't want to mess with that either way +#define NOMINMAX +#include +#undef NOMINMAX +#else +#include +#endif +// second +#include +// third +#include +#include +#endif -// From StringTools.hpp: namespace CLI { + +/// Convert a wide string to a narrow string. +CLI11_INLINE std::string narrow(const std::wstring &str); +CLI11_INLINE std::string narrow(const wchar_t *str); +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t size); + +/// Convert a narrow string to a wide string. +CLI11_INLINE std::wstring widen(const std::string &str); +CLI11_INLINE std::wstring widen(const char *str); +CLI11_INLINE std::wstring widen(const char *str, std::size_t size); + +#ifdef CLI11_CPP17 +CLI11_INLINE std::string narrow(std::wstring_view str); +CLI11_INLINE std::wstring widen(std::string_view str); +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +/// Convert a char-string to a native path correctly. +CLI11_INLINE std::filesystem::path to_path(std::string_view str); +#endif // CLI11_HAS_FILESYSTEM + + + + +namespace detail { + +#if !CLI11_HAS_CODECVT +/// Attempt to set one of the acceptable unicode locales for conversion +CLI11_INLINE void set_unicode_locale() { + static const std::array unicode_locales{{"C.UTF-8", "en_US.UTF-8", ".UTF-8"}}; + + for(const auto &locale_name : unicode_locales) { + if(std::setlocale(LC_ALL, locale_name) != nullptr) { + return; + } + } + throw std::runtime_error("CLI::narrow: could not set locale to C.UTF-8"); +} + +template struct scope_guard_t { + F closure; + + explicit scope_guard_t(F closure_) : closure(closure_) {} + ~scope_guard_t() { closure(); } +}; + +template CLI11_NODISCARD CLI11_INLINE scope_guard_t scope_guard(F &&closure) { + return scope_guard_t{std::forward(closure)}; +} + +#endif // !CLI11_HAS_CODECVT + +CLI11_DIAGNOSTIC_PUSH +CLI11_DIAGNOSTIC_IGNORE_DEPRECATED + +CLI11_INLINE std::string narrow_impl(const wchar_t *str, std::size_t str_size) { +#if CLI11_HAS_CODECVT +#ifdef _WIN32 + return std::wstring_convert>().to_bytes(str, str + str_size); + +#else + return std::wstring_convert>().to_bytes(str, str + str_size); + +#endif // _WIN32 +#else // CLI11_HAS_CODECVT + (void)str_size; + std::mbstate_t state = std::mbstate_t(); + const wchar_t *it = str; + + std::string old_locale = std::setlocale(LC_ALL, nullptr); + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); + set_unicode_locale(); + + std::size_t new_size = std::wcsrtombs(nullptr, &it, 0, &state); + if(new_size == static_cast(-1)) { + throw std::runtime_error("CLI::narrow: conversion error in std::wcsrtombs at offset " + + std::to_string(it - str)); + } + std::string result(new_size, '\0'); + std::wcsrtombs(const_cast(result.data()), &str, new_size, &state); + + return result; + +#endif // CLI11_HAS_CODECVT +} + +CLI11_INLINE std::wstring widen_impl(const char *str, std::size_t str_size) { +#if CLI11_HAS_CODECVT +#ifdef _WIN32 + return std::wstring_convert>().from_bytes(str, str + str_size); + +#else + return std::wstring_convert>().from_bytes(str, str + str_size); + +#endif // _WIN32 +#else // CLI11_HAS_CODECVT + (void)str_size; + std::mbstate_t state = std::mbstate_t(); + const char *it = str; + + std::string old_locale = std::setlocale(LC_ALL, nullptr); + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); + set_unicode_locale(); + + std::size_t new_size = std::mbsrtowcs(nullptr, &it, 0, &state); + if(new_size == static_cast(-1)) { + throw std::runtime_error("CLI::widen: conversion error in std::mbsrtowcs at offset " + + std::to_string(it - str)); + } + std::wstring result(new_size, L'\0'); + std::mbsrtowcs(const_cast(result.data()), &str, new_size, &state); + + return result; + +#endif // CLI11_HAS_CODECVT +} + +CLI11_DIAGNOSTIC_POP + +} // namespace detail + +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t str_size) { return detail::narrow_impl(str, str_size); } +CLI11_INLINE std::string narrow(const std::wstring &str) { return detail::narrow_impl(str.data(), str.size()); } +// Flawfinder: ignore +CLI11_INLINE std::string narrow(const wchar_t *str) { return detail::narrow_impl(str, std::wcslen(str)); } + +CLI11_INLINE std::wstring widen(const char *str, std::size_t str_size) { return detail::widen_impl(str, str_size); } +CLI11_INLINE std::wstring widen(const std::string &str) { return detail::widen_impl(str.data(), str.size()); } +// Flawfinder: ignore +CLI11_INLINE std::wstring widen(const char *str) { return detail::widen_impl(str, std::strlen(str)); } + +#ifdef CLI11_CPP17 +CLI11_INLINE std::string narrow(std::wstring_view str) { return detail::narrow_impl(str.data(), str.size()); } +CLI11_INLINE std::wstring widen(std::string_view str) { return detail::widen_impl(str.data(), str.size()); } +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE std::filesystem::path to_path(std::string_view str) { + return std::filesystem::path{ +#ifdef _WIN32 + widen(str) +#else + str +#endif // _WIN32 + }; +} +#endif // CLI11_HAS_FILESYSTEM + + + + +namespace detail { +#ifdef _WIN32 +/// Decode and return UTF-8 argv from GetCommandLineW. +CLI11_INLINE std::vector compute_win32_argv(); +#endif +} // namespace detail + + + +namespace detail { + +#ifdef _WIN32 +CLI11_INLINE std::vector compute_win32_argv() { + std::vector result; + int argc = 0; + + auto deleter = [](wchar_t **ptr) { LocalFree(ptr); }; + // NOLINTBEGIN(*-avoid-c-arrays) + auto wargv = std::unique_ptr(CommandLineToArgvW(GetCommandLineW(), &argc), deleter); + // NOLINTEND(*-avoid-c-arrays) + + if(wargv == nullptr) { + throw std::runtime_error("CommandLineToArgvW failed with code " + std::to_string(GetLastError())); + } + + result.reserve(static_cast(argc)); + for(size_t i = 0; i < static_cast(argc); ++i) { + result.push_back(narrow(wargv[i])); + } + + return result; +} +#endif + +} // namespace detail + + + + /// Include the items in this namespace to get free conversion of enums to/from streams. /// (This is available inside CLI as well, so CLI11 will use this without a using statement). namespace enums { @@ -184,21 +464,7 @@ namespace detail { constexpr int expected_max_vector_size{1 << 29}; // Based on http://stackoverflow.com/questions/236129/split-a-string-in-c /// Split a string by a delim -inline std::vector split(const std::string &s, char delim) { - std::vector elems; - // Check to see if empty string, give consistent result - if(s.empty()) { - elems.emplace_back(); - } else { - std::stringstream ss; - ss.str(s); - std::string item; - while(std::getline(ss, item, delim)) { - elems.push_back(item); - } - } - return elems; -} +CLI11_INLINE std::vector split(const std::string &s, char delim); /// Simple function to join a string template std::string join(const T &v, std::string delim = ",") { @@ -215,16 +481,20 @@ template std::string join(const T &v, std::string delim = ",") { /// Simple function to join a string from processed elements template ::value>::type> + typename Callable, + typename = typename std::enable_if::value>::type> std::string join(const T &v, Callable func, std::string delim = ",") { std::ostringstream s; auto beg = std::begin(v); auto end = std::end(v); - if(beg != end) - s << func(*beg++); + auto loc = s.tellp(); while(beg != end) { - s << delim << func(*beg++); + auto nloc = s.tellp(); + if(nloc > loc) { + s << delim; + loc = nloc; + } + s << func(*beg++); } return s.str(); } @@ -243,33 +513,16 @@ template std::string rjoin(const T &v, std::string delim = ",") { // Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string /// Trim whitespace from left of string -inline std::string <rim(std::string &str) { - auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(ch, std::locale()); }); - str.erase(str.begin(), it); - return str; -} +CLI11_INLINE std::string <rim(std::string &str); /// Trim anything from left of string -inline std::string <rim(std::string &str, const std::string &filter) { - auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); - str.erase(str.begin(), it); - return str; -} +CLI11_INLINE std::string <rim(std::string &str, const std::string &filter); /// Trim whitespace from right of string -inline std::string &rtrim(std::string &str) { - auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(ch, std::locale()); }); - str.erase(it.base(), str.end()); - return str; -} +CLI11_INLINE std::string &rtrim(std::string &str); /// Trim anything from right of string -inline std::string &rtrim(std::string &str, const std::string &filter) { - auto it = - std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); - str.erase(it.base(), str.end()); - return str; -} +CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter); /// Trim whitespace from string inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); } @@ -284,15 +537,16 @@ inline std::string trim_copy(const std::string &str) { } /// remove quotes at the front and back of a string either '"' or '\'' -inline std::string &remove_quotes(std::string &str) { - if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) { - if(str.front() == str.back()) { - str.pop_back(); - str.erase(str.begin(), str.begin() + 1); - } - } - return str; -} +CLI11_INLINE std::string &remove_quotes(std::string &str); + +/// remove quotes from all elements of a string vector and process escaped components +CLI11_INLINE void remove_quotes(std::vector &args); + +/// Add a leader to the beginning of all new lines (nothing is added +/// at the start of the first line). `"; "` would be for ini files +/// +/// Can't use Regex, or this would be a subs. +CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input); /// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) inline std::string trim_copy(const std::string &str, const std::string &filter) { @@ -300,39 +554,39 @@ inline std::string trim_copy(const std::string &str, const std::string &filter) return trim(s, filter); } /// Print a two part "help" string -inline std::ostream &format_help(std::ostream &out, std::string name, std::string description, std::size_t wid) { - name = " " + name; - out << std::setw(static_cast(wid)) << std::left << name; - if(!description.empty()) { - if(name.length() >= wid) - out << "\n" << std::setw(static_cast(wid)) << ""; - for(const char c : description) { - out.put(c); - if(c == '\n') { - out << std::setw(static_cast(wid)) << ""; - } - } - } - out << "\n"; - return out; -} +CLI11_INLINE std::ostream & +format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid); + +/// Print subcommand aliases +CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector &aliases, std::size_t wid); /// Verify the first character of an option +/// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with template bool valid_first_char(T c) { - return std::isalnum(c, std::locale()) || c == '_' || c == '?' || c == '@'; + return ((c != '-') && (static_cast(c) > 33)); // space and '!' not allowed } /// Verify following characters of an option -template bool valid_later_char(T c) { return valid_first_char(c) || c == '.' || c == '-'; } +template bool valid_later_char(T c) { + // = and : are value separators, { has special meaning for option defaults, + // and control codes other than tab would just be annoying to deal with in many places allowing space here has too + // much potential for inadvertent entry errors and bugs + return ((c != '=') && (c != ':') && (c != '{') && ((static_cast(c) > 32) || c == '\t')); +} -/// Verify an option name -inline bool valid_name_string(const std::string &str) { - if(str.empty() || !valid_first_char(str[0])) - return false; - for(auto c : str.substr(1)) - if(!valid_later_char(c)) - return false; - return true; +/// Verify an option/subcommand name +CLI11_INLINE bool valid_name_string(const std::string &str); + +/// Verify an app name +inline bool valid_alias_name_string(const std::string &str) { + static const std::string badChars(std::string("\n") + '\0'); + return (str.find_first_of(badChars) == std::string::npos); +} + +/// check if a string is a container segment separator (empty or "%%") +inline bool is_separator(const std::string &str) { + static const std::string sep("%%"); + return (str.empty() || str == sep); } /// Verify that str consists of letters only @@ -343,7 +597,7 @@ inline bool isalpha(const std::string &str) { /// Return a lower case version of a string inline std::string to_lower(std::string str) { std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { - return std::tolower(x, std::locale()); + return std::tolower(x, std::locale()); }); return str; } @@ -355,66 +609,20 @@ inline std::string remove_underscore(std::string str) { } /// Find and replace a substring with another substring -inline std::string find_and_replace(std::string str, std::string from, std::string to) { - - std::size_t start_pos = 0; - - while((start_pos = str.find(from, start_pos)) != std::string::npos) { - str.replace(start_pos, from.length(), to); - start_pos += to.length(); - } - - return str; -} +CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to); /// check if the flag definitions has possible false flags inline bool has_default_flag_values(const std::string &flags) { return (flags.find_first_of("{!") != std::string::npos); } -inline void remove_default_flag_values(std::string &flags) { - auto loc = flags.find_first_of('{'); - while(loc != std::string::npos) { - auto finish = flags.find_first_of("},", loc + 1); - if((finish != std::string::npos) && (flags[finish] == '}')) { - flags.erase(flags.begin() + static_cast(loc), - flags.begin() + static_cast(finish) + 1); - } - loc = flags.find_first_of('{', loc + 1); - } - flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end()); -} +CLI11_INLINE void remove_default_flag_values(std::string &flags); /// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores -inline std::ptrdiff_t find_member(std::string name, - const std::vector names, - bool ignore_case = false, - bool ignore_underscore = false) { - auto it = std::end(names); - if(ignore_case) { - if(ignore_underscore) { - name = detail::to_lower(detail::remove_underscore(name)); - it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { - return detail::to_lower(detail::remove_underscore(local_name)) == name; - }); - } else { - name = detail::to_lower(name); - it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { - return detail::to_lower(local_name) == name; - }); - } - - } else if(ignore_underscore) { - name = detail::remove_underscore(name); - it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { - return detail::remove_underscore(local_name) == name; - }); - } else { - it = std::find(std::begin(names), std::end(names), name); - } - - return (it != std::end(names)) ? (it - std::begin(names)) : (-1); -} +CLI11_INLINE std::ptrdiff_t find_member(std::string name, + const std::vector names, + bool ignore_case = false, + bool ignore_underscore = false); /// Find a trigger string and call a modify callable function that takes the current string and starting position of the /// trigger and returns the position in the string to search for the next trigger string @@ -426,60 +634,115 @@ template inline std::string find_and_modify(std::string str, return str; } +/// close a sequence of characters indicated by a closure character. Brackets allows sub sequences +/// recognized bracket sequences include "'`[(<{ other closure characters are assumed to be literal strings +CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char); + /// Split a string '"one two" "three"' into 'one two', 'three' -/// Quote characters can be ` ' or " -inline std::vector split_up(std::string str, char delimiter = '\0') { +/// Quote characters can be ` ' or " or bracket characters [{(< with matching to the matching bracket +CLI11_INLINE std::vector split_up(std::string str, char delimiter = '\0'); - const std::string delims("\'\"`"); - auto find_ws = [delimiter](char ch) { - return (delimiter == '\0') ? (std::isspace(ch, std::locale()) != 0) : (ch == delimiter); - }; - trim(str); +/// get the value of an environmental variable or empty string if empty +CLI11_INLINE std::string get_environment_value(const std::string &env_name); - std::vector output; - bool embeddedQuote = false; - char keyChar = ' '; - while(!str.empty()) { - if(delims.find_first_of(str[0]) != std::string::npos) { - keyChar = str[0]; - auto end = str.find_first_of(keyChar, 1); - while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes - end = str.find_first_of(keyChar, end + 1); - embeddedQuote = true; - } - if(end != std::string::npos) { - output.push_back(str.substr(1, end - 1)); - str = str.substr(end + 1); - } else { - output.push_back(str.substr(1)); - str = ""; - } - } else { - auto it = std::find_if(std::begin(str), std::end(str), find_ws); - if(it != std::end(str)) { - std::string value = std::string(str.begin(), it); - output.push_back(value); - str = std::string(it + 1, str.end()); - } else { - output.push_back(str); - str = ""; - } +/// This function detects an equal or colon followed by an escaped quote after an argument +/// then modifies the string to replace the equality with a space. This is needed +/// to allow the split up function to work properly and is intended to be used with the find_and_modify function +/// the return value is the offset+1 which is required by the find_and_modify function. +CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset); + +/// @brief detect if a string has escapable characters +/// @param str the string to do the detection on +/// @return true if the string has escapable characters +CLI11_INLINE bool has_escapable_character(const std::string &str); + +/// @brief escape all escapable characters +/// @param str the string to escape +/// @return a string with the escapble characters escaped with '\' +CLI11_INLINE std::string add_escaped_characters(const std::string &str); + +/// @brief replace the escaped characters with their equivalent +CLI11_INLINE std::string remove_escaped_characters(const std::string &str); + +/// generate a string with all non printable characters escaped to hex codes +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape); + +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string); + +/// extract an escaped binary_string +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string); + +/// process a quoted string, remove the quotes and if appropriate handle escaped characters +CLI11_INLINE bool process_quoted_string(std::string &str, char string_char = '\"', char literal_char = '\''); + +} // namespace detail + + + + +namespace detail { +CLI11_INLINE std::vector split(const std::string &s, char delim) { + std::vector elems; + // Check to see if empty string, give consistent result + if(s.empty()) { + elems.emplace_back(); + } else { + std::stringstream ss; + ss.str(s); + std::string item; + while(std::getline(ss, item, delim)) { + elems.push_back(item); } - // transform any embedded quotes into the regular character - if(embeddedQuote) { - output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar)); - embeddedQuote = false; + } + return elems; +} + +CLI11_INLINE std::string <rim(std::string &str) { + auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(str.begin(), it); + return str; +} + +CLI11_INLINE std::string <rim(std::string &str, const std::string &filter) { + auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(str.begin(), it); + return str; +} + +CLI11_INLINE std::string &rtrim(std::string &str) { + auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(it.base(), str.end()); + return str; +} + +CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter) { + auto it = + std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(it.base(), str.end()); + return str; +} + +CLI11_INLINE std::string &remove_quotes(std::string &str) { + if(str.length() > 1 && (str.front() == '"' || str.front() == '\'' || str.front() == '`')) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); } - trim(str); } - return output; + return str; } -/// Add a leader to the beginning of all new lines (nothing is added -/// at the start of the first line). `"; "` would be for ini files -/// -/// Can't use Regex, or this would be a subs. -inline std::string fix_newlines(const std::string &leader, std::string input) { +CLI11_INLINE std::string &remove_outer(std::string &str, char key) { + if(str.length() > 1 && (str.front() == key)) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; +} + +CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input) { std::string::size_type n = 0; while(n != std::string::npos && n < input.size()) { n = input.find('\n', n); @@ -491,133 +754,578 @@ inline std::string fix_newlines(const std::string &leader, std::string input) { return input; } -/// This function detects an equal or colon followed by an escaped quote after an argument -/// then modifies the string to replace the equality with a space. This is needed -/// to allow the split up function to work properly and is intended to be used with the find_and_modify function -/// the return value is the offset+1 which is required by the find_and_modify function. -inline std::size_t escape_detect(std::string &str, std::size_t offset) { - auto next = str[offset + 1]; - if((next == '\"') || (next == '\'') || (next == '`')) { - auto astart = str.find_last_of("-/ \"\'`", offset - 1); - if(astart != std::string::npos) { - if(str[astart] == ((str[offset] == '=') ? '-' : '/')) - str[offset] = ' '; // interpret this as a space so the split_up works properly +CLI11_INLINE std::ostream & +format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid) { + name = " " + name; + out << std::setw(static_cast(wid)) << std::left << name; + if(!description.empty()) { + if(name.length() >= wid) + out << "\n" << std::setw(static_cast(wid)) << ""; + for(const char c : description) { + out.put(c); + if(c == '\n') { + out << std::setw(static_cast(wid)) << ""; + } } } - return offset + 1; + out << "\n"; + return out; } -/// Add quotes if the string contains spaces -inline std::string &add_quotes_if_needed(std::string &str) { - if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) { - char quote = str.find('"') < str.find('\'') ? '\'' : '"'; - if(str.find(' ') != std::string::npos) { - str.insert(0, 1, quote); - str.append(1, quote); +CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector &aliases, std::size_t wid) { + if(!aliases.empty()) { + out << std::setw(static_cast(wid)) << " aliases: "; + bool front = true; + for(const auto &alias : aliases) { + if(!front) { + out << ", "; + } else { + front = false; + } + out << detail::fix_newlines(" ", alias); } + out << "\n"; } - return str; + return out; } -} // namespace detail +CLI11_INLINE bool valid_name_string(const std::string &str) { + if(str.empty() || !valid_first_char(str[0])) { + return false; + } + auto e = str.end(); + for(auto c = str.begin() + 1; c != e; ++c) + if(!valid_later_char(*c)) + return false; + return true; +} -} // namespace CLI +CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to) { -// From Error.hpp: + std::size_t start_pos = 0; -namespace CLI { + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } -// Use one of these on all error classes. -// These are temporary and are undef'd at the end of this file. -#define CLI11_ERROR_DEF(parent, name) \ - protected: \ - name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \ - name(std::string ename, std::string msg, ExitCodes exit_code) \ - : parent(std::move(ename), std::move(msg), exit_code) {} \ - \ - public: \ - name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ - name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} + return str; +} -// This is added after the one above if a class is used directly and builds its own message -#define CLI11_ERROR_SIMPLE(name) \ - explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {} +CLI11_INLINE void remove_default_flag_values(std::string &flags) { + auto loc = flags.find_first_of('{', 2); + while(loc != std::string::npos) { + auto finish = flags.find_first_of("},", loc + 1); + if((finish != std::string::npos) && (flags[finish] == '}')) { + flags.erase(flags.begin() + static_cast(loc), + flags.begin() + static_cast(finish) + 1); + } + loc = flags.find_first_of('{', loc + 1); + } + flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end()); +} -/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, -/// int values from e.get_error_code(). -enum class ExitCodes { - Success = 0, - IncorrectConstruction = 100, - BadNameString, - OptionAlreadyAdded, - FileError, - ConversionError, - ValidationError, - RequiredError, - RequiresError, - ExcludesError, - ExtrasError, - ConfigError, - InvalidError, - HorribleError, - OptionNotFound, - ArgumentMismatch, - BaseClass = 127 -}; +CLI11_INLINE std::ptrdiff_t +find_member(std::string name, const std::vector names, bool ignore_case, bool ignore_underscore) { + auto it = std::end(names); + if(ignore_case) { + if(ignore_underscore) { + name = detail::to_lower(detail::remove_underscore(name)); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(detail::remove_underscore(local_name)) == name; + }); + } else { + name = detail::to_lower(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(local_name) == name; + }); + } -// Error definitions + } else if(ignore_underscore) { + name = detail::remove_underscore(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::remove_underscore(local_name) == name; + }); + } else { + it = std::find(std::begin(names), std::end(names), name); + } -/// @defgroup error_group Errors -/// @brief Errors thrown by CLI11 -/// -/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. -/// @{ + return (it != std::end(names)) ? (it - std::begin(names)) : (-1); +} -/// All errors derive from this one -class Error : public std::runtime_error { - int actual_exit_code; - std::string error_name{"Error"}; +static const std::string escapedChars("\b\t\n\f\r\"\\"); +static const std::string escapedCharsCode("btnfr\"\\"); +static const std::string bracketChars{"\"'`[(<{"}; +static const std::string matchBracketChars("\"'`])>}"); - public: - int get_exit_code() const { return actual_exit_code; } +CLI11_INLINE bool has_escapable_character(const std::string &str) { + return (str.find_first_of(escapedChars) != std::string::npos); +} - std::string get_name() const { return error_name; } +CLI11_INLINE std::string add_escaped_characters(const std::string &str) { + std::string out; + out.reserve(str.size() + 4); + for(char s : str) { + auto sloc = escapedChars.find_first_of(s); + if(sloc != std::string::npos) { + out.push_back('\\'); + out.push_back(escapedCharsCode[sloc]); + } else { + out.push_back(s); + } + } + return out; +} - Error(std::string name, std::string msg, int exit_code = static_cast(ExitCodes::BaseClass)) - : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {} +CLI11_INLINE std::uint32_t hexConvert(char hc) { + int hcode{0}; + if(hc >= '0' && hc <= '9') { + hcode = (hc - '0'); + } else if(hc >= 'A' && hc <= 'F') { + hcode = (hc - 'A' + 10); + } else if(hc >= 'a' && hc <= 'f') { + hcode = (hc - 'a' + 10); + } else { + hcode = -1; + } + return static_cast(hcode); +} + +CLI11_INLINE char make_char(std::uint32_t code) { return static_cast(static_cast(code)); } + +CLI11_INLINE void append_codepoint(std::string &str, std::uint32_t code) { + if(code < 0x80) { // ascii code equivalent + str.push_back(static_cast(code)); + } else if(code < 0x800) { // \u0080 to \u07FF + // 110yyyyx 10xxxxxx; 0x3f == 0b0011'1111 + str.push_back(make_char(0xC0 | code >> 6)); + str.push_back(make_char(0x80 | (code & 0x3F))); + } else if(code < 0x10000) { // U+0800...U+FFFF + if(0xD800 <= code && code <= 0xDFFF) { + throw std::invalid_argument("[0xD800, 0xDFFF] are not valid UTF-8."); + } + // 1110yyyy 10yxxxxx 10xxxxxx + str.push_back(make_char(0xE0 | code >> 12)); + str.push_back(make_char(0x80 | (code >> 6 & 0x3F))); + str.push_back(make_char(0x80 | (code & 0x3F))); + } else if(code < 0x110000) { // U+010000 ... U+10FFFF + // 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx + str.push_back(make_char(0xF0 | code >> 18)); + str.push_back(make_char(0x80 | (code >> 12 & 0x3F))); + str.push_back(make_char(0x80 | (code >> 6 & 0x3F))); + str.push_back(make_char(0x80 | (code & 0x3F))); + } +} + +CLI11_INLINE std::string remove_escaped_characters(const std::string &str) { + + std::string out; + out.reserve(str.size()); + for(auto loc = str.begin(); loc < str.end(); ++loc) { + if(*loc == '\\') { + if(str.end() - loc < 2) { + throw std::invalid_argument("invalid escape sequence " + str); + } + auto ecloc = escapedCharsCode.find_first_of(*(loc + 1)); + if(ecloc != std::string::npos) { + out.push_back(escapedChars[ecloc]); + ++loc; + } else if(*(loc + 1) == 'u') { + // must have 4 hex characters + if(str.end() - loc < 6) { + throw std::invalid_argument("unicode sequence must have 4 hex codes " + str); + } + std::uint32_t code{0}; + std::uint32_t mplier{16 * 16 * 16}; + for(int ii = 2; ii < 6; ++ii) { + std::uint32_t res = hexConvert(*(loc + ii)); + if(res > 0x0F) { + throw std::invalid_argument("unicode sequence must have 4 hex codes " + str); + } + code += res * mplier; + mplier = mplier / 16; + } + append_codepoint(out, code); + loc += 5; + } else if(*(loc + 1) == 'U') { + // must have 8 hex characters + if(str.end() - loc < 10) { + throw std::invalid_argument("unicode sequence must have 8 hex codes " + str); + } + std::uint32_t code{0}; + std::uint32_t mplier{16 * 16 * 16 * 16 * 16 * 16 * 16}; + for(int ii = 2; ii < 10; ++ii) { + std::uint32_t res = hexConvert(*(loc + ii)); + if(res > 0x0F) { + throw std::invalid_argument("unicode sequence must have 8 hex codes " + str); + } + code += res * mplier; + mplier = mplier / 16; + } + append_codepoint(out, code); + loc += 9; + } else if(*(loc + 1) == '0') { + out.push_back('\0'); + ++loc; + } else { + throw std::invalid_argument(std::string("unrecognized escape sequence \\") + *(loc + 1) + " in " + str); + } + } else { + out.push_back(*loc); + } + } + return out; +} - Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} -}; +CLI11_INLINE std::size_t close_string_quote(const std::string &str, std::size_t start, char closure_char) { + std::size_t loc{0}; + for(loc = start + 1; loc < str.size(); ++loc) { + if(str[loc] == closure_char) { + break; + } + if(str[loc] == '\\') { + // skip the next character for escaped sequences + ++loc; + } + } + return loc; +} -// Note: Using Error::Error constructors does not work on GCC 4.7 +CLI11_INLINE std::size_t close_literal_quote(const std::string &str, std::size_t start, char closure_char) { + auto loc = str.find_first_of(closure_char, start + 1); + return (loc != std::string::npos ? loc : str.size()); +} -/// Construction errors (not in parsing) -class ConstructionError : public Error { - CLI11_ERROR_DEF(Error, ConstructionError) -}; +CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char) { -/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) -class IncorrectConstruction : public ConstructionError { - CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) - CLI11_ERROR_SIMPLE(IncorrectConstruction) - static IncorrectConstruction PositionalFlag(std::string name) { - return IncorrectConstruction(name + ": Flags cannot be positional"); - } - static IncorrectConstruction Set0Opt(std::string name) { - return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); - } - static IncorrectConstruction SetFlag(std::string name) { - return IncorrectConstruction(name + ": Cannot set an expected number for flags"); + auto bracket_loc = matchBracketChars.find(closure_char); + switch(bracket_loc) { + case 0: + return close_string_quote(str, start, closure_char); + case 1: + case 2: + case std::string::npos: + return close_literal_quote(str, start, closure_char); + default: + break; } - static IncorrectConstruction ChangeNotVector(std::string name) { - return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); + + std::string closures(1, closure_char); + auto loc = start + 1; + + while(loc < str.size()) { + if(str[loc] == closures.back()) { + closures.pop_back(); + if(closures.empty()) { + return loc; + } + } + bracket_loc = bracketChars.find(str[loc]); + if(bracket_loc != std::string::npos) { + switch(bracket_loc) { + case 0: + loc = close_string_quote(str, loc, str[loc]); + break; + case 1: + case 2: + loc = close_literal_quote(str, loc, str[loc]); + break; + default: + closures.push_back(matchBracketChars[bracket_loc]); + break; + } + } + ++loc; } - static IncorrectConstruction AfterMultiOpt(std::string name) { - return IncorrectConstruction( - name + ": You can't change expected arguments after you've changed the multi option policy!"); + if(loc > str.size()) { + loc = str.size(); } - static IncorrectConstruction MissingOption(std::string name) { - return IncorrectConstruction("Option " + name + " is not defined"); + return loc; +} + +CLI11_INLINE std::vector split_up(std::string str, char delimiter) { + + auto find_ws = [delimiter](char ch) { + return (delimiter == '\0') ? std::isspace(ch, std::locale()) : (ch == delimiter); + }; + trim(str); + + std::vector output; + while(!str.empty()) { + if(bracketChars.find_first_of(str[0]) != std::string::npos) { + auto bracketLoc = bracketChars.find_first_of(str[0]); + auto end = close_sequence(str, 0, matchBracketChars[bracketLoc]); + if(end >= str.size()) { + output.push_back(std::move(str)); + str.clear(); + } else { + output.push_back(str.substr(0, end + 1)); + if(end + 2 < str.size()) { + str = str.substr(end + 2); + } else { + str.clear(); + } + } + + } else { + auto it = std::find_if(std::begin(str), std::end(str), find_ws); + if(it != std::end(str)) { + std::string value = std::string(str.begin(), it); + output.push_back(value); + str = std::string(it + 1, str.end()); + } else { + output.push_back(str); + str.clear(); + } + } + trim(str); + } + return output; +} + +CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset) { + auto next = str[offset + 1]; + if((next == '\"') || (next == '\'') || (next == '`')) { + auto astart = str.find_last_of("-/ \"\'`", offset - 1); + if(astart != std::string::npos) { + if(str[astart] == ((str[offset] == '=') ? '-' : '/')) + str[offset] = ' '; // interpret this as a space so the split_up works properly + } + } + return offset + 1; +} + +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape) { + // s is our escaped output string + std::string escaped_string{}; + // loop through all characters + for(char c : string_to_escape) { + // check if a given character is printable + // the cast is necessary to avoid undefined behaviour + if(isprint(static_cast(c)) == 0) { + std::stringstream stream; + // if the character is not printable + // we'll convert it to a hex string using a stringstream + // note that since char is signed we have to cast it to unsigned first + stream << std::hex << static_cast(static_cast(c)); + std::string code = stream.str(); + escaped_string += std::string("\\x") + (code.size() < 2 ? "0" : "") + code; + + } else { + escaped_string.push_back(c); + } + } + if(escaped_string != string_to_escape) { + auto sqLoc = escaped_string.find('\''); + while(sqLoc != std::string::npos) { + escaped_string.replace(sqLoc, sqLoc + 1, "\\x27"); + sqLoc = escaped_string.find('\''); + } + escaped_string.insert(0, "'B\"("); + escaped_string.push_back(')'); + escaped_string.push_back('"'); + escaped_string.push_back('\''); + } + return escaped_string; +} + +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string) { + size_t ssize = escaped_string.size(); + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { + return true; + } + return (escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0); +} + +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string) { + std::size_t start{0}; + std::size_t tail{0}; + size_t ssize = escaped_string.size(); + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { + start = 3; + tail = 2; + } else if(escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0) { + start = 4; + tail = 3; + } + + if(start == 0) { + return escaped_string; + } + std::string outstring; + + outstring.reserve(ssize - start - tail); + std::size_t loc = start; + while(loc < ssize - tail) { + // ssize-2 to skip )" at the end + if(escaped_string[loc] == '\\' && (escaped_string[loc + 1] == 'x' || escaped_string[loc + 1] == 'X')) { + auto c1 = escaped_string[loc + 2]; + auto c2 = escaped_string[loc + 3]; + + std::uint32_t res1 = hexConvert(c1); + std::uint32_t res2 = hexConvert(c2); + if(res1 <= 0x0F && res2 <= 0x0F) { + loc += 4; + outstring.push_back(static_cast(res1 * 16 + res2)); + continue; + } + } + outstring.push_back(escaped_string[loc]); + ++loc; + } + return outstring; +} + +CLI11_INLINE void remove_quotes(std::vector &args) { + for(auto &arg : args) { + if(arg.front() == '\"' && arg.back() == '\"') { + remove_quotes(arg); + // only remove escaped for string arguments not literal strings + arg = remove_escaped_characters(arg); + } else { + remove_quotes(arg); + } + } +} + +CLI11_INLINE bool process_quoted_string(std::string &str, char string_char, char literal_char) { + if(str.size() <= 1) { + return false; + } + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + return true; + } + if(str.front() == string_char && str.back() == string_char) { + detail::remove_outer(str, string_char); + if(str.find_first_of('\\') != std::string::npos) { + str = detail::remove_escaped_characters(str); + } + return true; + } + if((str.front() == literal_char || str.front() == '`') && str.back() == str.front()) { + detail::remove_outer(str, str.front()); + return true; + } + return false; +} + +std::string get_environment_value(const std::string &env_name) { + char *buffer = nullptr; + std::string ename_string; + +#ifdef _MSC_VER + // Windows version + std::size_t sz = 0; + if(_dupenv_s(&buffer, &sz, env_name.c_str()) == 0 && buffer != nullptr) { + ename_string = std::string(buffer); + free(buffer); + } +#else + // This also works on Windows, but gives a warning + buffer = std::getenv(env_name.c_str()); + if(buffer != nullptr) { + ename_string = std::string(buffer); + } +#endif + return ename_string; +} + +} // namespace detail + + + +// Use one of these on all error classes. +// These are temporary and are undef'd at the end of this file. +#define CLI11_ERROR_DEF(parent, name) \ + protected: \ + name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \ + name(std::string ename, std::string msg, ExitCodes exit_code) \ + : parent(std::move(ename), std::move(msg), exit_code) {} \ + \ + public: \ + name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ + name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} + +// This is added after the one above if a class is used directly and builds its own message +#define CLI11_ERROR_SIMPLE(name) \ + explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {} + +/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, +/// int values from e.get_error_code(). +enum class ExitCodes { + Success = 0, + IncorrectConstruction = 100, + BadNameString, + OptionAlreadyAdded, + FileError, + ConversionError, + ValidationError, + RequiredError, + RequiresError, + ExcludesError, + ExtrasError, + ConfigError, + InvalidError, + HorribleError, + OptionNotFound, + ArgumentMismatch, + BaseClass = 127 +}; + +// Error definitions + +/// @defgroup error_group Errors +/// @brief Errors thrown by CLI11 +/// +/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. +/// @{ + +/// All errors derive from this one +class Error : public std::runtime_error { + int actual_exit_code; + std::string error_name{"Error"}; + + public: + CLI11_NODISCARD int get_exit_code() const { return actual_exit_code; } + + CLI11_NODISCARD std::string get_name() const { return error_name; } + + Error(std::string name, std::string msg, int exit_code = static_cast(ExitCodes::BaseClass)) + : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {} + + Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} +}; + +// Note: Using Error::Error constructors does not work on GCC 4.7 + +/// Construction errors (not in parsing) +class ConstructionError : public Error { + CLI11_ERROR_DEF(Error, ConstructionError) +}; + +/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) +class IncorrectConstruction : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) + CLI11_ERROR_SIMPLE(IncorrectConstruction) + static IncorrectConstruction PositionalFlag(std::string name) { + return IncorrectConstruction(name + ": Flags cannot be positional"); + } + static IncorrectConstruction Set0Opt(std::string name) { + return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); + } + static IncorrectConstruction SetFlag(std::string name) { + return IncorrectConstruction(name + ": Cannot set an expected number for flags"); + } + static IncorrectConstruction ChangeNotVector(std::string name) { + return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); + } + static IncorrectConstruction AfterMultiOpt(std::string name) { + return IncorrectConstruction( + name + ": You can't change expected arguments after you've changed the multi option policy!"); + } + static IncorrectConstruction MissingOption(std::string name) { + return IncorrectConstruction("Option " + name + " is not defined"); } static IncorrectConstruction MultiOptionPolicy(std::string name) { return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); @@ -626,10 +1334,16 @@ class IncorrectConstruction : public ConstructionError { /// Thrown on construction of a bad name class BadNameString : public ConstructionError { - CLI11_ERROR_DEF(ConstructionError, BadNameString) + CLI11_ERROR_DEF(ConstructionError, BadNameString) CLI11_ERROR_SIMPLE(BadNameString) static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } + static BadNameString MissingDash(std::string name) { + return BadNameString("Long names strings require 2 dashes " + name); + } static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } + static BadNameString BadPositionalName(std::string name) { + return BadNameString("Invalid positional Name: " + name); + } static BadNameString DashesOnly(std::string name) { return BadNameString("Must have a name, not just dashes: " + name); } @@ -640,14 +1354,14 @@ class BadNameString : public ConstructionError { /// Thrown when an option already exists class OptionAlreadyAdded : public ConstructionError { - CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) + CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) explicit OptionAlreadyAdded(std::string name) : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} static OptionAlreadyAdded Requires(std::string name, std::string other) { - return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded); + return {name + " requires " + other, ExitCodes::OptionAlreadyAdded}; } static OptionAlreadyAdded Excludes(std::string name, std::string other) { - return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded); + return {name + " excludes " + other, ExitCodes::OptionAlreadyAdded}; } }; @@ -655,46 +1369,53 @@ class OptionAlreadyAdded : public ConstructionError { /// Anything that can error in Parse class ParseError : public Error { - CLI11_ERROR_DEF(Error, ParseError) + CLI11_ERROR_DEF(Error, ParseError) }; // Not really "errors" /// This is a successful completion on parsing, supposed to exit class Success : public ParseError { - CLI11_ERROR_DEF(ParseError, Success) + CLI11_ERROR_DEF(ParseError, Success) Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {} }; /// -h or --help on command line -class CallForHelp : public ParseError { - CLI11_ERROR_DEF(ParseError, CallForHelp) +class CallForHelp : public Success { + CLI11_ERROR_DEF(Success, CallForHelp) CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} }; /// Usually something like --help-all on command line -class CallForAllHelp : public ParseError { - CLI11_ERROR_DEF(ParseError, CallForAllHelp) +class CallForAllHelp : public Success { + CLI11_ERROR_DEF(Success, CallForAllHelp) CallForAllHelp() : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} }; -/// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. +/// -v or --version on command line +class CallForVersion : public Success { + CLI11_ERROR_DEF(Success, CallForVersion) + CallForVersion() + : CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Does not output a diagnostic in CLI11_PARSE, but allows main() to return with a specific error code. class RuntimeError : public ParseError { - CLI11_ERROR_DEF(ParseError, RuntimeError) + CLI11_ERROR_DEF(ParseError, RuntimeError) explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} }; /// Thrown when parsing an INI file and it is missing class FileError : public ParseError { - CLI11_ERROR_DEF(ParseError, FileError) + CLI11_ERROR_DEF(ParseError, FileError) CLI11_ERROR_SIMPLE(FileError) static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); } }; /// Thrown when conversion call back fails, such as when an int fails to coerce to a string class ConversionError : public ParseError { - CLI11_ERROR_DEF(ParseError, ConversionError) + CLI11_ERROR_DEF(ParseError, ConversionError) CLI11_ERROR_SIMPLE(ConversionError) ConversionError(std::string member, std::string name) : ConversionError("The value " + member + " is not an allowed value for " + name) {} @@ -710,51 +1431,49 @@ class ConversionError : public ParseError { /// Thrown when validation of results fails class ValidationError : public ParseError { - CLI11_ERROR_DEF(ParseError, ValidationError) + CLI11_ERROR_DEF(ParseError, ValidationError) CLI11_ERROR_SIMPLE(ValidationError) explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {} }; /// Thrown when a required option is missing class RequiredError : public ParseError { - CLI11_ERROR_DEF(ParseError, RequiredError) + CLI11_ERROR_DEF(ParseError, RequiredError) explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} static RequiredError Subcommand(std::size_t min_subcom) { if(min_subcom == 1) { return RequiredError("A subcommand"); } - return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", - ExitCodes::RequiredError); + return {"Requires at least " + std::to_string(min_subcom) + " subcommands", ExitCodes::RequiredError}; } static RequiredError Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) { if((min_option == 1) && (max_option == 1) && (used == 0)) return RequiredError("Exactly 1 option from [" + option_list + "]"); if((min_option == 1) && (max_option == 1) && (used > 1)) { - return RequiredError("Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) + - " were given", - ExitCodes::RequiredError); + return {"Exactly 1 option from [" + option_list + "] is required but " + std::to_string(used) + + " were given", + ExitCodes::RequiredError}; } if((min_option == 1) && (used == 0)) return RequiredError("At least 1 option from [" + option_list + "]"); if(used < min_option) { - return RequiredError("Requires at least " + std::to_string(min_option) + " options used and only " + - std::to_string(used) + "were given from [" + option_list + "]", - ExitCodes::RequiredError); + return {"Requires at least " + std::to_string(min_option) + " options used but only " + + std::to_string(used) + " were given from [" + option_list + "]", + ExitCodes::RequiredError}; } if(max_option == 1) - return RequiredError("Requires at most 1 options be given from [" + option_list + "]", - ExitCodes::RequiredError); + return {"Requires at most 1 options be given from [" + option_list + "]", ExitCodes::RequiredError}; - return RequiredError("Requires at most " + std::to_string(max_option) + " options be used and " + - std::to_string(used) + "were given from [" + option_list + "]", - ExitCodes::RequiredError); + return {"Requires at most " + std::to_string(max_option) + " options be used but " + std::to_string(used) + + " were given from [" + option_list + "]", + ExitCodes::RequiredError}; } }; /// Thrown when the wrong number of arguments has been received class ArgumentMismatch : public ParseError { - CLI11_ERROR_DEF(ParseError, ArgumentMismatch) + CLI11_ERROR_DEF(ParseError, ArgumentMismatch) CLI11_ERROR_SIMPLE(ArgumentMismatch) ArgumentMismatch(std::string name, int expected, std::size_t received) : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + @@ -777,41 +1496,45 @@ class ArgumentMismatch : public ParseError { static ArgumentMismatch FlagOverride(std::string name) { return ArgumentMismatch(name + " was given a disallowed flag override"); } + static ArgumentMismatch PartialType(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + type + " only partially specified: " + std::to_string(num) + + " required for each element"); + } }; /// Thrown when a requires option is missing class RequiresError : public ParseError { - CLI11_ERROR_DEF(ParseError, RequiresError) + CLI11_ERROR_DEF(ParseError, RequiresError) RequiresError(std::string curname, std::string subname) : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} }; /// Thrown when an excludes option is present class ExcludesError : public ParseError { - CLI11_ERROR_DEF(ParseError, ExcludesError) + CLI11_ERROR_DEF(ParseError, ExcludesError) ExcludesError(std::string curname, std::string subname) : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} }; /// Thrown when too many positionals or options are found class ExtrasError : public ParseError { - CLI11_ERROR_DEF(ParseError, ExtrasError) + CLI11_ERROR_DEF(ParseError, ExtrasError) explicit ExtrasError(std::vector args) : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " : "The following argument was not expected: ") + - detail::rjoin(args, " "), + detail::rjoin(args, " "), ExitCodes::ExtrasError) {} ExtrasError(const std::string &name, std::vector args) : ExtrasError(name, (args.size() > 1 ? "The following arguments were not expected: " : "The following argument was not expected: ") + - detail::rjoin(args, " "), + detail::rjoin(args, " "), ExitCodes::ExtrasError) {} }; /// Thrown when extra values are found in an INI file class ConfigError : public ParseError { - CLI11_ERROR_DEF(ParseError, ConfigError) + CLI11_ERROR_DEF(ParseError, ConfigError) CLI11_ERROR_SIMPLE(ConfigError) static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); } static ConfigError NotConfigurable(std::string item) { @@ -821,7 +1544,7 @@ class ConfigError : public ParseError { /// Thrown when validation fails before parsing class InvalidError : public ParseError { - CLI11_ERROR_DEF(ParseError, InvalidError) + CLI11_ERROR_DEF(ParseError, InvalidError) explicit InvalidError(std::string name) : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { } @@ -830,7 +1553,7 @@ class InvalidError : public ParseError { /// This is just a safety check to verify selection and parsing match - you should not ever see it /// Strings are directly added to this error, but again, it should never be seen. class HorribleError : public ParseError { - CLI11_ERROR_DEF(ParseError, HorribleError) + CLI11_ERROR_DEF(ParseError, HorribleError) CLI11_ERROR_SIMPLE(HorribleError) }; @@ -838,7 +1561,7 @@ class HorribleError : public ParseError { /// Thrown when counting a non-existent option class OptionNotFound : public Error { - CLI11_ERROR_DEF(Error, OptionNotFound) + CLI11_ERROR_DEF(Error, OptionNotFound) explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} }; @@ -847,11 +1570,8 @@ class OptionNotFound : public Error { /// @} -} // namespace CLI -// From TypeTools.hpp: -namespace CLI { // Type tools @@ -868,12 +1588,14 @@ constexpr enabler dummy = {}; /// A copy of enable_if_t from C++14, compatible with C++11. /// /// We could check to see if C++14 is being used, but it does not hurt to redefine this -/// (even Google does this: https://github.com/google/skia/blob/master/include/private/SkTLogic.h) +/// (even Google does this: https://github.com/google/skia/blob/main/include/private/SkTLogic.h) /// It is not in the std namespace anyway, so no harm done. template using enable_if_t = typename std::enable_if::type; /// A copy of std::void_t from C++17 (helper for C++11 and C++14) -template struct make_void { using type = void; }; +template struct make_void { + using type = void; +}; /// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine template using void_t = typename make_void::type; @@ -881,15 +1603,6 @@ template using void_t = typename make_void::type; /// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine template using conditional_t = typename std::conditional::type; -/// Check to see if something is a vector (fail check by default) -template struct is_vector : std::false_type {}; - -/// Check to see if something is a vector (true if actually a vector) -template struct is_vector> : std::true_type {}; - -/// Check to see if something is a vector (true if actually a const vector) -template struct is_vector> : std::true_type {}; - /// Check to see if something is bool (fail check by default) template struct is_bool : std::false_type {}; @@ -911,10 +1624,31 @@ template struct is_copyable_ptr { }; /// This can be specialized to override the type deduction for IsMember. -template struct IsMemberType { using type = T; }; +template struct IsMemberType { + using type = T; +}; /// The main custom type needed here is const char * should be a string. -template <> struct IsMemberType { using type = std::string; }; +template <> struct IsMemberType { + using type = std::string; +}; + +namespace adl_detail { +/// Check for existence of user-supplied lexical_cast. +/// +/// This struct has to be in a separate namespace so that it doesn't see our lexical_cast overloads in CLI::detail. +/// Standard says it shouldn't see them if it's defined before the corresponding lexical_cast declarations, but this +/// requires a working implementation of two-phase lookup, and not all compilers can boast that (msvc, ahem). +template class is_lexical_castable { + template + static auto test(int) -> decltype(lexical_cast(std::declval(), std::declval()), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; +} // namespace adl_detail namespace detail { @@ -924,7 +1658,9 @@ namespace detail { /// pointer_traits be valid. /// not a pointer -template struct element_type { using type = T; }; +template struct element_type { + using type = T; +}; template struct element_type::value>::type> { using type = typename std::pointer_traits::element_type; @@ -932,7 +1668,9 @@ template struct element_type struct element_value_type { using type = typename element_type::type::value_type; }; +template struct element_value_type { + using type = typename element_type::type::value_type; +}; /// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing. template struct pair_adaptor : std::false_type { @@ -987,14 +1725,22 @@ template class is_direct_constructible { static auto test(int, std::true_type) -> decltype( // NVCC warns about narrowing conversions here #ifdef __CUDACC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diag_suppress 2361 +#else #pragma diag_suppress 2361 #endif - TT { std::declval() } +#endif + TT{std::declval()} #ifdef __CUDACC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diag_default 2361 +#else #pragma diag_default 2361 #endif - , - std::is_move_assignable()); +#endif + , + std::is_move_assignable()); template static auto test(int, std::false_type) -> std::false_type; @@ -1031,6 +1777,17 @@ template class is_istreamable { static constexpr bool value = decltype(test(0))::value; }; +/// Check for complex +template class is_complex { + template + static auto test(int) -> decltype(std::declval().real(), std::declval().imag(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + /// Templated operation to get a value from a stream template ::value, detail::enabler> = detail::dummy> bool from_stream(const std::string &istring, T &obj) { @@ -1045,12 +1802,51 @@ bool from_stream(const std::string & /*istring*/, T & /*obj*/) { return false; } +// check to see if an object is a mutable container (fail by default) +template struct is_mutable_container : std::false_type {}; + +/// type trait to test if a type is a mutable container meaning it has a value_type, it has an iterator, a clear, and +/// end methods and an insert function. And for our purposes we exclude std::string and types that can be constructed +/// from a std::string +template +struct is_mutable_container< + T, + conditional_t().end()), + decltype(std::declval().clear()), + decltype(std::declval().insert(std::declval().end())>(), + std::declval()))>, + void>> : public conditional_t::value || + std::is_constructible::value, + std::false_type, + std::true_type> {}; + +// check to see if an object is a mutable container (fail by default) +template struct is_readable_container : std::false_type {}; + +/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an end +/// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from +/// a std::string +template +struct is_readable_container< + T, + conditional_t().end()), decltype(std::declval().begin())>, void>> + : public std::true_type {}; + +// check to see if an object is a wrapper (fail by default) +template struct is_wrapper : std::false_type {}; + +// check if an object is a wrapper (it has a value_type defined) +template +struct is_wrapper, void>> : public std::true_type {}; + // Check for tuple like types, as in classes with a tuple_size type trait template class is_tuple_like { template // static auto test(int) // -> decltype(std::conditional<(std::tuple_size::value > 0), std::true_type, std::false_type>::type()); - static auto test(int) -> decltype(std::tuple_size::value, std::true_type{}); + static auto test(int) -> decltype(std::tuple_size::type>::value, std::true_type{}); template static auto test(...) -> std::false_type; public: @@ -1065,17 +1861,17 @@ auto to_string(T &&value) -> decltype(std::forward(value)) { /// Construct a string from the object template ::value && !std::is_convertible::value, - detail::enabler> = detail::dummy> + enable_if_t::value && !std::is_convertible::value, + detail::enabler> = detail::dummy> std::string to_string(const T &value) { - return std::string(value); + return std::string(value); // NOLINT(google-readability-casting) } /// Convert an object to a string (streaming must be supported for that type) template ::value && !std::is_constructible::value && - is_ostreamable::value, - detail::enabler> = detail::dummy> + enable_if_t::value && !std::is_constructible::value && + is_ostreamable::value, + detail::enabler> = detail::dummy> std::string to_string(T &&value) { std::stringstream stream; stream << value; @@ -1084,44 +1880,46 @@ std::string to_string(T &&value) { /// If conversion is not supported, return an empty string (streaming is not supported for that type) template ::value && !is_ostreamable::value && - !is_vector::type>::type>::value, - detail::enabler> = detail::dummy> + enable_if_t::value && !is_ostreamable::value && + !is_readable_container::type>::value, + detail::enabler> = detail::dummy> std::string to_string(T &&) { - return std::string{}; + return {}; } -/// convert a vector to a string +/// convert a readable container to a string template ::value && !is_ostreamable::value && - is_vector::type>::type>::value, - detail::enabler> = detail::dummy> + enable_if_t::value && !is_ostreamable::value && + is_readable_container::value, + detail::enabler> = detail::dummy> std::string to_string(T &&variable) { - std::vector defaults; - defaults.reserve(variable.size()); auto cval = variable.begin(); auto end = variable.end(); + if(cval == end) { + return {"{}"}; + } + std::vector defaults; while(cval != end) { defaults.emplace_back(CLI::detail::to_string(*cval)); ++cval; } - return std::string("[" + detail::join(defaults) + "]"); + return {"[" + detail::join(defaults) + "]"}; } /// special template overload template ::value, detail::enabler> = detail::dummy> + typename T2, + typename T, + enable_if_t::value, detail::enabler> = detail::dummy> auto checked_to_string(T &&value) -> decltype(to_string(std::forward(value))) { return to_string(std::forward(value)); } /// special template overload template ::value, detail::enabler> = detail::dummy> + typename T2, + typename T, + enable_if_t::value, detail::enabler> = detail::dummy> std::string checked_to_string(T &&) { return std::string{}; } @@ -1137,85 +1935,235 @@ std::string value_string(const T &value) { } /// for other types just use the regular to_string function template ::value && !std::is_arithmetic::value, detail::enabler> = detail::dummy> + enable_if_t::value && !std::is_arithmetic::value, detail::enabler> = detail::dummy> auto value_string(const T &value) -> decltype(to_string(value)) { return to_string(value); } -/// This will only trigger for actual void type -template struct type_count { static const int value{0}; }; +/// template to get the underlying value type if it exists or use a default +template struct wrapped_type { + using type = def; +}; -/// Set of overloads to get the type size of an object -template struct type_count::value>::type> { - static constexpr int value{std::tuple_size::value}; +/// Type size for regular object types that do not look like a tuple +template struct wrapped_type::value>::type> { + using type = typename T::value_type; +}; + +/// This will only trigger for actual void type +template struct type_count_base { + static const int value{0}; }; + /// Type size for regular object types that do not look like a tuple template -struct type_count< - T, - typename std::enable_if::value && !is_tuple_like::value && !std::is_void::value>::type> { +struct type_count_base::value && !is_mutable_container::value && + !std::is_void::value>::type> { static constexpr int value{1}; }; -/// Type size of types that look like a vector -template struct type_count::value>::type> { - static constexpr int value{is_vector::value ? expected_max_vector_size - : type_count::value}; +/// the base tuple size +template +struct type_count_base::value && !is_mutable_container::value>::type> { + static constexpr int value{std::tuple_size::value}; +}; + +/// Type count base for containers is the type_count_base of the individual element +template struct type_count_base::value>::type> { + static constexpr int value{type_count_base::value}; }; +/// Set of overloads to get the type size of an object + +/// forward declare the subtype_count structure +template struct subtype_count; + +/// forward declare the subtype_count_min structure +template struct subtype_count_min; + /// This will only trigger for actual void type -template struct expected_count { static const int value{0}; }; +template struct type_count { + static const int value{0}; +}; -/// For most types the number of expected items is 1 +/// Type size for regular object types that do not look like a tuple template -struct expected_count::value && !std::is_void::value>::type> { +struct type_count::value && !is_tuple_like::value && !is_complex::value && + !std::is_void::value>::type> { static constexpr int value{1}; }; -/// number of expected items in a vector -template struct expected_count::value>::type> { - static constexpr int value{expected_max_vector_size}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count::value>::type> { + static constexpr int value{2}; }; -// Enumeration of the different supported categorizations of objects -enum class object_category : int { - integral_value = 2, - unsigned_integral = 4, +/// Type size of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template struct type_count::value>::type> { + static constexpr int value{subtype_count::value}; +}; + +/// Type size of types that are wrappers,except containers complex and tuples(which can also be wrappers sometimes) +template +struct type_count::value && !is_complex::value && !is_tuple_like::value && + !is_mutable_container::value>::type> { + static constexpr int value{type_count::value}; +}; + +/// 0 if the index > tuple size +template +constexpr typename std::enable_if::value, int>::type tuple_type_size() { + return 0; +} + +/// Recursively generate the tuple type name +template + constexpr typename std::enable_if < I::value, int>::type tuple_type_size() { + return subtype_count::type>::value + tuple_type_size(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template struct type_count::value>::type> { + static constexpr int value{tuple_type_size()}; +}; + +/// definition of subtype count +template struct subtype_count { + static constexpr int value{is_mutable_container::value ? expected_max_vector_size : type_count::value}; +}; + +/// This will only trigger for actual void type +template struct type_count_min { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_min< + T, + typename std::enable_if::value && !is_tuple_like::value && !is_wrapper::value && + !is_complex::value && !std::is_void::value>::type> { + static constexpr int value{type_count::value}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count_min::value>::type> { + static constexpr int value{1}; +}; + +/// Type size min of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template +struct type_count_min< + T, + typename std::enable_if::value && !is_complex::value && !is_tuple_like::value>::type> { + static constexpr int value{subtype_count_min::value}; +}; + +/// 0 if the index > tuple size +template +constexpr typename std::enable_if::value, int>::type tuple_type_size_min() { + return 0; +} + +/// Recursively generate the tuple type name +template + constexpr typename std::enable_if < I::value, int>::type tuple_type_size_min() { + return subtype_count_min::type>::value + tuple_type_size_min(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template struct type_count_min::value>::type> { + static constexpr int value{tuple_type_size_min()}; +}; + +/// definition of subtype count +template struct subtype_count_min { + static constexpr int value{is_mutable_container::value + ? ((type_count::value < expected_max_vector_size) ? type_count::value : 0) + : type_count_min::value}; +}; + +/// This will only trigger for actual void type +template struct expected_count { + static const int value{0}; +}; + +/// For most types the number of expected items is 1 +template +struct expected_count::value && !is_wrapper::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; +/// number of expected items in a vector +template struct expected_count::value>::type> { + static constexpr int value{expected_max_vector_size}; +}; + +/// number of expected items in a vector +template +struct expected_count::value && is_wrapper::value>::type> { + static constexpr int value{expected_count::value}; +}; + +// Enumeration of the different supported categorizations of objects +enum class object_category : int { + char_value = 1, + integral_value = 2, + unsigned_integral = 4, enumeration = 6, boolean_value = 8, floating_point = 10, number_constructible = 12, double_constructible = 14, integer_constructible = 16, - vector_value = 30, - tuple_value = 35, - // string assignable or greater used in a condition so anything string like must come last - string_assignable = 50, - string_constructible = 60, - other = 200, + // string like types + string_assignable = 23, + string_constructible = 24, + wstring_assignable = 25, + wstring_constructible = 26, + other = 45, + // special wrapper or container types + wrapper_value = 50, + complex_number = 60, + tuple_value = 70, + container_value = 80, }; +/// Set of overloads to classify an object according to type + /// some type that is not otherwise recognized template struct classify_object { static constexpr object_category value{object_category::other}; }; -/// Set of overloads to classify an object according to type +/// Signed integers template -struct classify_object::value && std::is_signed::value && +struct classify_object< + T, + typename std::enable_if::value && !std::is_same::value && std::is_signed::value && !is_bool::value && !std::is_enum::value>::type> { static constexpr object_category value{object_category::integral_value}; }; /// Unsigned integers template -struct classify_object< - T, - typename std::enable_if::value && std::is_unsigned::value && !is_bool::value>::type> { +struct classify_object::value && std::is_unsigned::value && + !std::is_same::value && !is_bool::value>::type> { static constexpr object_category value{object_category::unsigned_integral}; }; +/// single character values +template +struct classify_object::value && !std::is_enum::value>::type> { + static constexpr object_category value{object_category::char_value}; +}; + /// Boolean values template struct classify_object::value>::type> { static constexpr object_category value{object_category::boolean_value}; @@ -1225,13 +2173,23 @@ template struct classify_object struct classify_object::value>::type> { static constexpr object_category value{object_category::floating_point}; }; +#if defined _MSC_VER +// in MSVC wstring should take precedence if available this isn't as useful on other compilers due to the broader use of +// utf-8 encoding +#define WIDE_STRING_CHECK \ + !std::is_assignable::value && !std::is_constructible::value +#define STRING_CHECK true +#else +#define WIDE_STRING_CHECK true +#define STRING_CHECK !std::is_assignable::value && !std::is_constructible::value +#endif /// String and similar direct assignment template struct classify_object< T, - typename std::enable_if::value && !std::is_integral::value && - std::is_assignable::value && !is_vector::value>::type> { + typename std::enable_if::value && !std::is_integral::value && WIDE_STRING_CHECK && + std::is_assignable::value>::type> { static constexpr object_category value{object_category::string_assignable}; }; @@ -1240,68 +2198,105 @@ template struct classify_object< T, typename std::enable_if::value && !std::is_integral::value && - !std::is_assignable::value && - std::is_constructible::value && !is_vector::value>::type> { + !std::is_assignable::value && (type_count::value == 1) && + WIDE_STRING_CHECK && std::is_constructible::value>::type> { static constexpr object_category value{object_category::string_constructible}; }; +/// Wide strings +template +struct classify_object::value && !std::is_integral::value && + STRING_CHECK && std::is_assignable::value>::type> { + static constexpr object_category value{object_category::wstring_assignable}; +}; + +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + !std::is_assignable::value && (type_count::value == 1) && + STRING_CHECK && std::is_constructible::value>::type> { + static constexpr object_category value{object_category::wstring_constructible}; +}; + /// Enumerations template struct classify_object::value>::type> { static constexpr object_category value{object_category::enumeration}; }; +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::complex_number}; +}; + /// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, /// vectors, and enumerations template struct uncommon_type { - using type = typename std::conditional::value && !std::is_integral::value && - !std::is_assignable::value && - !std::is_constructible::value && !is_vector::value && - !std::is_enum::value, + using type = typename std::conditional< + !std::is_floating_point::value && !std::is_integral::value && + !std::is_assignable::value && !std::is_constructible::value && + !std::is_assignable::value && !std::is_constructible::value && + !is_complex::value && !is_mutable_container::value && !std::is_enum::value, std::true_type, std::false_type>::type; static constexpr bool value = type::value; }; +/// wrapper type +template +struct classify_object::value && is_wrapper::value && + !is_tuple_like::value && uncommon_type::value)>::type> { + static constexpr object_category value{object_category::wrapper_value}; +}; + /// Assignable from double or int template struct classify_object::value && type_count::value == 1 && - is_direct_constructible::value && - is_direct_constructible::value>::type> { + typename std::enable_if::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && + is_direct_constructible::value>::type> { static constexpr object_category value{object_category::number_constructible}; }; /// Assignable from int template struct classify_object::value && type_count::value == 1 && - !is_direct_constructible::value && - is_direct_constructible::value>::type> { + typename std::enable_if::value && type_count::value == 1 && + !is_wrapper::value && !is_direct_constructible::value && + is_direct_constructible::value>::type> { static constexpr object_category value{object_category::integer_constructible}; }; /// Assignable from double template struct classify_object::value && type_count::value == 1 && - is_direct_constructible::value && - !is_direct_constructible::value>::type> { + typename std::enable_if::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && + !is_direct_constructible::value>::type> { static constexpr object_category value{object_category::double_constructible}; }; /// Tuple type template -struct classify_object::value >= 2 && !is_vector::value) || - (is_tuple_like::value && uncommon_type::value && - !is_direct_constructible::value && - !is_direct_constructible::value)>::type> { +struct classify_object< + T, + typename std::enable_if::value && + ((type_count::value >= 2 && !is_wrapper::value) || + (uncommon_type::value && !is_direct_constructible::value && + !is_direct_constructible::value) || + (uncommon_type::value && type_count::value >= 2))>::type> { static constexpr object_category value{object_category::tuple_value}; + // the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be + // constructed from just the first element so tuples of can be constructed from a string, which + // could lead to issues so there are two variants of the condition, the first isolates things with a type size >=2 + // mainly to get tuples on Xcode with the exception of wrappers, the second is the main one and just separating out + // those cases that are caught by other object classifications }; -/// Vector type -template struct classify_object::value>::type> { - static constexpr object_category value{object_category::vector_value}; +/// container type +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::container_value}; }; // Type name print @@ -1311,67 +2306,95 @@ template struct classify_object::value == object_category::integral_value || - classify_object::value == object_category::integer_constructible, - detail::enabler> = detail::dummy> + enable_if_t::value == object_category::char_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "CHAR"; +} + +template ::value == object_category::integral_value || + classify_object::value == object_category::integer_constructible, + detail::enabler> = detail::dummy> constexpr const char *type_name() { return "INT"; } template ::value == object_category::unsigned_integral, detail::enabler> = detail::dummy> + enable_if_t::value == object_category::unsigned_integral, detail::enabler> = detail::dummy> constexpr const char *type_name() { return "UINT"; } template ::value == object_category::floating_point || - classify_object::value == object_category::number_constructible || - classify_object::value == object_category::double_constructible, - detail::enabler> = detail::dummy> + enable_if_t::value == object_category::floating_point || + classify_object::value == object_category::number_constructible || + classify_object::value == object_category::double_constructible, + detail::enabler> = detail::dummy> constexpr const char *type_name() { return "FLOAT"; } /// Print name for enumeration types template ::value == object_category::enumeration, detail::enabler> = detail::dummy> + enable_if_t::value == object_category::enumeration, detail::enabler> = detail::dummy> constexpr const char *type_name() { return "ENUM"; } /// Print name for enumeration types template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> + enable_if_t::value == object_category::boolean_value, detail::enabler> = detail::dummy> constexpr const char *type_name() { return "BOOLEAN"; } +/// Print name for enumeration types +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "COMPLEX"; +} + /// Print for all other types template ::value >= object_category::string_assignable, detail::enabler> = detail::dummy> + enable_if_t::value >= object_category::string_assignable && + classify_object::value <= object_category::other, + detail::enabler> = detail::dummy> constexpr const char *type_name() { return "TEXT"; } +/// typename for tuple value +template ::value == object_category::tuple_value && type_count_base::value >= 2, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Generate type name for a wrapper or container value +template ::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration /// Print name for single element tuple types template ::value == object_category::tuple_value && type_count::value == 1, - detail::enabler> = detail::dummy> + enable_if_t::value == object_category::tuple_value && type_count_base::value == 1, + detail::enabler> = detail::dummy> inline std::string type_name() { - return type_name::type>(); + return type_name::type>::type>(); } /// Empty string if the index > tuple size template -inline typename std::enable_if::value, std::string>::type tuple_name() { +inline typename std::enable_if::value, std::string>::type tuple_name() { return std::string{}; } /// Recursively generate the tuple type name template -inline typename std::enable_if < I::value, std::string>::type tuple_name() { - std::string str = std::string(type_name::type>()) + ',' + tuple_name(); +inline typename std::enable_if<(I < type_count_base::value), std::string>::type tuple_name() { + auto str = std::string{type_name::type>::type>()} + ',' + + tuple_name(); if(str.back() == ',') str.pop_back(); return str; @@ -1379,25 +2402,130 @@ inline typename std::enable_if < I::value, std::string>::type tupl /// Print type name for tuples with 2 or more elements template ::value == object_category::tuple_value && type_count::value >= 2, - detail::enabler> = detail::dummy> -std::string type_name() { + enable_if_t::value == object_category::tuple_value && type_count_base::value >= 2, + detail::enabler>> +inline std::string type_name() { auto tname = std::string(1, '[') + tuple_name(); tname.push_back(']'); return tname; } -/// This one should not be used normally, since vector types print the internal type +/// get the type name for a type that has a value_type member template ::value == object_category::vector_value, detail::enabler> = detail::dummy> + enable_if_t::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler>> inline std::string type_name() { return type_name(); } // Lexical cast -/// Convert a flag into an integer value typically binary flags -inline std::int64_t to_flag_value(std::string val) { +/// Convert to an unsigned integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty() || input.front() == '-') { + return false; + } + char *val{nullptr}; + errno = 0; + std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + val = nullptr; + std::int64_t output_sll = std::strtoll(input.c_str(), &val, 0); + if(val == (input.c_str() + input.size())) { + output = (output_sll < 0) ? static_cast(0) : static_cast(output_sll); + return (static_cast(output) == output_sll); + } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return integral_conversion(nstring, output); + } + if(input.compare(0, 2, "0o") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoull(input.c_str() + 2, &val, 8); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + if(input.compare(0, 2, "0b") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoull(input.c_str() + 2, &val, 2); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + return false; +} + +/// Convert to a signed integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty()) { + return false; + } + char *val = nullptr; + errno = 0; + std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + if(input == "true") { + // this is to deal with a few oddities with flags and wrapper int types + output = static_cast(1); + return true; + } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return integral_conversion(nstring, output); + } + if(input.compare(0, 2, "0o") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoll(input.c_str() + 2, &val, 8); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + if(input.compare(0, 2, "0b") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoll(input.c_str() + 2, &val, 2); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + return false; +} + +/// Convert a flag into an integer value typically binary flags sets errno to nonzero if conversion failed +inline std::int64_t to_flag_value(std::string val) noexcept { static const std::string trueString("true"); static const std::string falseString("false"); if(val == trueString) { @@ -1407,7 +2535,7 @@ inline std::int64_t to_flag_value(std::string val) { return -1; } val = detail::to_lower(val); - std::int64_t ret; + std::int64_t ret = 0; if(val.size() == 1) { if(val[0] >= '1' && val[0] <= '9') { return (static_cast(val[0]) - '0'); @@ -1425,7 +2553,8 @@ inline std::int64_t to_flag_value(std::string val) { ret = 1; break; default: - throw std::invalid_argument("unrecognized character"); + errno = EINVAL; + return -1; } return ret; } @@ -1434,82 +2563,109 @@ inline std::int64_t to_flag_value(std::string val) { } else if(val == falseString || val == "off" || val == "no" || val == "disable") { ret = -1; } else { - ret = std::stoll(val); + char *loc_ptr{nullptr}; + ret = std::strtoll(val.c_str(), &loc_ptr, 0); + if(loc_ptr != (val.c_str() + val.size()) && errno == 0) { + errno = EINVAL; + } } return ret; } -/// Signed integers +/// Integer conversion template ::value == object_category::integral_value, detail::enabler> = detail::dummy> + enable_if_t::value == object_category::integral_value || + classify_object::value == object_category::unsigned_integral, + detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { - try { - std::size_t n = 0; - std::int64_t output_ll = std::stoll(input, &n, 0); - output = static_cast(output_ll); - return n == input.size() && static_cast(output) == output_ll; - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { - return false; - } + return integral_conversion(input, output); } -/// Unsigned integers +/// char values template ::value == object_category::unsigned_integral, detail::enabler> = detail::dummy> + enable_if_t::value == object_category::char_value, detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { - if(!input.empty() && input.front() == '-') - return false; // std::stoull happily converts negative values to junk without any errors. - - try { - std::size_t n = 0; - std::uint64_t output_ll = std::stoull(input, &n, 0); - output = static_cast(output_ll); - return n == input.size() && static_cast(output) == output_ll; - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { - return false; + if(input.size() == 1) { + output = static_cast(input[0]); + return true; } + return integral_conversion(input, output); } /// Boolean values template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> + enable_if_t::value == object_category::boolean_value, detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { - try { - auto out = to_flag_value(input); + errno = 0; + auto out = to_flag_value(input); + if(errno == 0) { output = (out > 0); - return true; - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { - // if the number is out of the range of a 64 bit value then it is still a number and for this purpose is still - // valid all we care about the sign + } else if(errno == ERANGE) { output = (input[0] != '-'); - return true; + } else { + return false; } + return true; } /// Floats template ::value == object_category::floating_point, detail::enabler> = detail::dummy> + enable_if_t::value == object_category::floating_point, detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { - try { - std::size_t n = 0; - output = static_cast(std::stold(input, &n)); - return n == input.size(); - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { + if(input.empty()) { return false; } + char *val = nullptr; + auto output_ld = std::strtold(input.c_str(), &val); + output = static_cast(output_ld); + if(val == (input.c_str() + input.size())) { + return true; + } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return lexical_cast(nstring, output); + } + return false; +} + +/// complex +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + using XC = typename wrapped_type::type; + XC x{0.0}, y{0.0}; + auto str1 = input; + bool worked = false; + auto nloc = str1.find_last_of("+-"); + if(nloc != std::string::npos && nloc > 0) { + worked = lexical_cast(str1.substr(0, nloc), x); + str1 = str1.substr(nloc); + if(str1.back() == 'i' || str1.back() == 'j') + str1.pop_back(); + worked = worked && lexical_cast(str1, y); + } else { + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + worked = lexical_cast(str1, y); + x = XC{0}; + } else { + worked = lexical_cast(str1, x); + y = XC{0}; + } + } + if(worked) { + output = T{x, y}; + return worked; + } + return from_stream(input, output); } /// String and similar direct assignment template ::value == object_category::string_assignable, detail::enabler> = detail::dummy> + enable_if_t::value == object_category::string_assignable, detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { output = input; return true; @@ -1524,35 +2680,79 @@ bool lexical_cast(const std::string &input, T &output) { return true; } +/// Wide strings +template < + typename T, + enable_if_t::value == object_category::wstring_assignable, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = widen(input); + return true; +} + +template < + typename T, + enable_if_t::value == object_category::wstring_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = T{widen(input)}; + return true; +} + /// Enumerations template ::value == object_category::enumeration, detail::enabler> = detail::dummy> + enable_if_t::value == object_category::enumeration, detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { typename std::underlying_type::type val; - bool retval = detail::lexical_cast(input, val); - if(!retval) { + if(!integral_conversion(input, val)) { return false; } output = static_cast(val); return true; } +/// wrapper types +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = val; + return true; + } + return from_stream(input, output); +} + +template ::value == object_category::wrapper_value && + !std::is_assignable::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + /// Assignable from double or int template < typename T, enable_if_t::value == object_category::number_constructible, detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { - int val; - if(lexical_cast(input, val)) { + int val = 0; + if(integral_conversion(input, val)) { output = T(val); return true; - } else { - double dval; - if(lexical_cast(input, dval)) { - output = T{dval}; - return true; - } } + + double dval = 0.0; + if(lexical_cast(input, dval)) { + output = T{dval}; + return true; + } + return from_stream(input, output); } @@ -1561,8 +2761,8 @@ template < typename T, enable_if_t::value == object_category::integer_constructible, detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { - int val; - if(lexical_cast(input, val)) { + int val = 0; + if(integral_conversion(input, val)) { output = T(val); return true; } @@ -1574,7 +2774,7 @@ template < typename T, enable_if_t::value == object_category::double_constructible, detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { - double val; + double val = 0.0; if(lexical_cast(input, val)) { output = T{val}; return true; @@ -1582,277 +2782,559 @@ bool lexical_cast(const std::string &input, T &output) { return from_stream(input, output); } +/// Non-string convertible from an int +template ::value == object_category::other && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val = 0; + if(integral_conversion(input, val)) { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4800) +#endif + // with Atomic this could produce a warning due to the conversion but if atomic gets here it is an old style + // so will most likely still work + output = val; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + return true; + } + // LCOV_EXCL_START + // This version of cast is only used for odd cases in an older compilers the fail over + // from_stream is tested elsewhere an not relevant for coverage here + return from_stream(input, output); + // LCOV_EXCL_STOP +} + /// Non-string parsable by a stream -template ::value == object_category::other, detail::enabler> = detail::dummy> +template ::value == object_category::other && !std::is_assignable::value && + is_istreamable::value, + detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { - static_assert(is_istreamable::value, + return from_stream(input, output); +} + +/// Fallback overload that prints a human-readable error for types that we don't recognize and that don't have a +/// user-supplied lexical_cast overload. +template ::value == object_category::other && !std::is_assignable::value && + !is_istreamable::value && !adl_detail::is_lexical_castable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string & /*input*/, T & /*output*/) { + static_assert(!std::is_same::value, // Can't just write false here. "option object type must have a lexical cast overload or streaming input operator(>>) defined, if it " "is convertible from another type use the add_option(...) with XC being the known type"); - return from_stream(input, output); + return false; } /// Assign a value through lexical cast operations -template < - typename T, - typename XC, - enable_if_t::value && (classify_object::value == object_category::string_assignable || - classify_object::value == object_category::string_constructible), - detail::enabler> = detail::dummy> -bool lexical_assign(const std::string &input, T &output) { +/// Strings can be empty so we need to do a little different +template ::value && + (classify_object::value == object_category::string_assignable || + classify_object::value == object_category::string_constructible || + classify_object::value == object_category::wstring_assignable || + classify_object::value == object_category::wstring_constructible), + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { return lexical_cast(input, output); } /// Assign a value through lexical cast operations -template ::value && classify_object::value != object_category::string_assignable && - classify_object::value != object_category::string_constructible, - detail::enabler> = detail::dummy> -bool lexical_assign(const std::string &input, T &output) { +template ::value && std::is_assignable::value && + classify_object::value != object_category::string_assignable && + classify_object::value != object_category::string_constructible && + classify_object::value != object_category::wstring_assignable && + classify_object::value != object_category::wstring_constructible, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { if(input.empty()) { - output = T{}; + output = AssignTo{}; return true; } + return lexical_cast(input, output); } -/// Assign a value converted from a string in lexical cast to the output value directly -template < - typename T, - typename XC, - enable_if_t::value && std::is_assignable::value, detail::enabler> = detail::dummy> -bool lexical_assign(const std::string &input, T &output) { - XC val{}; - bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true; - if(parse_result) { - output = val; +/// Assign a value through lexical cast operations +template ::value && !std::is_assignable::value && + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + typename AssignTo::value_type emptyVal{}; + output = emptyVal; + return true; } - return parse_result; + return lexical_cast(input, output); } -/// Assign a value from a lexical cast through constructing a value and move assigning it -template ::value && !std::is_assignable::value && - std::is_move_assignable::value, - detail::enabler> = detail::dummy> -bool lexical_assign(const std::string &input, T &output) { - XC val{}; - bool parse_result = input.empty() ? true : lexical_cast(input, val); +/// Assign a value through lexical cast operations for int compatible values +/// mainly for atomic operations on some compilers +template ::value && !std::is_assignable::value && + classify_object::value != object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = 0; + return true; + } + int val{0}; + if(lexical_cast(input, val)) { +#if defined(__clang__) +/* on some older clang compilers */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-conversion" +#endif + output = val; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + return true; + } + return false; +} + +/// Assign a value converted from a string in lexical cast to the output value directly +template ::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true; if(parse_result) { - output = T(val); // use () form of constructor to allow some implicit conversions + output = val; } return parse_result; } -/// Lexical conversion if there is only one element + +/// Assign a value from a lexical cast through constructing a value and move assigning it template < - typename T, - typename XC, - enable_if_t::value && !is_tuple_like::value && !is_vector::value && !is_vector::value, - detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector &strings, T &output) { - return lexical_assign(strings[0], output); + typename AssignTo, + typename ConvertTo, + enable_if_t::value && !std::is_assignable::value && + std::is_move_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = input.empty() ? true : lexical_cast(input, val); + if(parse_result) { + output = AssignTo(val); // use () form of constructor to allow some implicit conversions + } + return parse_result; } -/// Lexical conversion if there is only one element but the conversion type is for two call a two element constructor -template ::value == 1 && type_count::value == 2, detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector &strings, T &output) { - typename std::tuple_element<0, XC>::type v1; - typename std::tuple_element<1, XC>::type v2; - bool retval = lexical_assign(strings[0], v1); - if(strings.size() > 1) { - retval = retval && lexical_assign(strings[1], v2); - } +/// primary lexical conversion operation, 1 string to 1 type of some kind +template ::value <= object_category::other && + classify_object::value <= object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + return lexical_assign(strings[0], output); +} + +/// Lexical conversion if there is only one element but the conversion type is for two, then call a two element +/// constructor +template ::value <= 2) && expected_count::value == 1 && + is_tuple_like::value && type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + // the remove const is to handle pair types coming from a container + using FirstType = typename std::remove_const::type>::type; + using SecondType = typename std::tuple_element<1, ConvertTo>::type; + FirstType v1; + SecondType v2; + bool retval = lexical_assign(strings[0], v1); + retval = retval && lexical_assign((strings.size() > 1) ? strings[1] : std::string{}, v2); if(retval) { - output = T{v1, v2}; + output = AssignTo{v1, v2}; } return retval; } -/// Lexical conversion of a vector types -template ::value == expected_max_vector_size && - expected_count::value == expected_max_vector_size && type_count::value == 1, - detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector &strings, T &output) { - output.clear(); - output.reserve(strings.size()); +/// Lexical conversion of a container types of single elements +template ::value && is_mutable_container::value && + type_count::value == 1, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + output.erase(output.begin(), output.end()); + if(strings.empty()) { + return true; + } + if(strings.size() == 1 && strings[0] == "{}") { + return true; + } + bool skip_remaining = false; + if(strings.size() == 2 && strings[0] == "{}" && is_separator(strings[1])) { + skip_remaining = true; + } for(const auto &elem : strings) { - - output.emplace_back(); - bool retval = lexical_assign(elem, output.back()); + typename AssignTo::value_type out; + bool retval = lexical_assign(elem, out); if(!retval) { return false; } + output.insert(output.end(), std::move(out)); + if(skip_remaining) { + break; + } } return (!output.empty()); } -/// Lexical conversion of a vector types with type size of two -template ::value == expected_max_vector_size && - expected_count::value == expected_max_vector_size && type_count::value == 2, - detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector &strings, T &output) { - output.clear(); - for(std::size_t ii = 0; ii < strings.size(); ii += 2) { +/// Lexical conversion for complex types +template ::value, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { - typename std::tuple_element<0, typename XC::value_type>::type v1; - typename std::tuple_element<1, typename XC::value_type>::type v2; - bool retval = lexical_assign(strings[ii], v1); - if(strings.size() > ii + 1) { - retval = retval && lexical_assign(strings[ii + 1], v2); + if(strings.size() >= 2 && !strings[1].empty()) { + using XC2 = typename wrapped_type::type; + XC2 x{0.0}, y{0.0}; + auto str1 = strings[1]; + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); } - if(retval) { - output.emplace_back(v1, v2); - } else { - return false; + auto worked = lexical_cast(strings[0], x) && lexical_cast(str1, y); + if(worked) { + output = ConvertTo{x, y}; } + return worked; } - return (!output.empty()); + return lexical_assign(strings[0], output); } /// Conversion to a vector type using a particular single type as the conversion type -template ::value == expected_max_vector_size) && (expected_count::value == 1) && - (type_count::value == 1), - detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector &strings, T &output) { +template ::value && (expected_count::value == 1) && + (type_count::value == 1), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { bool retval = true; output.clear(); output.reserve(strings.size()); for(const auto &elem : strings) { output.emplace_back(); - retval = retval && lexical_assign(elem, output.back()); + retval = retval && lexical_assign(elem, output.back()); } return (!output.empty()) && retval; } -// This one is last since it can call other lexical_conversion functions -/// Lexical conversion if there is only one element but the conversion type is a vector -template ::value && !is_vector::value && is_vector::value, detail::enabler> = - detail::dummy> -bool lexical_conversion(const std::vector &strings, T &output) { + +// forward declaration + +/// Lexical conversion of a container types with conversion type of two elements +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(std::vector strings, AssignTo &output); + +/// Lexical conversion of a vector types with type_size >2 forward declaration +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); + +/// Conversion for tuples +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); // forward declaration + +/// Conversion for operations where the assigned type is some class but the conversion is a mutable container or large +/// tuple +template ::value && !is_mutable_container::value && + classify_object::value != object_category::wrapper_value && + (is_mutable_container::value || type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) { - XC val; - auto retval = lexical_conversion(strings, val); - output = T{val}; + ConvertTo val; + auto retval = lexical_conversion(strings, val); + output = AssignTo{val}; return retval; } - output = T{}; + output = AssignTo{}; return true; } /// function template for converting tuples if the static Index is greater than the tuple size -template -inline typename std::enable_if= type_count::value, bool>::type tuple_conversion(const std::vector &, - T &) { +template +inline typename std::enable_if<(I >= type_count_base::value), bool>::type +tuple_conversion(const std::vector &, AssignTo &) { return true; } + +/// Conversion of a tuple element where the type size ==1 and not a mutable container +template +inline typename std::enable_if::value && type_count::value == 1, bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_assign(strings[0], output); + strings.erase(strings.begin()); + return retval; +} + +/// Conversion of a tuple element where the type size !=1 but the size is fixed and not a mutable container +template +inline typename std::enable_if::value && (type_count::value > 1) && + type_count::value == type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_conversion(strings, output); + strings.erase(strings.begin(), strings.begin() + type_count::value); + return retval; +} + +/// Conversion of a tuple element where the type is a mutable container or a type with different min and max type sizes +template +inline typename std::enable_if::value || + type_count::value != type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + + std::size_t index{subtype_count_min::value}; + const std::size_t mx_count{subtype_count::value}; + const std::size_t mx{(std::min)(mx_count, strings.size() - 1)}; + + while(index < mx) { + if(is_separator(strings[index])) { + break; + } + ++index; + } + bool retval = lexical_conversion( + std::vector(strings.begin(), strings.begin() + static_cast(index)), output); + if(strings.size() > index) { + strings.erase(strings.begin(), strings.begin() + static_cast(index) + 1); + } else { + strings.clear(); + } + return retval; +} + /// Tuple conversion operation -template -inline typename std::enable_if < - I::value, bool>::type tuple_conversion(const std::vector &strings, T &output) { +template +inline typename std::enable_if<(I < type_count_base::value), bool>::type +tuple_conversion(std::vector strings, AssignTo &output) { bool retval = true; - if(strings.size() > I) { - retval = retval && lexical_assign::type, - typename std::conditional::value, - typename std::tuple_element::type, - XC>::type>(strings[I], std::get(output)); + using ConvertToElement = typename std:: + conditional::value, typename std::tuple_element::type, ConvertTo>::type; + if(!strings.empty()) { + retval = retval && tuple_type_conversion::type, ConvertToElement>( + strings, std::get(output)); } - retval = retval && tuple_conversion(strings, output); + retval = retval && tuple_conversion(std::move(strings), output); return retval; } -/// Conversion for tuples -template ::value, detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector &strings, T &output) { - static_assert( - !is_tuple_like::value || type_count::value == type_count::value, - "if the conversion type is defined as a tuple it must be the same size as the type you are converting to"); - return tuple_conversion(strings, output); +/// Lexical conversion of a container types with tuple elements of size 2 +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler>> +bool lexical_conversion(std::vector strings, AssignTo &output) { + output.clear(); + while(!strings.empty()) { + + typename std::remove_const::type>::type v1; + typename std::tuple_element<1, typename ConvertTo::value_type>::type v2; + bool retval = tuple_type_conversion(strings, v1); + if(!strings.empty()) { + retval = retval && tuple_type_conversion(strings, v2); + } + if(retval) { + output.insert(output.end(), typename AssignTo::value_type{v1, v2}); + } else { + return false; + } + } + return (!output.empty()); } -/// Lexical conversion of a vector types with type_size >2 -template ::value == expected_max_vector_size && - expected_count::value == expected_max_vector_size && (type_count::value > 2), - detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector &strings, T &output) { +/// lexical conversion of tuples with type count>2 or tuples of types of some element with a type size>=2 +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + static_assert( + !is_tuple_like::value || type_count_base::value == type_count_base::value, + "if the conversion type is defined as a tuple it must be the same size as the type you are converting to"); + return tuple_conversion(strings, output); +} + +/// Lexical conversion of a vector types for everything but tuples of two elements and types of size 1 +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { bool retval = true; output.clear(); std::vector temp; - std::size_t ii = 0; - std::size_t icount = 0; - std::size_t xcm = type_count::value; - while(ii < strings.size()) { + std::size_t ii{0}; + std::size_t icount{0}; + std::size_t xcm{type_count::value}; + auto ii_max = strings.size(); + while(ii < ii_max) { temp.push_back(strings[ii]); ++ii; ++icount; - if(icount == xcm || temp.back().empty()) { - if(static_cast(xcm) == expected_max_vector_size) { + if(icount == xcm || is_separator(temp.back()) || ii == ii_max) { + if(static_cast(xcm) > type_count_min::value && is_separator(temp.back())) { temp.pop_back(); } - output.emplace_back(); - retval = retval && lexical_conversion(temp, output.back()); + typename AssignTo::value_type temp_out; + retval = retval && + lexical_conversion(temp, temp_out); temp.clear(); if(!retval) { return false; } + output.insert(output.end(), std::move(temp_out)); icount = 0; } } return retval; } -/// Sum a vector of flag representations -/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is -/// by -/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most -/// common true and false strings then uses stoll to convert the rest for summing -template ::value && std::is_unsigned::value, detail::enabler> = detail::dummy> -void sum_flag_vector(const std::vector &flags, T &output) { - std::int64_t count{0}; - for(auto &flag : flags) { - count += detail::to_flag_value(flag); + +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + if(strings.empty() || strings.front().empty()) { + output = ConvertTo{}; + return true; } - output = (count > 0) ? static_cast(count) : T{0}; + typename ConvertTo::value_type val; + if(lexical_conversion(strings, val)) { + output = ConvertTo{val}; + return true; + } + return false; } -/// Sum a vector of flag representations -/// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is -/// by -/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most -/// common true and false strings then uses stoll to convert the rest for summing -template ::value && std::is_signed::value, detail::enabler> = detail::dummy> -void sum_flag_vector(const std::vector &flags, T &output) { - std::int64_t count{0}; - for(auto &flag : flags) { - count += detail::to_flag_value(flag); +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + using ConvertType = typename ConvertTo::value_type; + if(strings.empty() || strings.front().empty()) { + output = ConvertType{}; + return true; + } + ConvertType val; + if(lexical_conversion(strings, val)) { + output = val; + return true; + } + return false; +} + +/// Sum a vector of strings +inline std::string sum_string_vector(const std::vector &values) { + double val{0.0}; + bool fail{false}; + std::string output; + for(const auto &arg : values) { + double tv{0.0}; + auto comp = lexical_cast(arg, tv); + if(!comp) { + errno = 0; + auto fv = detail::to_flag_value(arg); + fail = (errno != 0); + if(fail) { + break; + } + tv = static_cast(fv); + } + val += tv; } - output = static_cast(count); + if(fail) { + for(const auto &arg : values) { + output.append(arg); + } + } else { + std::ostringstream out; + out.precision(16); + out << val; + output = out.str(); + } + return output; } } // namespace detail -} // namespace CLI -// From Split.hpp: -namespace CLI { + namespace detail { // Returns false if not a short option. Otherwise, sets opt name and rest and returns true -inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) { +CLI11_INLINE bool split_short(const std::string ¤t, std::string &name, std::string &rest); + +// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true +CLI11_INLINE bool split_long(const std::string ¤t, std::string &name, std::string &value); + +// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true +CLI11_INLINE bool split_windows_style(const std::string ¤t, std::string &name, std::string &value); + +// Splits a string into multiple long and short names +CLI11_INLINE std::vector split_names(std::string current); + +/// extract default flag values either {def} or starting with a ! +CLI11_INLINE std::vector> get_default_flag_values(const std::string &str); + +/// Get a vector of short names, one of long names, and a single name +CLI11_INLINE std::tuple, std::vector, std::string> +get_names(const std::vector &input); + +} // namespace detail + + + +namespace detail { + +CLI11_INLINE bool split_short(const std::string ¤t, std::string &name, std::string &rest) { if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { name = current.substr(1, 1); rest = current.substr(2); @@ -1861,9 +3343,8 @@ inline bool split_short(const std::string ¤t, std::string &name, std::stri return false; } -// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true -inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { - if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) { +CLI11_INLINE bool split_long(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 2 && current.compare(0, 2, "--") == 0 && valid_first_char(current[2])) { auto loc = current.find_first_of('='); if(loc != std::string::npos) { name = current.substr(2, loc - 2); @@ -1877,8 +3358,7 @@ inline bool split_long(const std::string ¤t, std::string &name, std::strin return false; } -// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true -inline bool split_windows_style(const std::string ¤t, std::string &name, std::string &value) { +CLI11_INLINE bool split_windows_style(const std::string ¤t, std::string &name, std::string &value) { if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) { auto loc = current.find_first_of(':'); if(loc != std::string::npos) { @@ -1893,11 +3373,10 @@ inline bool split_windows_style(const std::string ¤t, std::string &name, s return false; } -// Splits a string into multiple long and short names -inline std::vector split_names(std::string current) { +CLI11_INLINE std::vector split_names(std::string current) { std::vector output; - std::size_t val; - while((val = current.find(",")) != std::string::npos) { + std::size_t val = 0; + while((val = current.find(',')) != std::string::npos) { output.push_back(trim_copy(current.substr(0, val))); current = current.substr(val + 1); } @@ -1905,15 +3384,14 @@ inline std::vector split_names(std::string current) { return output; } -/// extract default flag values either {def} or starting with a ! -inline std::vector> get_default_flag_values(const std::string &str) { +CLI11_INLINE std::vector> get_default_flag_values(const std::string &str) { std::vector flags = split_names(str); flags.erase(std::remove_if(flags.begin(), flags.end(), [](const std::string &name) { - return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) && - (name.back() == '}')) || - (name[0] == '!')))); + return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) && + (name.back() == '}')) || + (name[0] == '!')))); }), flags.end()); std::vector> output; @@ -1924,7 +3402,7 @@ inline std::vector> get_default_flag_values( if((def_start != std::string::npos) && (flag.back() == '}')) { defval = flag.substr(def_start + 1); defval.pop_back(); - flag.erase(def_start, std::string::npos); + flag.erase(def_start, std::string::npos); // NOLINT(readability-suspicious-call-argument) } flag.erase(0, flag.find_first_not_of("-!")); output.emplace_back(flag, defval); @@ -1932,14 +3410,12 @@ inline std::vector> get_default_flag_values( return output; } -/// Get a vector of short names, one of long names, and a single name -inline std::tuple, std::vector, std::string> +CLI11_INLINE std::tuple, std::vector, std::string> get_names(const std::vector &input) { std::vector short_names; std::vector long_names; std::string pos_name; - for(std::string name : input) { if(name.length() == 0) { continue; @@ -1947,6 +3423,8 @@ get_names(const std::vector &input) { if(name.length() > 1 && name[0] == '-' && name[1] != '-') { if(name.length() == 2 && valid_first_char(name[1])) short_names.emplace_back(1, name[1]); + else if(name.length() > 2) + throw BadNameString::MissingDash(name); else throw BadNameString::OneCharName(name); } else if(name.length() > 2 && name.substr(0, 2) == "--") { @@ -1958,22 +3436,21 @@ get_names(const std::vector &input) { } else if(name == "-" || name == "--") { throw BadNameString::DashesOnly(name); } else { - if(pos_name.length() > 0) + if(!pos_name.empty()) throw BadNameString::MultiPositionalNames(name); - pos_name = name; + if(valid_name_string(name)) { + pos_name = name; + } else { + throw BadNameString::BadPositionalName(name); + } } } - - return std::tuple, std::vector, std::string>( - short_names, long_names, pos_name); + return std::make_tuple(short_names, long_names, pos_name); } } // namespace detail -} // namespace CLI -// From ConfigFwd.hpp: -namespace CLI { class App; @@ -1984,12 +3461,11 @@ struct ConfigItem { /// This is the name std::string name{}; - /// Listing of inputs std::vector inputs{}; /// The list of parents and name joined by "." - std::string fullname() const { + CLI11_NODISCARD std::string fullname() const { std::vector tmp = parents; tmp.emplace_back(name); return detail::join(tmp, "."); @@ -2009,15 +3485,18 @@ class Config { virtual std::vector from_config(std::istream &) const = 0; /// Get a flag value - virtual std::string to_flag(const ConfigItem &item) const { + CLI11_NODISCARD virtual std::string to_flag(const ConfigItem &item) const { if(item.inputs.size() == 1) { return item.inputs.at(0); } - throw ConversionError::TooManyInputsFlag(item.fullname()); + if(item.inputs.empty()) { + return "{}"; + } + throw ConversionError::TooManyInputsFlag(item.fullname()); // LCOV_EXCL_LINE } /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure - std::vector from_file(const std::string &name) { + CLI11_NODISCARD std::vector from_file(const std::string &name) const { std::ifstream input{name}; if(!input.good()) throw FileError::Missing(name); @@ -2029,19 +3508,31 @@ class Config { virtual ~Config() = default; }; -/// This converter works with INI/TOML files; to write proper TOML files use ConfigTOML +/// This converter works with INI/TOML files; to write INI files use ConfigINI class ConfigBase : public Config { protected: /// the character used for comments - char commentChar = ';'; + char commentChar = '#'; /// the character used to start an array '\0' is a default to not use - char arrayStart = '\0'; + char arrayStart = '['; /// the character used to end an array '\0' is a default to not use - char arrayEnd = '\0'; + char arrayEnd = ']'; /// the character used to separate elements in an array - char arraySeparator = ' '; + char arraySeparator = ','; /// the character used separate the name from the value char valueDelimiter = '='; + /// the character to use around strings + char stringQuote = '"'; + /// the character to use around single characters and literal strings + char literalQuote = '\''; + /// the maximum number of layers to allow + uint8_t maximumLayers{255}; + /// the separator used to separator parent layers + char parentSeparatorChar{'.'}; + /// Specify the configuration index to use for arrayed sections + int16_t configIndex{-1}; + /// Specify the configuration section that should be used + std::string configSection{}; public: std::string @@ -2069,28 +3560,60 @@ class ConfigBase : public Config { valueDelimiter = vSep; return this; } + /// Specify the quote characters used around strings and literal strings + ConfigBase *quoteCharacter(char qString, char literalChar) { + stringQuote = qString; + literalQuote = literalChar; + return this; + } + /// Specify the maximum number of parents + ConfigBase *maxLayers(uint8_t layers) { + maximumLayers = layers; + return this; + } + /// Specify the separator to use for parent layers + ConfigBase *parentSeparator(char sep) { + parentSeparatorChar = sep; + return this; + } + /// get a reference to the configuration section + std::string §ionRef() { return configSection; } + /// get the section + CLI11_NODISCARD const std::string §ion() const { return configSection; } + /// specify a particular section of the configuration file to use + ConfigBase *section(const std::string §ionName) { + configSection = sectionName; + return this; + } + + /// get a reference to the configuration index + int16_t &indexRef() { return configIndex; } + /// get the section index + CLI11_NODISCARD int16_t index() const { return configIndex; } + /// specify a particular index in the section to use (-1) for all sections to use + ConfigBase *index(int16_t sectionIndex) { + configIndex = sectionIndex; + return this; + } }; -/// the default Config is the INI file format -using ConfigINI = ConfigBase; +/// the default Config is the TOML file format +using ConfigTOML = ConfigBase; -/// ConfigTOML generates a TOML compliant output -class ConfigTOML : public ConfigINI { +/// ConfigINI generates a "standard" INI compliant output +class ConfigINI : public ConfigTOML { public: - ConfigTOML() { - commentChar = '#'; - arrayStart = '['; - arrayEnd = ']'; - arraySeparator = ','; + ConfigINI() { + commentChar = ';'; + arrayStart = '\0'; + arrayEnd = '\0'; + arraySeparator = ' '; valueDelimiter = '='; } }; -} // namespace CLI -// From Validators.hpp: -namespace CLI { class Option; @@ -2121,6 +3644,9 @@ class Validator { /// specify that a validator should not modify the input bool non_modifying_{false}; + Validator(std::string validator_desc, std::function func) + : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(func)) {} + public: Validator() = default; /// Construct a Validator with just the description string @@ -2136,18 +3662,7 @@ class Validator { } /// This is the required operator for a Validator - provided to help /// users (CLI11 uses the member `func` directly) - std::string operator()(std::string &str) const { - std::string retstring; - if(active_) { - if(non_modifying_) { - std::string value = str; - retstring = func_(value); - } else { - retstring = func_(str); - } - } - return retstring; - } + std::string operator()(std::string &str) const; /// This is the required operator for a Validator - provided to help /// users (CLI11 uses the member `func` directly) @@ -2162,13 +3677,10 @@ class Validator { return *this; } /// Specify the type string - Validator description(std::string validator_desc) const { - Validator newval(*this); - newval.desc_function_ = [validator_desc]() { return validator_desc; }; - return newval; - } + CLI11_NODISCARD Validator description(std::string validator_desc) const; + /// Generate type description information for the Validator - std::string get_description() const { + CLI11_NODISCARD std::string get_description() const { if(active_) { return desc_function_(); } @@ -2180,20 +3692,20 @@ class Validator { return *this; } /// Specify the type string - Validator name(std::string validator_name) const { + CLI11_NODISCARD Validator name(std::string validator_name) const { Validator newval(*this); newval.name_ = std::move(validator_name); return newval; } /// Get the name of the Validator - const std::string &get_name() const { return name_; } + CLI11_NODISCARD const std::string &get_name() const { return name_; } /// Specify whether the Validator is active or not Validator &active(bool active_val = true) { active_ = active_val; return *this; } /// Specify whether the Validator is active or not - Validator active(bool active_val = true) const { + CLI11_NODISCARD Validator active(bool active_val = true) const { Validator newval(*this); newval.active_ = active_val; return newval; @@ -2210,107 +3722,33 @@ class Validator { return *this; } /// Specify the application index of a validator - Validator application_index(int app_index) const { + CLI11_NODISCARD Validator application_index(int app_index) const { Validator newval(*this); newval.application_index_ = app_index; return newval; } /// Get the current value of the application index - int get_application_index() const { return application_index_; } + CLI11_NODISCARD int get_application_index() const { return application_index_; } /// Get a boolean if the validator is active - bool get_active() const { return active_; } + CLI11_NODISCARD bool get_active() const { return active_; } /// Get a boolean if the validator is allowed to modify the input returns true if it can modify the input - bool get_modifying() const { return !non_modifying_; } + CLI11_NODISCARD bool get_modifying() const { return !non_modifying_; } /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the /// same. - Validator operator&(const Validator &other) const { - Validator newval; - - newval._merge_description(*this, other, " AND "); - - // Give references (will make a copy in lambda function) - const std::function &f1 = func_; - const std::function &f2 = other.func_; - - newval.func_ = [f1, f2](std::string &input) { - std::string s1 = f1(input); - std::string s2 = f2(input); - if(!s1.empty() && !s2.empty()) - return std::string("(") + s1 + ") AND (" + s2 + ")"; - else - return s1 + s2; - }; - - newval.active_ = (active_ & other.active_); - newval.application_index_ = application_index_; - return newval; - } + Validator operator&(const Validator &other) const; /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the /// same. - Validator operator|(const Validator &other) const { - Validator newval; - - newval._merge_description(*this, other, " OR "); - - // Give references (will make a copy in lambda function) - const std::function &f1 = func_; - const std::function &f2 = other.func_; - - newval.func_ = [f1, f2](std::string &input) { - std::string s1 = f1(input); - std::string s2 = f2(input); - if(s1.empty() || s2.empty()) - return std::string(); - - return std::string("(") + s1 + ") OR (" + s2 + ")"; - }; - newval.active_ = (active_ & other.active_); - newval.application_index_ = application_index_; - return newval; - } + Validator operator|(const Validator &other) const; /// Create a validator that fails when a given validator succeeds - Validator operator!() const { - Validator newval; - const std::function &dfunc1 = desc_function_; - newval.desc_function_ = [dfunc1]() { - auto str = dfunc1(); - return (!str.empty()) ? std::string("NOT ") + str : std::string{}; - }; - // Give references (will make a copy in lambda function) - const std::function &f1 = func_; - - newval.func_ = [f1, dfunc1](std::string &test) -> std::string { - std::string s1 = f1(test); - if(s1.empty()) { - return std::string("check ") + dfunc1() + " succeeded improperly"; - } - return std::string{}; - }; - newval.active_ = active_; - newval.application_index_ = application_index_; - return newval; - } + Validator operator!() const; private: - void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger) { - - const std::function &dfunc1 = val1.desc_function_; - const std::function &dfunc2 = val2.desc_function_; - - desc_function_ = [=]() { - std::string f1 = dfunc1(); - std::string f2 = dfunc2(); - if((f1.empty()) || (f2.empty())) { - return f1 + f2; - } - return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')'; - }; - } -}; // namespace CLI + void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger); +}; /// Class wrapping some of the accessors of Validator class CustomValidator : public Validator { @@ -2324,179 +3762,42 @@ namespace detail { /// CLI enumeration of different file types enum class path_type { nonexistent, file, directory }; -#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 -/// get the type of the path from a file name -inline path_type check_path(const char *file) noexcept { - std::error_code ec; - auto stat = std::filesystem::status(file, ec); - if(ec) { - return path_type::nonexistent; - } - switch(stat.type()) { - case std::filesystem::file_type::none: - case std::filesystem::file_type::not_found: - return path_type::nonexistent; - case std::filesystem::file_type::directory: - return path_type::directory; - case std::filesystem::file_type::symlink: - case std::filesystem::file_type::block: - case std::filesystem::file_type::character: - case std::filesystem::file_type::fifo: - case std::filesystem::file_type::socket: - case std::filesystem::file_type::regular: - case std::filesystem::file_type::unknown: - default: - return path_type::file; - } -} -#else /// get the type of the path from a file name -inline path_type check_path(const char *file) noexcept { -#if defined(_MSC_VER) - struct __stat64 buffer; - if(_stat64(file, &buffer) == 0) { - return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; - } -#else - struct stat buffer; - if(stat(file, &buffer) == 0) { - return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; - } -#endif - return path_type::nonexistent; -} -#endif +CLI11_INLINE path_type check_path(const char *file) noexcept; + /// Check for an existing file (returns error message if check fails) class ExistingFileValidator : public Validator { public: - ExistingFileValidator() : Validator("FILE") { - func_ = [](std::string &filename) { - auto path_result = check_path(filename.c_str()); - if(path_result == path_type::nonexistent) { - return "File does not exist: " + filename; - } - if(path_result == path_type::directory) { - return "File is actually a directory: " + filename; - } - return std::string(); - }; - } + ExistingFileValidator(); }; /// Check for an existing directory (returns error message if check fails) class ExistingDirectoryValidator : public Validator { public: - ExistingDirectoryValidator() : Validator("DIR") { - func_ = [](std::string &filename) { - auto path_result = check_path(filename.c_str()); - if(path_result == path_type::nonexistent) { - return "Directory does not exist: " + filename; - } - if(path_result == path_type::file) { - return "Directory is actually a file: " + filename; - } - return std::string(); - }; - } + ExistingDirectoryValidator(); }; /// Check for an existing path class ExistingPathValidator : public Validator { public: - ExistingPathValidator() : Validator("PATH(existing)") { - func_ = [](std::string &filename) { - auto path_result = check_path(filename.c_str()); - if(path_result == path_type::nonexistent) { - return "Path does not exist: " + filename; - } - return std::string(); - }; - } + ExistingPathValidator(); }; /// Check for an non-existing path class NonexistentPathValidator : public Validator { public: - NonexistentPathValidator() : Validator("PATH(non-existing)") { - func_ = [](std::string &filename) { - auto path_result = check_path(filename.c_str()); - if(path_result != path_type::nonexistent) { - return "Path already exists: " + filename; - } - return std::string(); - }; - } + NonexistentPathValidator(); }; /// Validate the given string is a legal ipv4 address class IPV4Validator : public Validator { public: - IPV4Validator() : Validator("IPV4") { - func_ = [](std::string &ip_addr) { - auto result = CLI::detail::split(ip_addr, '.'); - if(result.size() != 4) { - return std::string("Invalid IPV4 address must have four parts (") + ip_addr + ')'; - } - int num; - for(const auto &var : result) { - bool retval = detail::lexical_cast(var, num); - if(!retval) { - return std::string("Failed parsing number (") + var + ')'; - } - if(num < 0 || num > 255) { - return std::string("Each IP number must be between 0 and 255 ") + var; - } - } - return std::string(); - }; - } -}; - -/// Validate the argument is a number and greater than 0 -class PositiveNumber : public Validator { - public: - PositiveNumber() : Validator("POSITIVE") { - func_ = [](std::string &number_str) { - double number; - if(!detail::lexical_cast(number_str, number)) { - return std::string("Failed parsing number: (") + number_str + ')'; - } - if(number <= 0) { - return std::string("Number less or equal to 0: (") + number_str + ')'; - } - return std::string(); - }; - } -}; -/// Validate the argument is a number and greater than or equal to 0 -class NonNegativeNumber : public Validator { - public: - NonNegativeNumber() : Validator("NONNEGATIVE") { - func_ = [](std::string &number_str) { - double number; - if(!detail::lexical_cast(number_str, number)) { - return std::string("Failed parsing number: (") + number_str + ')'; - } - if(number < 0) { - return std::string("Number less than 0: (") + number_str + ')'; - } - return std::string(); - }; - } + IPV4Validator(); }; -/// Validate the argument is a number -class Number : public Validator { +class EscapedStringTransformer : public Validator { public: - Number() : Validator("NUMBER") { - func_ = [](std::string &number_str) { - double number; - if(!detail::lexical_cast(number_str, number)) { - return std::string("Failed parsing as a number (") + number_str + ')'; - } - return std::string(); - }; - } + EscapedStringTransformer(); }; } // namespace detail @@ -2518,14 +3819,33 @@ const detail::NonexistentPathValidator NonexistentPath; /// Check for an IP4 address const detail::IPV4Validator ValidIPV4; -/// Check for a positive number -const detail::PositiveNumber PositiveNumber; +/// convert escaped characters into their associated values +const detail::EscapedStringTransformer EscapedString; -/// Check for a non-negative number -const detail::NonNegativeNumber NonNegativeNumber; +/// Validate the input as a particular type +template class TypeValidator : public Validator { + public: + explicit TypeValidator(const std::string &validator_name) + : Validator(validator_name, [](std::string &input_string) { + using CLI::detail::lexical_cast; + auto val = DesiredType(); + if(!lexical_cast(input_string, val)) { + return std::string("Failed parsing ") + input_string + " as a " + detail::type_name(); + } + return std::string(); + }) {} + TypeValidator() : TypeValidator(detail::type_name()) {} +}; /// Check for a number -const detail::Number Number; +const TypeValidator Number("NUMBER"); + +/// Modify a path if the file is a particular default location, can be used as Check or transform +/// with the error return optionally disabled +class FileOnDefaultPath : public Validator { + public: + explicit FileOnDefaultPath(std::string default_path, bool enableErrorReturn = true); +}; /// Produce a range (factory). Min and max are inclusive. class Range : public Validator { @@ -2534,26 +3854,40 @@ class Range : public Validator { /// /// Note that the constructor is templated, but the struct is not, so C++17 is not /// needed to provide nice syntax for Range(a,b). - template Range(T min, T max) { - std::stringstream out; - out << detail::type_name() << " in [" << min << " - " << max << "]"; - description(out.str()); - - func_ = [min, max](std::string &input) { - T val; - bool converted = detail::lexical_cast(input, val); - if((!converted) || (val < min || val > max)) - return std::string("Value ") + input + " not in range " + std::to_string(min) + " to " + - std::to_string(max); - - return std::string(); + template + Range(T min_val, T max_val, const std::string &validator_name = std::string{}) : Validator(validator_name) { + if(validator_name.empty()) { + std::stringstream out; + out << detail::type_name() << " in [" << min_val << " - " << max_val << "]"; + description(out.str()); + } + + func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; + T val; + bool converted = lexical_cast(input, val); + if((!converted) || (val < min_val || val > max_val)) { + std::stringstream out; + out << "Value " << input << " not in range ["; + out << min_val << " - " << max_val << "]"; + return out.str(); + } + return std::string{}; }; } /// Range of one value is 0 to value - template explicit Range(T max) : Range(static_cast(0), max) {} + template + explicit Range(T max_val, const std::string &validator_name = std::string{}) + : Range(static_cast(0), max_val, validator_name) {} }; +/// Check for a non negative number +const Range NonNegativeNumber((std::numeric_limits::max)(), "NONNEGATIVE"); + +/// Check for a positive valued number (val>0.0), ::min here is the smallest positive number +const Range PositiveNumber((std::numeric_limits::min)(), (std::numeric_limits::max)(), "POSITIVE"); + /// Produce a bounded range (factory). Min and max are inclusive. class Bound : public Validator { public: @@ -2561,33 +3895,34 @@ class Bound : public Validator { /// /// Note that the constructor is templated, but the struct is not, so C++17 is not /// needed to provide nice syntax for Range(a,b). - template Bound(T min, T max) { + template Bound(T min_val, T max_val) { std::stringstream out; - out << detail::type_name() << " bounded to [" << min << " - " << max << "]"; + out << detail::type_name() << " bounded to [" << min_val << " - " << max_val << "]"; description(out.str()); - func_ = [min, max](std::string &input) { - T val; - bool converted = detail::lexical_cast(input, val); - if(!converted) { - return std::string("Value ") + input + " could not be converted"; - } - if(val < min) - input = detail::to_string(min); - else if(val > max) - input = detail::to_string(max); - - return std::string{}; + func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; + T val; + bool converted = lexical_cast(input, val); + if(!converted) { + return std::string("Value ") + input + " could not be converted"; + } + if(val < min_val) + input = detail::to_string(min_val); + else if(val > max_val) + input = detail::to_string(max_val); + + return std::string{}; }; } /// Range of one value is 0 to value - template explicit Bound(T max) : Bound(static_cast(0), max) {} + template explicit Bound(T max_val) : Bound(static_cast(0), max_val) {} }; namespace detail { template ::type>::value, detail::enabler> = detail::dummy> + enable_if_t::type>::value, detail::enabler> = detail::dummy> auto smart_deref(T value) -> decltype(*value) { return *value; } @@ -2619,13 +3954,13 @@ template std::string generate_map(const T &map, bool key_only = fal out.append(detail::join( detail::smart_deref(map), [key_only](const iteration_type_t &v) { - std::string res{detail::to_string(detail::pair_adaptor::first(v))}; + std::string res{detail::to_string(detail::pair_adaptor::first(v))}; - if(!key_only) { - res.append("->"); - res += detail::to_string(detail::pair_adaptor::second(v)); - } - return res; + if(!key_only) { + res.append("->"); + res += detail::to_string(detail::pair_adaptor::second(v)); + } + return res; }, ",")); out.push_back('}'); @@ -2647,7 +3982,7 @@ auto search(const T &set, const V &val) -> std::pair::type; auto &setref = detail::smart_deref(set); auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) { - return (detail::pair_adaptor::first(v) == val); + return (detail::pair_adaptor::first(v) == val); }); return {(it != std::end(setref)), it}; } @@ -2663,7 +3998,7 @@ auto search(const T &set, const V &val) -> std::pair auto search(const T &set, const V &val, const std::function &filter_function) --> std::pair { + -> std::pair { using element_t = typename detail::element_type::type; // do the potentially faster first search auto res = search(set, val); @@ -2673,9 +4008,9 @@ auto search(const T &set, const V &val, const std::function &filter_functi // if we haven't found it do the longer linear search with all the element translations auto &setref = detail::smart_deref(set); auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) { - V a{detail::pair_adaptor::first(v)}; - a = filter_function(a); - return (a == val); + V a{detail::pair_adaptor::first(v)}; + a = filter_function(a); + return (a == val); }); return {(it != std::end(setref)), it}; } @@ -2688,9 +4023,8 @@ template inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { if((a > 0) == (b > 0)) { return ((std::numeric_limits::max)() / (std::abs)(a) < (std::abs)(b)); - } else { - return ((std::numeric_limits::min)() / (std::abs)(a) > -(std::abs)(b)); } + return ((std::numeric_limits::min)() / (std::abs)(a) > -(std::abs)(b)); } /// Do a check for overflow on unsigned numbers template @@ -2733,7 +4067,7 @@ class IsMember : public Validator { /// This allows in-place construction using an initializer list template - IsMember(std::initializer_list values, Args &&... args) + IsMember(std::initializer_list values, Args &&...args) : IsMember(std::vector(values), std::forward(args)...) {} /// This checks to see if an item is in a set (empty function) @@ -2749,7 +4083,7 @@ class IsMember : public Validator { using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map using local_item_t = typename IsMemberType::type; // This will convert bad types to good ones - // (const char * to std::string) + // (const char * to std::string) // Make a local copy of the filter function, using a std::function if not one already std::function filter_fn = filter_function; @@ -2760,38 +4094,37 @@ class IsMember : public Validator { // This is the function that validates // It stores a copy of the set pointer-like, so shared_ptr will stay alive func_ = [set, filter_fn](std::string &input) { - local_item_t b; - if(!detail::lexical_cast(input, b)) { - throw ValidationError(input); // name is added later - } - if(filter_fn) { - b = filter_fn(b); - } - auto res = detail::search(set, b, filter_fn); - if(res.first) { - // Make sure the version in the input string is identical to the one in the set - if(filter_fn) { - input = detail::value_string(detail::pair_adaptor::first(*(res.second))); - } + using CLI::detail::lexical_cast; + local_item_t b; + if(!lexical_cast(input, b)) { + throw ValidationError(input); // name is added later + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(set, b, filter_fn); + if(res.first) { + // Make sure the version in the input string is identical to the one in the set + if(filter_fn) { + input = detail::value_string(detail::pair_adaptor::first(*(res.second))); + } - // Return empty error string (success) - return std::string{}; - } + // Return empty error string (success) + return std::string{}; + } - // If you reach this point, the result was not found - std::string out(" not in "); - out += detail::generate_set(detail::smart_deref(set)); - return out; + // If you reach this point, the result was not found + return input + " not in " + detail::generate_set(detail::smart_deref(set)); }; } /// You can pass in as many filter functions as you like, they nest (string only currently) template - IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) + IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) : IsMember( - std::forward(set), - [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, - other...) {} + std::forward(set), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} }; /// definition of the default transformation object @@ -2804,7 +4137,7 @@ class Transformer : public Validator { /// This allows in-place construction template - Transformer(std::initializer_list> values, Args &&... args) + Transformer(std::initializer_list> values, Args &&...args) : Transformer(TransformPairs(values), std::forward(args)...) {} /// direct map of std::string to std::string @@ -2821,7 +4154,7 @@ class Transformer : public Validator { using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones - // (const char * to std::string) + // (const char * to std::string) // Make a local copy of the filter function, using a std::function if not one already std::function filter_fn = filter_function; @@ -2830,29 +4163,30 @@ class Transformer : public Validator { desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; func_ = [mapping, filter_fn](std::string &input) { - local_item_t b; - if(!detail::lexical_cast(input, b)) { - return std::string(); - // there is no possible way we can match anything in the mapping if we can't convert so just return - } - if(filter_fn) { - b = filter_fn(b); - } - auto res = detail::search(mapping, b, filter_fn); - if(res.first) { - input = detail::value_string(detail::pair_adaptor::second(*res.second)); - } - return std::string{}; + using CLI::detail::lexical_cast; + local_item_t b; + if(!lexical_cast(input, b)) { + return std::string(); + // there is no possible way we can match anything in the mapping if we can't convert so just return + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + } + return std::string{}; }; } /// You can pass in as many filter functions as you like, they nest template - Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) + Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) : Transformer( - std::forward(mapping), - [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, - other...) {} + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} }; /// translate named items to other or a value set @@ -2862,7 +4196,7 @@ class CheckedTransformer : public Validator { /// This allows in-place construction template - CheckedTransformer(std::initializer_list> values, Args &&... args) + CheckedTransformer(std::initializer_list> values, Args &&...args) : CheckedTransformer(TransformPairs(values), std::forward(args)...) {} /// direct map of std::string to std::string @@ -2879,56 +4213,57 @@ class CheckedTransformer : public Validator { using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones - // (const char * to std::string) + // (const char * to std::string) using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair // Make a local copy of the filter function, using a std::function if not one already std::function filter_fn = filter_function; auto tfunc = [mapping]() { - std::string out("value in "); - out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; - out += detail::join( - detail::smart_deref(mapping), - [](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor::second(v)); }, - ","); - out.push_back('}'); - return out; + std::string out("value in "); + out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; + out += detail::join( + detail::smart_deref(mapping), + [](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor::second(v)); }, + ","); + out.push_back('}'); + return out; }; desc_function_ = tfunc; func_ = [mapping, tfunc, filter_fn](std::string &input) { - local_item_t b; - bool converted = detail::lexical_cast(input, b); - if(converted) { - if(filter_fn) { - b = filter_fn(b); - } - auto res = detail::search(mapping, b, filter_fn); - if(res.first) { - input = detail::value_string(detail::pair_adaptor::second(*res.second)); - return std::string{}; - } - } - for(const auto &v : detail::smart_deref(mapping)) { - auto output_string = detail::value_string(detail::pair_adaptor::second(v)); - if(output_string == input) { - return std::string(); - } - } + using CLI::detail::lexical_cast; + local_item_t b; + bool converted = lexical_cast(input, b); + if(converted) { + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + return std::string{}; + } + } + for(const auto &v : detail::smart_deref(mapping)) { + auto output_string = detail::value_string(detail::pair_adaptor::second(v)); + if(output_string == input) { + return std::string(); + } + } - return "Check " + input + " " + tfunc() + " FAILED"; + return "Check " + input + " " + tfunc() + " FAILED"; }; } /// You can pass in as many filter functions as you like, they nest template - CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) + CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) : CheckedTransformer( - std::forward(mapping), - [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, - other...) {} + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} }; /// Helper function to allow ignore_case to be passed to IsMember or Transform @@ -2978,59 +4313,68 @@ class AsNumberWithUnit : public Validator { // transform function func_ = [mapping, opts](std::string &input) -> std::string { - Number num; - - detail::rtrim(input); - if(input.empty()) { - throw ValidationError("Input is empty"); - } - - // Find split position between number and prefix - auto unit_begin = input.end(); - while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) { - --unit_begin; - } - - std::string unit{unit_begin, input.end()}; - input.resize(static_cast(std::distance(input.begin(), unit_begin))); - detail::trim(input); - - if(opts & UNIT_REQUIRED && unit.empty()) { - throw ValidationError("Missing mandatory unit"); - } - if(opts & CASE_INSENSITIVE) { - unit = detail::to_lower(unit); - } - - bool converted = detail::lexical_cast(input, num); - if(!converted) { - throw ValidationError(std::string("Value ") + input + " could not be converted to " + - detail::type_name()); - } - - if(unit.empty()) { - // No need to modify input if no unit passed - return {}; - } - - // find corresponding factor - auto it = mapping.find(unit); - if(it == mapping.end()) { - throw ValidationError(unit + - " unit not recognized. " - "Allowed values: " + - detail::generate_map(mapping, true)); - } - - // perform safe multiplication - bool ok = detail::checked_multiply(num, it->second); - if(!ok) { - throw ValidationError(detail::to_string(num) + " multiplied by " + unit + - " factor would cause number overflow. Use smaller value."); - } - input = detail::to_string(num); - - return {}; + Number num{}; + + detail::rtrim(input); + if(input.empty()) { + throw ValidationError("Input is empty"); + } + + // Find split position between number and prefix + auto unit_begin = input.end(); + while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) { + --unit_begin; + } + + std::string unit{unit_begin, input.end()}; + input.resize(static_cast(std::distance(input.begin(), unit_begin))); + detail::trim(input); + + if(opts & UNIT_REQUIRED && unit.empty()) { + throw ValidationError("Missing mandatory unit"); + } + if(opts & CASE_INSENSITIVE) { + unit = detail::to_lower(unit); + } + if(unit.empty()) { + using CLI::detail::lexical_cast; + if(!lexical_cast(input, num)) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // No need to modify input if no unit passed + return {}; + } + + // find corresponding factor + auto it = mapping.find(unit); + if(it == mapping.end()) { + throw ValidationError(unit + + " unit not recognized. " + "Allowed values: " + + detail::generate_map(mapping, true)); + } + + if(!input.empty()) { + using CLI::detail::lexical_cast; + bool converted = lexical_cast(input, num); + if(!converted) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // perform safe multiplication + bool ok = detail::checked_multiply(num, it->second); + if(!ok) { + throw ValidationError(detail::to_string(num) + " multiplied by " + unit + + " factor would cause number overflow. Use smaller value."); + } + } else { + num = static_cast(it->second); + } + + input = detail::to_string(num); + + return {}; }; } @@ -3075,6 +4419,10 @@ class AsNumberWithUnit : public Validator { } }; +inline AsNumberWithUnit::Options operator|(const AsNumberWithUnit::Options &a, const AsNumberWithUnit::Options &b) { + return static_cast(static_cast(a) | static_cast(b)); +} + /// Converts a human-readable size string (with unit literal) to uin64_t size. /// Example: /// "100" => 100 @@ -3097,44 +4445,14 @@ class AsSizeValue : public AsNumberWithUnit { /// The first option is formally correct, but /// the second interpretation is more wide-spread /// (see https://en.wikipedia.org/wiki/Binary_prefix). - explicit AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) { - if(kb_is_1000) { - description("SIZE [b, kb(=1000b), kib(=1024b), ...]"); - } else { - description("SIZE [b, kb(=1024b), ...]"); - } - } + explicit AsSizeValue(bool kb_is_1000); private: /// Get mapping - static std::map init_mapping(bool kb_is_1000) { - std::map m; - result_t k_factor = kb_is_1000 ? 1000 : 1024; - result_t ki_factor = 1024; - result_t k = 1; - result_t ki = 1; - m["b"] = 1; - for(std::string p : {"k", "m", "g", "t", "p", "e"}) { - k *= k_factor; - ki *= ki_factor; - m[p] = k; - m[p + "b"] = k; - m[p + "i"] = ki; - m[p + "ib"] = ki; - } - return m; - } + static std::map init_mapping(bool kb_is_1000); /// Cache calculated mapping - static std::map get_mapping(bool kb_is_1000) { - if(kb_is_1000) { - static auto m = init_mapping(true); - return m; - } else { - static auto m = init_mapping(false); - return m; - } - } + static std::map get_mapping(bool kb_is_1000); }; namespace detail { @@ -3142,7 +4460,308 @@ namespace detail { /// the string is assumed to contain a file name followed by other arguments /// the return value contains is a pair with the first argument containing the program name and the second /// everything else. -inline std::pair split_program_name(std::string commandline) { +CLI11_INLINE std::pair split_program_name(std::string commandline); + +} // namespace detail +/// @} + + + + +CLI11_INLINE std::string Validator::operator()(std::string &str) const { + std::string retstring; + if(active_) { + if(non_modifying_) { + std::string value = str; + retstring = func_(value); + } else { + retstring = func_(str); + } + } + return retstring; +} + +CLI11_NODISCARD CLI11_INLINE Validator Validator::description(std::string validator_desc) const { + Validator newval(*this); + newval.desc_function_ = [validator_desc]() { return validator_desc; }; + return newval; +} + +CLI11_INLINE Validator Validator::operator&(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " AND "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(!s1.empty() && !s2.empty()) + return std::string("(") + s1 + ") AND (" + s2 + ")"; + return s1 + s2; + }; + + newval.active_ = active_ && other.active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE Validator Validator::operator|(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " OR "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(s1.empty() || s2.empty()) + return std::string(); + + return std::string("(") + s1 + ") OR (" + s2 + ")"; + }; + newval.active_ = active_ && other.active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE Validator Validator::operator!() const { + Validator newval; + const std::function &dfunc1 = desc_function_; + newval.desc_function_ = [dfunc1]() { + auto str = dfunc1(); + return (!str.empty()) ? std::string("NOT ") + str : std::string{}; + }; + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + + newval.func_ = [f1, dfunc1](std::string &test) -> std::string { + std::string s1 = f1(test); + if(s1.empty()) { + return std::string("check ") + dfunc1() + " succeeded improperly"; + } + return std::string{}; + }; + newval.active_ = active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE void +Validator::_merge_description(const Validator &val1, const Validator &val2, const std::string &merger) { + + const std::function &dfunc1 = val1.desc_function_; + const std::function &dfunc2 = val2.desc_function_; + + desc_function_ = [=]() { + std::string f1 = dfunc1(); + std::string f2 = dfunc2(); + if((f1.empty()) || (f2.empty())) { + return f1 + f2; + } + return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')'; + }; +} + +namespace detail { + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE path_type check_path(const char *file) noexcept { + std::error_code ec; + auto stat = std::filesystem::status(to_path(file), ec); + if(ec) { + return path_type::nonexistent; + } + switch(stat.type()) { + case std::filesystem::file_type::none: // LCOV_EXCL_LINE + case std::filesystem::file_type::not_found: + return path_type::nonexistent; // LCOV_EXCL_LINE + case std::filesystem::file_type::directory: + return path_type::directory; + case std::filesystem::file_type::symlink: + case std::filesystem::file_type::block: + case std::filesystem::file_type::character: + case std::filesystem::file_type::fifo: + case std::filesystem::file_type::socket: + case std::filesystem::file_type::regular: + case std::filesystem::file_type::unknown: + default: + return path_type::file; + } +} +#else +CLI11_INLINE path_type check_path(const char *file) noexcept { +#if defined(_MSC_VER) + struct __stat64 buffer; + if(_stat64(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#else + struct stat buffer; + if(stat(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#endif + return path_type::nonexistent; +} +#endif + +CLI11_INLINE ExistingFileValidator::ExistingFileValidator() : Validator("FILE") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "File does not exist: " + filename; + } + if(path_result == path_type::directory) { + return "File is actually a directory: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE ExistingDirectoryValidator::ExistingDirectoryValidator() : Validator("DIR") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Directory does not exist: " + filename; + } + if(path_result == path_type::file) { + return "Directory is actually a file: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE ExistingPathValidator::ExistingPathValidator() : Validator("PATH(existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Path does not exist: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE NonexistentPathValidator::NonexistentPathValidator() : Validator("PATH(non-existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result != path_type::nonexistent) { + return "Path already exists: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") { + func_ = [](std::string &ip_addr) { + auto result = CLI::detail::split(ip_addr, '.'); + if(result.size() != 4) { + return std::string("Invalid IPV4 address must have four parts (") + ip_addr + ')'; + } + int num = 0; + for(const auto &var : result) { + using CLI::detail::lexical_cast; + bool retval = lexical_cast(var, num); + if(!retval) { + return std::string("Failed parsing number (") + var + ')'; + } + if(num < 0 || num > 255) { + return std::string("Each IP number must be between 0 and 255 ") + var; + } + } + return std::string{}; + }; +} + +CLI11_INLINE EscapedStringTransformer::EscapedStringTransformer() { + func_ = [](std::string &str) { + try { + if(str.size() > 1 && (str.front() == '\"' || str.front() == '\'' || str.front() == '`') && + str.front() == str.back()) { + process_quoted_string(str); + } else if(str.find_first_of('\\') != std::string::npos) { + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + } else { + str = remove_escaped_characters(str); + } + } + return std::string{}; + } catch(const std::invalid_argument &ia) { + return std::string(ia.what()); + } + }; +} +} // namespace detail + +CLI11_INLINE FileOnDefaultPath::FileOnDefaultPath(std::string default_path, bool enableErrorReturn) + : Validator("FILE") { + func_ = [default_path, enableErrorReturn](std::string &filename) { + auto path_result = detail::check_path(filename.c_str()); + if(path_result == detail::path_type::nonexistent) { + std::string test_file_path = default_path; + if(default_path.back() != '/' && default_path.back() != '\\') { + // Add folder separator + test_file_path += '/'; + } + test_file_path.append(filename); + path_result = detail::check_path(test_file_path.c_str()); + if(path_result == detail::path_type::file) { + filename = test_file_path; + } else { + if(enableErrorReturn) { + return "File does not exist: " + filename; + } + } + } + return std::string{}; + }; +} + +CLI11_INLINE AsSizeValue::AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) { + if(kb_is_1000) { + description("SIZE [b, kb(=1000b), kib(=1024b), ...]"); + } else { + description("SIZE [b, kb(=1024b), ...]"); + } +} + +CLI11_INLINE std::map AsSizeValue::init_mapping(bool kb_is_1000) { + std::map m; + result_t k_factor = kb_is_1000 ? 1000 : 1024; + result_t ki_factor = 1024; + result_t k = 1; + result_t ki = 1; + m["b"] = 1; + for(std::string p : {"k", "m", "g", "t", "p", "e"}) { + k *= k_factor; + ki *= ki_factor; + m[p] = k; + m[p + "b"] = k; + m[p + "i"] = ki; + m[p + "ib"] = ki; + } + return m; +} + +CLI11_INLINE std::map AsSizeValue::get_mapping(bool kb_is_1000) { + if(kb_is_1000) { + static auto m = init_mapping(true); + return m; + } + static auto m = init_mapping(false); + return m; +} + +namespace detail { + +CLI11_INLINE std::pair split_program_name(std::string commandline) { // try to determine the programName std::pair vals; trim(commandline); @@ -3152,14 +4771,37 @@ inline std::pair split_program_name(std::string comman if(esp == std::string::npos) { // if we have reached the end and haven't found a valid file just assume the first argument is the // program name - esp = commandline.find_first_of(' ', 1); + if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') { + bool embeddedQuote = false; + auto keyChar = commandline[0]; + auto end = commandline.find_first_of(keyChar, 1); + while((end != std::string::npos) && (commandline[end - 1] == '\\')) { // deal with escaped quotes + end = commandline.find_first_of(keyChar, end + 1); + embeddedQuote = true; + } + if(end != std::string::npos) { + vals.first = commandline.substr(1, end - 1); + esp = end + 1; + if(embeddedQuote) { + vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar)); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + break; } } - vals.first = commandline.substr(0, esp); - rtrim(vals.first); + if(vals.first.empty()) { + vals.first = commandline.substr(0, esp); + rtrim(vals.first); + } + // strip the program name - vals.second = (esp != std::string::npos) ? commandline.substr(esp + 1) : std::string{}; + vals.second = (esp < commandline.length() - 1) ? commandline.substr(esp + 1) : std::string{}; ltrim(vals.second); return vals; } @@ -3167,11 +4809,8 @@ inline std::pair split_program_name(std::string comman } // namespace detail /// @} -} // namespace CLI -// From FormatterFwd.hpp: -namespace CLI { class Option; class App; @@ -3211,6 +4850,8 @@ class FormatterBase { FormatterBase() = default; FormatterBase(const FormatterBase &) = default; FormatterBase(FormatterBase &&) = default; + FormatterBase &operator=(const FormatterBase &) = default; + FormatterBase &operator=(FormatterBase &&) = default; /// Adding a destructor in this form to work around bug in GCC 4.7 virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default) @@ -3233,15 +4874,14 @@ class FormatterBase { ///@{ /// Get the current value of a name (REQUIRED, etc.) - std::string get_label(std::string key) const { + CLI11_NODISCARD std::string get_label(std::string key) const { if(labels_.find(key) == labels_.end()) return key; - else - return labels_.at(key); + return labels_.at(key); } /// Get the current column width - std::size_t get_column_width() const { return column_width_; } + CLI11_NODISCARD std::size_t get_column_width() const { return column_width_; } ///@} }; @@ -3273,13 +4913,16 @@ class Formatter : public FormatterBase { Formatter() = default; Formatter(const Formatter &) = default; Formatter(Formatter &&) = default; + Formatter &operator=(const Formatter &) = default; + Formatter &operator=(Formatter &&) = default; /// @name Overridables ///@{ /// This prints out a group of options with title /// - virtual std::string make_group(std::string group, bool is_positional, std::vector opts) const; + CLI11_NODISCARD virtual std::string + make_group(std::string group, bool is_positional, std::vector opts) const; /// This prints out just the positionals "group" virtual std::string make_positionals(const App *app) const; @@ -3335,11 +4978,8 @@ class Formatter : public FormatterBase { ///@} }; -} // namespace CLI -// From Option.hpp: -namespace CLI { using results_t = std::vector; /// callback function definition @@ -3355,7 +4995,9 @@ enum class MultiOptionPolicy : char { TakeLast, //!< take only the last Expected number of arguments TakeFirst, //!< take only the first Expected number of arguments Join, //!< merge all the arguments together into a single string via the delimiter character default('\n') - TakeAll //!< just get all the passed argument regardless + TakeAll, //!< just get all the passed argument regardless + Sum, //!< sum all the arguments together if numerical or concatenate directly without delimiter + Reverse, //!< take only the last Expected number of arguments in reverse order }; /// This is the CRTP base class for Option and OptionDefaults. It was designed this way @@ -3392,23 +5034,16 @@ template class OptionBase { MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw}; /// Copy the contents to another similar class (one based on OptionBase) - template void copy_to(T *other) const { - other->group(group_); - other->required(required_); - other->ignore_case(ignore_case_); - other->ignore_underscore(ignore_underscore_); - other->configurable(configurable_); - other->disable_flag_override(disable_flag_override_); - other->delimiter(delimiter_); - other->always_capture_default(always_capture_default_); - other->multi_option_policy(multi_option_policy_); - } + template void copy_to(T *other) const; public: // setters /// Changes the group membership CRTP *group(const std::string &name) { + if(!detail::valid_alias_name_string(name)) { + throw IncorrectConstruction("Group names may not contain newlines or null characters"); + } group_ = name; return static_cast(this); } @@ -3430,44 +5065,44 @@ template class OptionBase { // Getters /// Get the group of this option - const std::string &get_group() const { return group_; } + CLI11_NODISCARD const std::string &get_group() const { return group_; } /// True if this is a required option - bool get_required() const { return required_; } + CLI11_NODISCARD bool get_required() const { return required_; } /// The status of ignore case - bool get_ignore_case() const { return ignore_case_; } + CLI11_NODISCARD bool get_ignore_case() const { return ignore_case_; } /// The status of ignore_underscore - bool get_ignore_underscore() const { return ignore_underscore_; } + CLI11_NODISCARD bool get_ignore_underscore() const { return ignore_underscore_; } /// The status of configurable - bool get_configurable() const { return configurable_; } + CLI11_NODISCARD bool get_configurable() const { return configurable_; } /// The status of configurable - bool get_disable_flag_override() const { return disable_flag_override_; } + CLI11_NODISCARD bool get_disable_flag_override() const { return disable_flag_override_; } /// Get the current delimiter char - char get_delimiter() const { return delimiter_; } + CLI11_NODISCARD char get_delimiter() const { return delimiter_; } /// Return true if this will automatically capture the default value for help printing - bool get_always_capture_default() const { return always_capture_default_; } + CLI11_NODISCARD bool get_always_capture_default() const { return always_capture_default_; } /// The status of the multi option policy - MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; } + CLI11_NODISCARD MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; } // Shortcuts for multi option policy /// Set the multi option policy to take last CRTP *take_last() { - auto self = static_cast(this); + auto *self = static_cast(this); self->multi_option_policy(MultiOptionPolicy::TakeLast); return self; } /// Set the multi option policy to take last CRTP *take_first() { - auto self = static_cast(this); + auto *self = static_cast(this); self->multi_option_policy(MultiOptionPolicy::TakeFirst); return self; } @@ -3481,7 +5116,7 @@ template class OptionBase { /// Set the multi option policy to join CRTP *join() { - auto self = static_cast(this); + auto *self = static_cast(this); self->multi_option_policy(MultiOptionPolicy::Join); return self; } @@ -3582,6 +5217,9 @@ class Option : public OptionBase