mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
hook nesting level limit
This commit is contained in:
@@ -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?
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user