From 4203e990be112629ef3319e31ed55316bd3559eb Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Thu, 29 Feb 2024 09:46:09 -0800 Subject: [PATCH] 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