CVE-2020-22874: Integer Overflow or Wraparound (CWE-190)
value: Integer overflow vulnerability in function Jsi_ObjArraySizer in jsish before 3.0.8, allows remote attackers to execute arbitrary code.
CWE description: The product performs a calculation that can
         produce an integer overflow or wraparound when the logic
         assumes that the resulting value will always be larger than
         the original value. This occurs when an integer value is
         incremented to a value that is too large to store in the
         associated representation. When this occurs, the value may
         become a very small or negative number.
CVSS v3 base score: 9.8
Published: 2021-07-13T15:15Z
Repository: https://github.com/pcmacdon/jsish
Language: C
Files changed: md/Reference.md, src/jsi.h, src/jsiArray.c, src/jsiCData.c, src/jsiInt.h, src/jsiInterp.c, src/jsiObj.c, src/jsiValue.c, tools/protos.jsi, www/reference.wiki
Commit date: 2020-04-13 21:13:17 +0000
Commit message: Release "3.0.8": Address Array alloc sizing issues from issue "integer overflow and buffer overflow #5".

FossilOrigin-Name: 8c46a1d465b358110dcfb271721d35fe843a1b52f2fa24ccc10094eb8aaf6fe4
Diff: diff --git a/md/Reference.md b/md/Reference.md
index f8313ea..a99d36c 100644
--- a/md/Reference.md
+++ b/md/Reference.md
@@ -598,11 +598,11 @@ Otherwise waits until the sub-interp is idle, to make call and return result.</t
 <tr><td>jsppChars</td><td><i>STRKEY</i></td><td>Line preprocessor when sourcing files. Line starts with first char, and either ends with it, or matches string.</td><td><i></i></td></tr>
 <tr><td>jsppCallback</td><td><i>FUNC</i></td><td>Command to preprocess lines that match jsppChars. Call func(interpName:string, opCnt:number).</td><td><i></i></td></tr>
 <tr><td>lockTimeout</td><td><i>INT</i></td><td>Thread time-out for mutex lock acquires (milliseconds).</td><td><i></i></td></tr>
 <tr><td>logOpts</td><td><i><a href='#logOptsOptions'>options</a></i></td><td>Options for log output to add file/line/time.</td><td><i></i></td></tr>
 <tr><td>maxDepth</td><td><i>INT</i></td><td>Depth limit of recursive function calls (1000).</td><td><i></i></td></tr>
-<tr><td>maxArrayList</td><td><i>INT</i></td><td>Maximum array convertable to list (100000).</td><td><i></i></td></tr>
+<tr><td>maxArrayList</td><td><i>UINT</i></td><td>Maximum array convertable to list (100000).</td><td><i></i></td></tr>
 <tr><td>maxIncDepth</td><td><i>INT</i></td><td>Maximum allowed source/require nesting depth (50).</td><td><i></i></td></tr>
 <tr><td>maxInterpDepth</td><td><i>INT</i></td><td>Maximum nested subinterp create depth (10).</td><td><i></i></td></tr>
 <tr><td>maxUserObjs</td><td><i>INT</i></td><td>Maximum number of 'new' object calls, eg. File, RegExp, etc.</td><td><i></i></td></tr>
 <tr><td>maxOpCnt</td><td><i>INT</i></td><td>Execution limit for op-code evaluation.</td><td><i>initOnly</i></td></tr>
 <tr><td>memDebug</td><td><i>INT</i></td><td>Memory debugging level: 1=summary, 2=detail.</td><td><i></i></td></tr>
diff --git a/src/jsi.h b/src/jsi.h
index ba55da4..8a0b178 100644
--- a/src/jsi.h
+++ b/src/jsi.h
@@ -2,11 +2,11 @@
 #ifndef __JSI_H__
 #define __JSI_H__
 
 #define JSI_VERSION_MAJOR   3
 #define JSI_VERSION_MINOR   0
-#define JSI_VERSION_RELEASE 7
+#define JSI_VERSION_RELEASE 8
 
 #define JSI_VERSION (JSI_VERSION_MAJOR + ((Jsi_Number)JSI_VERSION_MINOR/100.0) + ((Jsi_Number)JSI_VERSION_RELEASE/10000.0))
 
 #ifndef JSI_EXTERN
 #define JSI_EXTERN extern
