first iteration of adding an instruction limit to jshooks

This commit is contained in:
Richard Holland
2024-07-08 12:49:36 +10:00
parent f2becaf140
commit f591290589
9 changed files with 98 additions and 24 deletions

View File

@@ -3340,7 +3340,7 @@ static void *worker_func(void *opaque)
JSContext *ctx;
JSValue val;
rt = JS_NewRuntime();
rt = JS_NewRuntime(-1);
if (rt == NULL) {
fprintf(stderr, "JS_NewRuntime failure");
exit(1);

View File

@@ -303,6 +303,7 @@ struct JSRuntime {
uint32_t operator_count;
#endif
void *user_opaque;
int64_t instruction_limit;
};
struct JSClass {
@@ -1615,7 +1616,7 @@ static inline BOOL js_check_stack_overflow(JSRuntime *rt, size_t alloca_size)
}
#endif
JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque)
JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque, uint32_t instructionLimit)
{
JSRuntime *rt;
JSMallocState ms;
@@ -1635,6 +1636,7 @@ JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque)
}
rt->malloc_state = ms;
rt->malloc_gc_threshold = 256 * 1024;
rt->instruction_limit = instructionLimit;
bf_context_init(&rt->bf_ctx, js_bf_realloc, rt);
set_dummy_numeric_ops(&rt->bigint_ops);
@@ -1772,9 +1774,9 @@ static const JSMallocFunctions def_malloc_funcs = {
js_def_malloc_usable_size,
};
JSRuntime *JS_NewRuntime(void)
JSRuntime *JS_NewRuntime(uint32_t instructionLimit)
{
return JS_NewRuntime2(&def_malloc_funcs, NULL);
return JS_NewRuntime2(&def_malloc_funcs, NULL, instructionLimit);
}
void JS_SetMemoryLimit(JSRuntime *rt, size_t limit)
@@ -6857,6 +6859,15 @@ static no_inline __exception int __js_poll_interrupts(JSContext *ctx)
static inline __exception int js_poll_interrupts(JSContext *ctx)
{
if (unlikely(--ctx->rt->instruction_limit <= 0))
{
/* XXX: should set a specific flag to avoid catching */
/* RHTODO: investigate if this is user-catchable. */
JS_ThrowInternalError(ctx, "interrupted");
JS_SetUncatchableError(ctx, ctx->rt->current_exception, TRUE);
return -1;
}
if (unlikely(--ctx->interrupt_counter <= 0)) {
return __js_poll_interrupts(ctx);
} else {

View File

@@ -331,7 +331,7 @@ typedef struct JSMallocFunctions {
typedef struct JSGCObjectHeader JSGCObjectHeader;
JSRuntime *JS_NewRuntime(void);
JSRuntime *JS_NewRuntime(uint32_t instructionLimit);
/* info lifetime must exceed that of rt */
void JS_SetRuntimeInfo(JSRuntime *rt, const char *info);
void JS_SetMemoryLimit(JSRuntime *rt, size_t limit);
@@ -341,7 +341,7 @@ void JS_SetMaxStackSize(JSRuntime *rt, size_t stack_size);
/* should be called when changing thread to update the stack top value
used to check stack overflow. */
void JS_UpdateStackTop(JSRuntime *rt);
JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque);
JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque, uint32_t instructionLimit);
void JS_FreeRuntime(JSRuntime *rt);
void *JS_GetRuntimeOpaque(JSRuntime *rt);
void JS_SetRuntimeOpaque(JSRuntime *rt, void *opaque);

View File

@@ -225,6 +225,8 @@ enum hook_log_code : uint16_t {
86, // the wasm contained a custom section (id=0)
INTERNAL_ERROR = 87, // an internal error described by the log text
JS_TEST_FAILURE = 88, // smoke test of js bytecode failed
JS_FEE_MISSING = 89,
JS_FEE_TOO_HIGH = 90,
// RH NOTE: only HookSet msgs got log codes, possibly all Hook log lines
// should get a code?
};

View File

@@ -819,7 +819,8 @@ apply(
uint32_t hookArgument,
uint8_t hookChainPosition,
// result of apply() if this is weak exec
std::shared_ptr<STObject const> const& provisionalMeta);
std::shared_ptr<STObject const> const& provisionalMeta,
uint32_t instructionLimit);
struct HookContext;
@@ -998,6 +999,7 @@ public:
size_t len,
bool callback,
uint32_t param,
uint32_t instructionLimit,
beast::Journal const& j) = 0;
static std::optional<std::string> validate(
@@ -1122,6 +1124,7 @@ public:
size_t len,
bool callback,
uint32_t hookArgument,
uint32_t instructionLimit, /* this is unused in wasm, set to 0 */
beast::Journal const& j) override
{
// HookExecutorWasm can only execute once
@@ -1298,9 +1301,9 @@ public:
JSRuntime* rt = NULL;
JSContext* ctx = NULL;
QuickJSVM(void* hookCtx)
QuickJSVM(void* hookCtx, uint32_t instructionLimit)
{
rt = JS_NewRuntime();
rt = JS_NewRuntime(instructionLimit);
ctx = JS_NewContextRaw(rt);
JS_AddIntrinsicBaseObjects(ctx);
JS_AddIntrinsicEval(ctx);
@@ -1430,7 +1433,7 @@ public:
};
/**
* Validate that a web assembly blob can be loaded by wasmedge
* Validate that a js blob can be loaded by quickjs
*/
static std::optional<std::string>
validate(const void* buf, size_t buf_len)
@@ -1441,7 +1444,8 @@ public:
std::optional<std::string> retval;
QuickJSVM vm{NULL};
// RHNOTE: hard instrction (internal program counter loop) limit of 1MM, like wasm.
QuickJSVM vm{NULL, 1000000};
JSContext* ctx = vm.ctx;
if (!vm.sane())
@@ -1511,6 +1515,7 @@ public:
size_t buf_len,
bool callback,
uint32_t hookArgument,
uint32_t instructionLimit,
beast::Journal const& j) override
{
// HookExecutorWasm can only execute once
@@ -1520,7 +1525,7 @@ public:
JLOG(j.trace()) << "HookInfo[" << HC_ACC()
<< "]: creating quickjs instance";
QuickJSVM vm{reinterpret_cast<void*>(&hookCtx)};
QuickJSVM vm{reinterpret_cast<void*>(&hookCtx), instructionLimit};
JSContext* ctx = vm.ctx;
if (!vm.sane())

View File

@@ -1535,7 +1535,8 @@ hook::apply(
bool isStrong,
uint32_t hookArgument,
uint8_t hookChainPosition,
std::shared_ptr<STObject const> const& provisionalMeta)
std::shared_ptr<STObject const> const& provisionalMeta,
uint32_t instructionLimit)
{
HookContext hookCtx = {
.applyCtx = applyCtx,
@@ -1582,7 +1583,8 @@ hook::apply(
HookExecutorWasm executor{hookCtx};
executor.execute(
bytecode.data(), (size_t)bytecode.size(), isCallback, hookArgument, j);
bytecode.data(),
(size_t)bytecode.size(), isCallback, hookArgument, 0 /* instructioin limit not used in wasm */, j);
break;
}
@@ -1594,7 +1596,7 @@ hook::apply(
HookExecutorJS executor{hookCtx};
executor.execute(
bytecode.data(), (size_t)bytecode.size(), isCallback, hookArgument, j);
bytecode.data(), (size_t)bytecode.size(), isCallback, hookArgument, instructionLimit, j);
break;
}

