hook nesting level limit

This commit is contained in:
Richard Holland
2023-01-19 11:12:27 +00:00
parent 713fbb3d72
commit 79601159a5
2 changed files with 135 additions and 52 deletions

View File

@@ -129,6 +129,7 @@ namespace hook
WASM_VALIDATION = 81, // a generic error while parsing wasm, usually leb128 overflow
HOOK_CBAK_DIFF_TYPES = 82, // hook and cbak function definitions were different
PARAMETERS_NAME_REPEATED = 83,
NESTING_LIMIT = 84, // the hook nested blocks/loops/ifs beyond 16 levels
// RH NOTE: only HookSet msgs got log codes, possibly all Hook log lines should get a code?
};
};

View File

@@ -7,11 +7,12 @@
#include <stack>
#include <string>
#include <functional>
#include <memory>
#include "Enum.h"
using GuardLog = std::optional<std::reference_wrapper<std::basic_ostream<char>>>;
#define DEBUG_GUARD 1
#define DEBUG_GUARD 0
#define DEBUG_GUARD_VERBOSE 0
#define DEBUG_GUARD_VERY_VERBOSE 0
@@ -143,36 +144,107 @@ parseSignedLeb128(
struct WasmBlkInf
{
uint32_t sanity_check;
uint32_t iteration_bound;
uint32_t instruction_count;
WasmBlkInf* parent;
std::vector<WasmBlkInf> children;
std::vector<WasmBlkInf*> children;
uint32_t start_byte;
bool is_root;
WasmBlkInf(
uint32_t iteration_bound_,
uint32_t instruction_count_,
WasmBlkInf* parent_,
uint32_t start_byte_,
bool is_root_ = false)
:
sanity_check(0x1234ABCDU),
iteration_bound(iteration_bound_),
instruction_count(instruction_count_),
children({}),
parent(parent_),
start_byte(start_byte_),
is_root(is_root_)
{
// all done by the above
}
WasmBlkInf* add_child(uint32_t iteration_bound, uint32_t start_byte)
{
WasmBlkInf* child = new WasmBlkInf(iteration_bound, 0, this, start_byte, false);
children.push_back(child);
return child;
}
void free_children(WasmBlkInf* blk)
{
for (WasmBlkInf* child : blk->children)
free_children(child);
delete blk;
}
~WasmBlkInf()
{
// only the root is responsible for freeing
if (is_root)
for (WasmBlkInf* child : children)
free_children(child);
}
};
#define PRINT_WCE(x)\
{\
if (DEBUG_GUARD)\
printf("[%u]%.*swce=%ld | g=%u, pg=%u, m=%g\n",\
x,\
level, " ",\
worst_case_execution,\
blk->iteration_bound,\
(blk->parent ? blk->parent->iteration_bound : -1),\
multiplier);\
printf("%llx:: [%u]%.*swce=%ld | start=%x instcount=%u guard=%u, "\
"parent_guard=%d, multiplier=%g parentptr=%llx\n",\
&blk,\
x,\
level, " ",\
worst_case_execution,\
blk->start_byte,\
blk->instruction_count,\
blk->iteration_bound,\
(blk->parent != 0 ? blk->parent->iteration_bound : -1),\
multiplier, &(blk->parent));\
}
// compute worst case execution time
// compute worst case execution time
inline
uint64_t compute_wce (const WasmBlkInf* blk, int level = 0)
uint64_t compute_wce (const WasmBlkInf* blk, int level, bool* recursion_limit_reached)
{
if (level > 16)
{
*recursion_limit_reached = true;
return 0;
}
if (blk->sanity_check != 0x1234ABCDU)
{
printf("!!! sanity check failed\n");
*recursion_limit_reached = true;
return (uint64_t)-1;
}
WasmBlkInf const* parent = blk->parent;
if (parent && parent->sanity_check != 0x1234ABCDU)
{
printf("!!! parent sanity check failed\n");
*recursion_limit_reached = true;
return (uint64_t)-1;
}
uint64_t worst_case_execution = blk->instruction_count;
double multiplier = 1.0;
if (blk->children.size() > 0)
for (auto const& child : blk->children)
worst_case_execution += compute_wce(&child, level + 1);
worst_case_execution += compute_wce(child, level + 1, recursion_limit_reached);
if (blk->parent == 0 ||
blk->parent->iteration_bound == 0) // this condtion should never occur [defensively programmed]
if (parent == 0 ||
parent->iteration_bound == 0) // this condtion should never occur [defensively programmed]
{
PRINT_WCE(1);
return worst_case_execution;
@@ -180,9 +252,9 @@ uint64_t compute_wce (const WasmBlkInf* blk, int level = 0)
// if the block has a parent then the quotient of its guard and its parent's guard
// gives us the loop iterations and thus the multiplier for the instruction count
multiplier =
multiplier =
((double)(blk->iteration_bound)) /
((double)(blk->parent->iteration_bound));
((double)(parent->iteration_bound));
worst_case_execution *= multiplier;
if (worst_case_execution < 1.0)
@@ -221,9 +293,13 @@ check_guard(
if (end_offset <= 0) end_offset = hook.size();
int block_depth = 0;
WasmBlkInf root { .iteration_bound = 1, .instruction_count = 0, .parent = 0, .children = {} };
WasmBlkInf* current = &root;
// the root node is constructed in a unique ptr, which will cause its destructor to be called
// when the function exits. The destructor of the root node will recursively free all heap allocated children.
//WasmBlkInf(uint32_t iteration_bound_, uint32_t instruction_count_,
// WasmBlkInf* parent_, uint32_t start_byte_, bool is_root_ = false) :
std::unique_ptr<WasmBlkInf> root = std::make_unique<WasmBlkInf>(1, 0, (WasmBlkInf*)0, start_offset, true);
WasmBlkInf* current = &(*root);
if (DEBUG_GUARD)
printf("\n\n\nstart of guard analysis for codesec %d\n", codesec);
@@ -275,7 +351,7 @@ check_guard(
SIGNED_LEB();
}
uint32_t iteration_bound = (current->parent == 0 ? 1 : current->parent->iteration_bound);
uint32_t iteration_bound = (current->parent == 0 ? 1 : current->iteration_bound);
if (instr == 0x03U)
{
// now look for the guard call
@@ -290,7 +366,7 @@ check_guard(
GUARD_ERROR("Missing first i32.const after loop instruction");
ADVANCE(1);
SIGNED_LEB(); // this is the ID, we don't need it here
// second i32
REQUIRE(1);
if (hook[i] != 0x41U)
@@ -305,9 +381,6 @@ check_guard(
ADVANCE(1);
uint64_t call_func_idx = LEB(); // the function being called *must* be the _g function
//printf("iteration_bound: %d, call_func_idx: %ld, guard_func_idx: %d\n",
// iteration_bound, call_func_idx, guard_func_idx);
if (iteration_bound == 0)
GUARD_ERROR("Guard call cannot specify 0 maxiter.");
@@ -316,19 +389,10 @@ check_guard(
if (guard_count++ > MAX_GUARD_CALLS)
GUARD_ERROR("Too many guard calls! Limit is 1024");
printf("guard_count: %d\n", guard_count);
}
current->children.push_back(
{
.iteration_bound = iteration_bound,
.instruction_count = 0,
.parent = current,
.children = {}
});
current = current->add_child(iteration_bound, i);
block_depth++;
current = &(current->children[current->children.size()-1]);
continue;
}
@@ -341,8 +405,20 @@ check_guard(
current = current->parent;
if (current == 0 && block_depth == -1 && (i >= end_offset))
break; // codesec end
else if (current == 0 || block_depth < 0)
GUARD_ERROR("Illegal block end");
else if (current == 0)
{
GUARD_ERROR("Illegal block end (current==0)");
}
else if (block_depth < 0)
{
GUARD_ERROR("Illegal block end (block_depth<0)");
}
if (current->sanity_check != 0x1234ABCDU)
{
GUARD_ERROR("Sanity check failed (bad pointer)");
}
continue;
}
@@ -373,8 +449,8 @@ check_guard(
LEB();
continue;
}
if (instr == 0x0FU) // return
if (instr == 0x0FU) // return
{
if (DEBUG_GUARD_VERBOSE)
printf("Guard checker - return instruction at %d [%x]\n", i, i);
@@ -401,7 +477,6 @@ check_guard(
{
if (guard_count++ > MAX_GUARD_CALLS)
GUARD_ERROR("Too many guard calls! Limit is 1024");
printf("guard_count: %d\n", guard_count);
}
continue;
@@ -414,10 +489,10 @@ check_guard(
<< "codesec: " << codesec << " hook byte offset: " << i << "\n";
return {};
}
// reference instructions
if (instr >= 0xD0U && instr <= 0xD2)
if (instr >= 0xD0U && instr <= 0xD2)
{
if (DEBUG_GUARD_VERBOSE)
printf("Guard checker - reference instruction at %d [%x]\n", i, i);
@@ -436,7 +511,7 @@ check_guard(
REQUIRE(1);
LEB();
}
continue;
}
@@ -447,7 +522,7 @@ check_guard(
{
if (DEBUG_GUARD_VERBOSE)
printf("Guard checker - parametric instruction at %d [%x]\n", i, i);
if (instr == 0x1CU) // select t*
{
REQUIRE(1);
@@ -493,13 +568,13 @@ check_guard(
LEB();
continue;
}
if (DEBUG_GUARD_VERBOSE)
printf("Guard checker - 0xFC instruction at %d [%x]\n", i, i);
uint64_t fc_type = LEB();
REQUIRE(1);
if (fc_type >= 12 && fc_type <= 17) // table instructions
{
LEB();
@@ -592,8 +667,8 @@ check_guard(
REQUIRE(4);
ADVANCE(4);
continue;
}
}
if (instr == 0x44U) // f64.const
{
if (DEBUG_GUARD_VERBOSE)
@@ -602,15 +677,15 @@ check_guard(
REQUIRE(8);
ADVANCE(8);
continue;
}
}
// even more numeric instructions
if (instr >= 0x45U && instr <= 0xC4U)
{
if (DEBUG_GUARD_VERBOSE)
printf("Guard checker - numeric instruction at %d [%x]\n", i, i);
// these have no arguments
// these have no arguments
continue;
}
@@ -650,7 +725,7 @@ check_guard(
}
continue;
}
// execution to here is an error, unknown instruction
{
char ihex[64];
@@ -660,7 +735,14 @@ check_guard(
}
}
uint64_t wce = compute_wce(&root);
bool recursion_limit_reached = false;
uint64_t wce = compute_wce(&(*root), 0, &recursion_limit_reached);
if (recursion_limit_reached)
{
GUARDLOG(hook::log::NESTING_LIMIT) << "GuardCheck "
<< "Maximum allowable depth of blocks reached (16 levels). Flatten your loops and conditions!.\n";
return {};
}
GUARDLOG(hook::log::INSTRUCTION_COUNT) << "GuardCheck "
<< "Total worse-case execution count: " << wce << "\n";