diff --git a/src/jsiArray.c b/src/jsiArray.c
index 298ec75..64f9555 100644
--- a/src/jsiArray.c
+++ b/src/jsiArray.c
@@ -265,11 +265,11 @@ static Jsi_RC jsi_ArrayFlatSub(Jsi_Interp *interp, Jsi_Obj* nobj, Jsi_Value *arr
         Jsi_Value *t = Jsi_ValueArrayIndex(interp, arr, i);
         if (t && depth>0 && Jsi_ValueIsArray(interp, t))
             rc = jsi_ArrayFlatSub(interp, nobj, t , depth-1);
         else if (!Jsi_ValueIsUndef(interp, t))
             Jsi_ObjArrayAdd(interp, nobj, t);
-        if ((++n + clen)>interp->maxArrayList)
+        if ((uint)(++n + clen)>interp->maxArrayList)
             return Jsi_LogError("array size exceeded");
     }
     return rc;
 }
 
diff --git a/src/jsiCData.c b/src/jsiCData.c
index 4dbc91e..0bb82d9 100644
--- a/src/jsiCData.c
+++ b/src/jsiCData.c
@@ -1274,12 +1274,12 @@ static Jsi_RC CDataStructDefineCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Valu
                 goto bail;
             }
             sf->flags |= JSI_OPT_BITSET_ENUM;
         }
         if (sf->arrSize) {
-            if (sf->arrSize>MAX_ARRAY_LIST) {
-                rc = Jsi_LogError("array size too big: %d >= %d", sf->arrSize, MAX_ARRAY_LIST);
+            if (sf->arrSize>interp->maxArrayList) {
+                rc = Jsi_LogError("array size too big: %d >= %d", sf->arrSize, interp->maxArrayList);
                 goto bail;
             }
             if (sf->bits || isEnum) {
                 rc = Jsi_LogError("array of %s unsupported", (sf->bits?"bits":"enums"));
                 goto bail;
diff --git a/src/jsiInt.h b/src/jsiInt.h
index 03fa347..87cb6eb 100644
--- a/src/jsiInt.h
+++ b/src/jsiInt.h
@@ -1257,11 +1257,11 @@ struct Jsi_Interp {
     int cleanup;
     int objId;
     Jsi_Value *Top_object;
     Jsi_ScopeStrs *scopes[JSI_MAX_SCOPE];
     int cur_scope;
-    int maxArrayList;
+    uint maxArrayList;
     int delRBCnt;
     Jsi_Func *activeFunc;  // Currently active function call.
     Jsi_Func *prevActiveFunc;  // Prev active function call.
     jsi_OpCode *curIp;  /* Used for debug Log msgs. */
     
diff --git a/src/jsiInterp.c b/src/jsiInterp.c
index 508f62f..133a74e 100644
--- a/src/jsiInterp.c
+++ b/src/jsiInterp.c
@@ -98,11 +98,11 @@ static Jsi_OptionSpec InterpOptions[] = {
     JSI_OPT(STRKEY,Jsi_Interp, jsppChars,   .help="Line preprocessor when sourcing files. Line starts with first char, and either ends with it, or matches string"),
     JSI_OPT(FUNC,  Jsi_Interp, jsppCallback,.help="Command to preprocess lines that match jsppChars. Call func(interpName:string, opCnt:number)"),
     JSI_OPT(INT,   Jsi_Interp, lockTimeout, .help="Thread time-out for mutex lock acquires (milliseconds)" ),
     JSI_OPT(CUSTOM,Jsi_Interp, logOpts,     .help="Options for log output to add file/line/time", .flags=0, .custom=Jsi_Opt_SwitchSuboption, .data=jsi_InterpLogOptions),
     JSI_OPT(INT,   Jsi_Interp, maxDepth,    .help="Depth limit of recursive function calls (1000)", .flags=JSI_OPT_LOCKSAFE),
-    JSI_OPT(INT,   Jsi_Interp, maxArrayList,.help="Maximum array convertable to list (100000)", .flags=JSI_OPT_LOCKSAFE),
+    JSI_OPT(UINT,  Jsi_Interp, maxArrayList,.help="Maximum array convertable to list (100000)", .flags=JSI_OPT_LOCKSAFE),
     JSI_OPT(INT,   Jsi_Interp, maxIncDepth, .help="Maximum allowed source/require nesting depth (50)", .flags=JSI_OPT_LOCKSAFE),
     JSI_OPT(INT,   Jsi_Interp, maxInterpDepth,.help="Maximum nested subinterp create depth (10)", .flags=JSI_OPT_LOCKSAFE),
     JSI_OPT(INT,   Jsi_Interp, maxUserObjs, .help="Maximum number of 'new' object calls, eg. File, RegExp, etc", .flags=JSI_OPT_LOCKSAFE ),
     JSI_OPT(INT,   Jsi_Interp, maxOpCnt,    .help="Execution limit for op-code evaluation", jsi_IIOF|JSI_OPT_LOCKSAFE ),
     JSI_OPT(INT,   Jsi_Interp, memDebug,    .help="Memory debugging level: 1=summary, 2=detail", .flags=JSI_OPT_NO_CLEAR),
@@ -1144,10 +1144,11 @@ static Jsi_Interp* jsi_InterpNew(Jsi_Interp *parent, Jsi_Value *opts, Jsi_Interp
             return NULL;
         }
     }
     interp->maxDepth = JSI_MAX_EVAL_DEPTH;
     interp->maxIncDepth = JSI_MAX_INCLUDE_DEPTH;
+    interp->maxArrayList = MAX_ARRAY_LIST;
     interp->typeWarnMax = 50;
     interp->subOpts.dblPrec = __DBL_DECIMAL_DIG__-1;
     interp->subOpts.prompt = "$ ";
     interp->subOpts.prompt2 = "> ";
 
@@ -1480,11 +1481,10 @@ static Jsi_Interp* jsi_InterpNew(Jsi_Interp *parent, Jsi_Value *opts, Jsi_Interp
 #ifdef JSI_MEM_DEBUG
     interp->codesTbl = (interp == jsiIntData.mainInterp ? Jsi_HashNew(interp, JSI_KEYS_ONEWORD, NULL) : jsiIntData.mainInterp->codesTbl);
 #endif
     if (interp->typeCheck.all|interp->typeCheck.parse|interp->typeCheck.funcsig)
         interp->staticFuncsTbl = Jsi_HashNew(interp, JSI_KEYS_STRING, NULL);
-    interp->maxArrayList = MAX_ARRAY_LIST;
     if (!jsiIntData.isInit) {
         jsiIntData.isInit = 1;
         jsi_InitValue(interp, 0);
         jsiIntData.interpsTbl = Jsi_HashNew(interp, JSI_KEYS_ONEWORD, 0);
     }
diff --git a/src/jsiObj.c b/src/jsiObj.c
index eae81de..7a11f49 100644
--- a/src/jsiObj.c
+++ b/src/jsiObj.c
@@ -74,11 +74,11 @@ static Jsi_RC ObjListifyCallback(Jsi_Tree *tree, Jsi_TreeEntry *hPtr, void *data
     if (!hPtr->f.bits.dontenum) {
         char *ep = NULL, *cp = (char*)Jsi_TreeKeyGet(hPtr);
         if (!cp || !isdigit(*cp))
             return JSI_OK;
         n = (int)strtol(cp, &ep, 0);
-        if (n<0 || n >= interp->maxArrayList)
+        if (n<0 || (uint)n >= interp->maxArrayList)
             return JSI_OK;
         hPtr->f.bits.isarrlist = 1;
         if (Jsi_ObjArraySizer(interp, obj, n) <= 0) 
             return Jsi_LogError("too long");
         obj->arr[n] = (Jsi_Value*)Jsi_TreeValueGet(hPtr);
@@ -412,16 +412,16 @@ int Jsi_ObjDecrRefCount(Jsi_Interp *interp, Jsi_Obj *obj)  {
 }
 
 
 int Jsi_ObjArraySizer(Jsi_Interp *interp, Jsi_Obj *obj, uint len)
 {
-    int nsiz = len + 1, mod = ALLOC_MOD_SIZE;
+    uint nsiz = len + 1, mod = ALLOC_MOD_SIZE;
     assert(obj->isarrlist);
     if (mod>1)
         nsiz = nsiz + ((mod-1) - (nsiz + mod - 1)%mod);
-    if (nsiz > MAX_ARRAY_LIST) {
-        Jsi_LogError("array size too large");
+    if (len >= interp->maxArrayList || nsiz > interp->maxArrayList) {
+        Jsi_LogError("array size too big: %u >= %u", len, interp->maxArrayList);
         return 0;
     }
     if (len >= obj->arrMaxSize) {
         int oldsz = (nsiz-obj->arrMaxSize);
         obj->arr = (Jsi_Value**)Jsi_Realloc(obj->arr, nsiz*sizeof(Jsi_Value*));
diff --git a/src/jsiValue.c b/src/jsiValue.c
index a9fa8a2..c520084 100644
--- a/src/jsiValue.c
+++ b/src/jsiValue.c
@@ -1034,11 +1034,11 @@ Jsi_Value *jsi_ValueObjKeyAssign(Jsi_Interp *interp, Jsi_Value *target, Jsi_Valu
     if (keyval->vt == JSI_VT_NUMBER && Jsi_NumberIsInteger(keyval->d.num) && keyval->d.num >= 0) {
         arrayindex = (int)keyval->d.num;
     }
     /* TODO: array["1"] also extern the length of array */
     
-    if (arrayindex >= 0 && arrayindex < MAX_ARRAY_LIST &&
+    if (arrayindex >= 0 && (uint)arrayindex < interp->maxArrayList &&
         target->vt == JSI_VT_OBJECT && target->d.obj->arr) {
         return jsi_ObjArraySetDup(interp, target->d.obj, value, arrayindex);
     }
     const char *kstr = Jsi_ValueToString(interp, keyval, NULL);
     
@@ -1371,11 +1371,11 @@ Jsi_RC Jsi_ValueInsertArray(Jsi_Interp *interp, Jsi_Value *target, int key, Jsi_
         return JSI_ERROR;
     }
     Jsi_Obj *obj = target->d.obj;
     
     if (obj->isarrlist) {
-        if (key >= 0 && key < interp->maxArrayList) {
+        if (key >= 0 && (uint)key < interp->maxArrayList) {
             Jsi_ObjArraySet(interp, obj, val, key);
             return JSI_OK;
         }
         return JSI_ERROR;
     }
diff --git a/tools/protos.jsi b/tools/protos.jsi
index 76d21d1..f2c4608 100755
--- a/tools/protos.jsi
+++ b/tools/protos.jsi
@@ -1,6 +1,6 @@
-//JSI Command Prototypes: version 3.0.6
+//JSI Command Prototypes: version 3.0.8
 throw("NOT EXECUTABLE: USE FILE IN GEANY EDITOR FOR CMD LINE COMPLETION + GOTO TAG");
 
 var Array = function(cmd,args) {};
 Array.prototype.concat = function(...):array {};
 Array.prototype.every = function(callback:function):any {};
diff --git a/www/reference.wiki b/www/reference.wiki
index 2b11595..51f6cdb 100644
--- a/www/reference.wiki
+++ b/www/reference.wiki
@@ -631,11 +631,11 @@ Otherwise waits until the sub-interp is idle, to make call and return result.</t
 <tr><td>jsppChars</td><td><i>STRKEY</i></td><td>Line preprocessor when sourcing files. Line starts with first char, and either ends with it, or matches string.</td><td><i></i></td></tr>
 <tr><td>jsppCallback</td><td><i>FUNC</i></td><td>Command to preprocess lines that match jsppChars. Call func(interpName:string, opCnt:number).</td><td><i></i></td></tr>
 <tr><td>lockTimeout</td><td><i>INT</i></td><td>Thread time-out for mutex lock acquires (milliseconds).</td><td><i></i></td></tr>
 <tr><td>logOpts</td><td><i><a href='#logOptsOptions'>options</a></i></td><td>Options for log output to add file/line/time.</td><td><i></i></td></tr>
 <tr><td>maxDepth</td><td><i>INT</i></td><td>Depth limit of recursive function calls (1000).</td><td><i></i></td></tr>
-<tr><td>maxArrayList</td><td><i>INT</i></td><td>Maximum array convertable to list (100000).</td><td><i></i></td></tr>
+<tr><td>maxArrayList</td><td><i>UINT</i></td><td>Maximum array convertable to list (100000).</td><td><i></i></td></tr>
 <tr><td>maxIncDepth</td><td><i>INT</i></td><td>Maximum allowed source/require nesting depth (50).</td><td><i></i></td></tr>
 <tr><td>maxInterpDepth</td><td><i>INT</i></td><td>Maximum nested subinterp create depth (10).</td><td><i></i></td></tr>
 <tr><td>maxUserObjs</td><td><i>INT</i></td><td>Maximum number of 'new' object calls, eg. File, RegExp, etc.</td><td><i></i></td></tr>
 <tr><td>maxOpCnt</td><td><i>INT</i></td><td>Execution limit for op-code evaluation.</td><td><i>initOnly</i></td></tr>
 <tr><td>memDebug</td><td><i>INT</i></td><td>Memory debugging level: 1=summary, 2=detail.</td><td><i></i></td></tr>

Vulnerable code: <tr><td>maxArrayList</td><td><i>INT</i></td><td>Maximum array convertable to list (100000).</td><td><i></i></td></tr>
#define JSI_VERSION_RELEASE 7
        if ((++n + clen)>interp->maxArrayList)
            if (sf->arrSize>MAX_ARRAY_LIST) {
                rc = Jsi_LogError("array size too big: %d >= %d", sf->arrSize, MAX_ARRAY_LIST);
    int maxArrayList;
    JSI_OPT(INT,   Jsi_Interp, maxArrayList,.help="Maximum array convertable to list (100000)", .flags=JSI_OPT_LOCKSAFE),
    interp->maxArrayList = MAX_ARRAY_LIST;
        if (n<0 || n >= interp->maxArrayList)
    int nsiz = len + 1, mod = ALLOC_MOD_SIZE;
    if (nsiz > MAX_ARRAY_LIST) {
        Jsi_LogError("array size too large");
    if (arrayindex >= 0 && arrayindex < MAX_ARRAY_LIST &&
        if (key >= 0 && key < interp->maxArrayList) {
//JSI Command Prototypes: version 3.0.6
<tr><td>maxArrayList</td><td><i>INT</i></td><td>Maximum array convertable to list (100000).</td><td><i></i></td></tr>
Fixed code: <tr><td>maxArrayList</td><td><i>UINT</i></td><td>Maximum array convertable to list (100000).</td><td><i></i></td></tr>
#define JSI_VERSION_RELEASE 8
        if ((uint)(++n + clen)>interp->maxArrayList)
            if (sf->arrSize>interp->maxArrayList) {
                rc = Jsi_LogError("array size too big: %d >= %d", sf->arrSize, interp->maxArrayList);
    uint maxArrayList;
    JSI_OPT(UINT,  Jsi_Interp, maxArrayList,.help="Maximum array convertable to list (100000)", .flags=JSI_OPT_LOCKSAFE),
    interp->maxArrayList = MAX_ARRAY_LIST;
        if (n<0 || (uint)n >= interp->maxArrayList)
    uint nsiz = len + 1, mod = ALLOC_MOD_SIZE;
    if (len >= interp->maxArrayList || nsiz > interp->maxArrayList) {
        Jsi_LogError("array size too big: %u >= %u", len, interp->maxArrayList);
    if (arrayindex >= 0 && (uint)arrayindex < interp->maxArrayList &&
        if (key >= 0 && (uint)key < interp->maxArrayList) {
//JSI Command Prototypes: version 3.0.8
<tr><td>maxArrayList</td><td><i>UINT</i></td><td>Maximum array convertable to list (100000).</td><td><i></i></td></tr>