View File

@@ -433,7 +433,7 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
auto version = hookSetObj.getFieldU16(sfHookApiVersion);
if (version > 1)
{
// we currently only accept api version 0
// we currently only accept api version 0 and 1
JLOG(ctx.j.trace())
<< "HookSet(" << hook::log::API_INVALID << ")[" << HS_ACC()
<< "]: Malformed transaction: SetHook "
@@ -462,7 +462,33 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
if (version == 1)
{
// RHTODO: guard or other check for js, depending on design choices
if (hookSetObj.isFieldPresent(sfFee) &&
isXRP(hookSetObj.getFieldAmount(sfFee)))
{
STAmount amt = hookSetObj.getFieldAmount(sfFee);
uint64_t fee = amt.xrp().drops();
if (amt < beast::zero || fee < 1 || fee > 1000000)
{
JLOG(ctx.j.trace())
<< "HookSet(" << hook::log::JS_FEE_TOO_HIGH << ")["
<< HS_ACC()
<< "]: Malformed transaction: When creating a JS Hook "
<< "you must include a Fee <= 1000000.";
return false;
}
}
else
{
JLOG(ctx.j.trace())
<< "HookSet(" << hook::log::JS_FEE_MISSING << ")["
<< HS_ACC()
<< "]: Malformed transaction: When creating a JS Hook "
<< "you must include a Fee field indicating the instruction limit.";
return false;
}
std::optional<std::string> result =
hook::HookExecutorJS::validate(
hook.data(), (size_t)hook.size());
@@ -749,7 +775,8 @@ SetHook::preflight(PreflightContext const& ctx)
if (name != sfCreateCode && name != sfHookHash &&
name != sfHookNamespace && name != sfHookParameters &&
name != sfHookOn && name != sfHookGrants &&
name != sfHookApiVersion && name != sfFlags)
name != sfHookApiVersion && name != sfFlags &&
name != sfFee)
{
JLOG(ctx.j.trace())
<< "HookSet(" << hook::log::HOOK_INVALID_FIELD << ")["
@@ -1525,6 +1552,17 @@ SetHook::setHook()
return tecREQUIRES_FLAG;
}
uint16_t hookApiVersion = hookSetObj->get().getFieldU16(sfHookApiVersion);
if (hookApiVersion == 1 && !hookSetObj->get().isFieldPresent(sfFee))
{
JLOG(ctx.j.warn())
<< "HookSet(" << hook::log::JS_FEE_MISSING << ")["
<< HS_ACC()
<< "]: Malformed transaction: SetHook operation for JS Hook missing fee.";
return tecINTERNAL;
}
ripple::Blob wasmBytes =
hookSetObj->get().getFieldVL(sfCreateCode);
@@ -1611,6 +1649,14 @@ SetHook::setHook()
slesToUpdate.emplace(*oldDefKeylet, oldDefSLE);
}
// override instruction count with fee for js
if (hookApiVersion == 1)
{
uint64_t fee = hookSetObj->get().getFieldAmount(sfFee).xrp().drops();
maxInstrCountHook = fee;
maxInstrCountCbak = fee; // RH TODO: add a second fee for cbak?
}
auto newHookDef = std::make_shared<SLE>(keylet);
newHookDef->setFieldH256(sfHookHash, *createHookHash);
newHookDef->setFieldH256(sfHookOn, *newHookOn);
@@ -1620,9 +1666,7 @@ SetHook::setHook()
hookSetObj->get().isFieldPresent(sfHookParameters)
? hookSetObj->get().getFieldArray(sfHookParameters)
: STArray{});
newHookDef->setFieldU16(
sfHookApiVersion,
hookSetObj->get().getFieldU16(sfHookApiVersion));
newHookDef->setFieldU16(sfHookApiVersion, hookApiVersion);
newHookDef->setFieldVL(sfCreateCode, wasmBytes);
newHookDef->setFieldH256(
sfHookSetTxnID, ctx.tx.getTransactionID());

