[PATCH v6 0/1] MR10592: vbscript: Add call depth limit to prevent stack overflow on infinite recursion.
Track call depth in script_ctx_t and return error 28 "Out of stack space" when it exceeds 572, matching Windows behavior. Previously Wine would crash with a native stack overflow. -- v6: vbscript: Add call depth limit to prevent stack overflow on infinite recursion. https://gitlab.winehq.org/wine/wine/-/merge_requests/10592
From: Francis De Brabandere <francisdb@gmail.com> Track call depth in script_ctx_t and return error 28 "Out of stack space" when it exceeds 572, matching Windows behavior. Previously Wine would crash with a native stack overflow. --- dlls/vbscript/interp.c | 8 +++++++ dlls/vbscript/tests/lang.vbs | 40 +++++++++++++++++++++++++++++++++++ dlls/vbscript/vbscript.h | 1 + dlls/vbscript/vbscript.rc | 1 + dlls/vbscript/vbscript_defs.h | 1 + 5 files changed, 51 insertions(+) diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index ba530fca38c..98581b703b8 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -25,6 +25,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(vbscript); static DISPID propput_dispid = DISPID_PROPERTYPUT; +static const unsigned max_call_depth = 572; typedef struct _exec_ctx_t { vbscode_t *code; @@ -2700,6 +2701,11 @@ HRESULT exec_script(script_ctx_t *ctx, BOOL extern_caller, function_t *func, vbd vbsop_t op; HRESULT hres = S_OK; + if(!extern_caller && ctx->call_depth++ >= max_call_depth) { + ctx->call_depth--; + return MAKE_VBSERROR(VBSE_OUT_OF_STACK); + } + exec.code = func->code_ctx; exec.caller = ctx->caller_exec; ctx->caller_exec = NULL; @@ -2858,6 +2864,8 @@ HRESULT exec_script(script_ctx_t *ctx, BOOL extern_caller, function_t *func, vbd } ctx->current_named_item = prev_named_item; + if(!extern_caller) + ctx->call_depth--; release_exec(&exec); return hres; } diff --git a/dlls/vbscript/tests/lang.vbs b/dlls/vbscript/tests/lang.vbs index 7ff524461c6..168babf4ddd 100644 --- a/dlls/vbscript/tests/lang.vbs +++ b/dlls/vbscript/tests/lang.vbs @@ -2926,6 +2926,46 @@ function recursingfunction2 end function call ok(recursingfunction2() = 1, "unexpected return value " & recursingfunction2()) +' Recursion depth: verify recursion works up to 572 levels +Dim recursionDepth +recursionDepth = 0 +Sub RecurseN(n) + recursionDepth = recursionDepth + 1 + If n > 1 Then RecurseN n - 1 +End Sub +RecurseN 572 +Call ok(recursionDepth = 572, "recursion depth: " & recursionDepth & " expected 572") + +' Error 28: Out of stack space (infinite recursion) +Sub RecurseForever() + RecurseForever +End Sub + +' Error 28: Out of stack space (mutual recursion) +' Depth varies on Windows (572 on Win10 64-bit VM, 1197 on Win10 64-bit CI, +' 3122 on Win10 32-bit CI). The exact limit is not well understood. +Dim mutualDepth +mutualDepth = 0 +Sub MutualA() + mutualDepth = mutualDepth + 1 + MutualB +End Sub +Sub MutualB() + mutualDepth = mutualDepth + 1 + MutualA +End Sub + +On Error Resume Next +Err.Clear +MutualA +Call ok(Err.Number = 28, "mutual recursion: err.number = " & Err.Number) + +' Error 28: Out of stack space (infinite recursion) +Err.Clear +RecurseForever +Call ok(Err.Number = 28, "infinite recursion: err.number = " & Err.Number) +On Error GoTo 0 + function f2(x,y) end function diff --git a/dlls/vbscript/vbscript.h b/dlls/vbscript/vbscript.h index 19c9260a342..edcc3865dcd 100644 --- a/dlls/vbscript/vbscript.h +++ b/dlls/vbscript/vbscript.h @@ -211,6 +211,7 @@ struct _script_ctx_t { exec_ctx_t *current_exec; exec_ctx_t *caller_exec; + unsigned call_depth; EXCEPINFO ei; vbscode_t *error_loc_code; diff --git a/dlls/vbscript/vbscript.rc b/dlls/vbscript/vbscript.rc index 73aedb4dcaf..afb08f4ec56 100644 --- a/dlls/vbscript/vbscript.rc +++ b/dlls/vbscript/vbscript.rc @@ -32,6 +32,7 @@ STRINGTABLE VBSE_ARRAY_LOCKED "This array is fixed or temporarily locked" VBSE_DIVISION_BY_ZERO "Division by zero" VBSE_TYPE_MISMATCH "Type mismatch" + VBSE_OUT_OF_STACK "Out of stack space" VBSE_FILE_NOT_FOUND "File not found" VBSE_IO_ERROR "Device I/O error" VBSE_FILE_ALREADY_EXISTS "File already exists" diff --git a/dlls/vbscript/vbscript_defs.h b/dlls/vbscript/vbscript_defs.h index 200e1ac4929..610c4f54b15 100644 --- a/dlls/vbscript/vbscript_defs.h +++ b/dlls/vbscript/vbscript_defs.h @@ -243,6 +243,7 @@ #define VBSE_ARRAY_LOCKED 10 #define VBSE_DIVISION_BY_ZERO 11 #define VBSE_TYPE_MISMATCH 13 +#define VBSE_OUT_OF_STACK 28 #define VBSE_FILE_NOT_FOUND 53 #define VBSE_IO_ERROR 57 #define VBSE_FILE_ALREADY_EXISTS 58 -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10592
Jacek Caban (@jacek) commented about dlls/vbscript/tests/lang.vbs:
+recursionDepth = 0 +Sub RecurseN(n) + recursionDepth = recursionDepth + 1 + If n > 1 Then RecurseN n - 1 +End Sub +RecurseN 572 +Call ok(recursionDepth = 572, "recursion depth: " & recursionDepth & " expected 572") + +' Error 28: Out of stack space (infinite recursion) +Sub RecurseForever() + RecurseForever +End Sub + +' Error 28: Out of stack space (mutual recursion) +' Depth varies on Windows (572 on Win10 64-bit VM, 1197 on Win10 64-bit CI, +' 3122 on Win10 32-bit CI). The exact limit is not well understood. FWIW, I would expect the exact stack limit to be dependent both on the implementation and on what exactly is on the stack. In jscript, we share the stack between various function calls, so unlike in vbscript, recursive script calls do not use the C stack, only the jscript stack. With that, we can handle overflow in `stack_push`. Ideally, vbscript would do the same, but that is a much larger task, so I am mentioning that just for context.
In the context of this patch, I am fine with your solution, but please avoid documenting such numbers in comments. They are subject to change, so such comments do not age well. Also, let's use some round number in the implementation (like 1024). -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10592#note_136532
participants (3)
-
Francis De Brabandere -
Francis De Brabandere (@francisdb) -
Jacek Caban (@jacek)