View File

@@ -1244,6 +1244,8 @@ Transactor::executeHookChain(
uint16_t hookApiVersion = hookDef->getFieldU16(sfHookApiVersion);
uint32_t fee = (uint32_t)(hookDef->getFieldAmount(sfFee).xrp().drops());
try
{
results.push_back(hook::apply(
@@ -1262,7 +1264,8 @@ Transactor::executeHookChain(
strong,
(strong ? 0 : 1UL), // 0 = strong, 1 = weak
hook_no - 1,
provisionalMeta));
provisionalMeta,
fee));
executedHookCount_++;
@@ -1379,6 +1382,8 @@ Transactor::doHookCallback(
? hookObj.getFieldH256(sfHookNamespace)
: hookDef->getFieldH256(sfHookNamespace));
uint64_t instructionLimit = hookDef->getFieldAmount(sfFee).xrp().drops();
std::map<std::vector<uint8_t>, std::vector<uint8_t>> parameters;
if (hook::gatherHookParameters(hookDef, hookObj, parameters, j_))
{
@@ -1413,7 +1418,8 @@ Transactor::doHookCallback(
? 1UL
: 0UL,
hook_no - 1,
provisionalMeta);
provisionalMeta,
instructionLimit);
executedHookCount_++;
@@ -1658,6 +1664,8 @@ Transactor::doAgainAsWeak(
return;
}
uint32_t instructionLimit = (uint32_t)(hookDef->getFieldAmount(sfFee).xrp().drops());
try
{
hook::HookResult aawResult = hook::apply(
@@ -1676,7 +1684,8 @@ Transactor::doAgainAsWeak(
false,
2UL, // param 2 = aaw
hook_no - 1,
provisionalMeta);
provisionalMeta,
instructionLimit);
executedHookCount_++;

View File

@@ -101,6 +101,7 @@ InnerObjectFormats::InnerObjectFormats()
{sfHookParameters, soeOPTIONAL},
{sfHookOn, soeOPTIONAL},
{sfHookApiVersion, soeOPTIONAL},
{sfFee, soeOPTIONAL},
{sfFlags, soeOPTIONAL}});
add(sfHookGrant.jsonName.c_str(),