From ef2b91c0cefc2a8d15e988f834bfac894d38111a Mon Sep 17 00:00:00 2001 From: ac-stef Date: Thu, 8 Jun 2017 19:00:04 +0200 Subject: [PATCH] more cubescript limits the limits are hardcoded and require recompilation to change * limit recursion depth * limit loop iterations * limit string lengths * limit push/pop depth * clean surplus whitespace from config files * provide context for script errors (limits accidental crashes, not intentionally malfunctioning code) --- config/menus_edit.cfg | 4 +- source/src/command.cpp | 87 +++++++++++++++++++++++++++++------- source/src/protos.h | 2 +- source/src/serverbrowser.cpp | 8 ++-- source/src/zip.cpp | 1 - 5 files changed, 77 insertions(+), 25 deletions(-) diff --git a/config/menus_edit.cfg b/config/menus_edit.cfg index 58787d79..16c084e5 100644 --- a/config/menus_edit.cfg +++ b/config/menus_edit.cfg @@ -575,7 +575,7 @@ menuitemslider "Arch sideways curve delta: " 0 5 "$__archside" 1 [ __archside = menuitem "\t[ Place arch ]" [ __setundoarch ; heightfield 2; arch $__archside ] menuitem "Edit arches\i\3" [ showmenu "Edit arches" ] menuitem "" -1 -menuitemslider "Slope axis: " 0 1 "$__slopeaxis" ["West to East (X)" "North to South (Y)"] [ __slopeaxis = $arg1 ] +menuitemradio "Slope axis: " 0 1 "$__slopeaxis" ["West to East (X)" "North to South (Y)"] [ __slopeaxis = $arg1 ] menuitemslider "Slope increment: " 0 5 "$__slopeinc" ["downwards 3" "downwards 2" "downwards 1" "upwards 1" "upwards 2" "upwards 3"] [ __slopeinc = $arg1 ] menuitem "\t[ Place slope ]" [ __setundoarch ; heightfield 0; push i (at "3 2 1 -1 -2 -3" $__slopeinc) ; slope (if $__slopeaxis 0 $i) (if $__slopeaxis $i 0) ; pop i ; 0] menuitem "" -1 @@ -584,7 +584,7 @@ menuitem "Undo last placed arch or slope" [ __undoarch ; -1 ] menuitemcheckbox "Automatically enlarge selections after placing arches and slopes" "$enlargearchslopeselections" [ enlargearchslopeselections $arg1 ] menuitem "Enlarge selections now" enlargevdeltaselections menuitem "" -1 -menuitemslider "Stairs axis: " 0 1 "$__stairaxis" ["West to East (X)" "North to South (Y)"] [ __stairaxis = $arg1 ] +menuitemradio "Stairs axis: " 0 1 "$__stairaxis" ["West to East (X)" "North to South (Y)"] [ __stairaxis = $arg1 ] menuitemslider "Step width: " 0 5 "$__stairwidth" ["downwards 3" "downwards 2" "downwards 1" "upwards 1" "upwards 2" "upwards 3"] [ __stairwidth = $arg1 ] menuitem "\t[ Place stairs ]" [ push i (at "-3 -2 -1 1 2 3" $__stairwidth) ; stairs (if $__stairaxis 0 $i) (if $__stairaxis $i 0) ; pop i ; 0] diff --git a/source/src/command.cpp b/source/src/command.cpp index 5a7cb2ef..7d1b3e2d 100644 --- a/source/src/command.cpp +++ b/source/src/command.cpp @@ -3,7 +3,15 @@ #include "cube.h" +#define CSLIMIT_LOOKUPNESTING 3 // fails silently +#define CSLIMIT_RECURSION 333 // rarely depths over 20 will be reached and setting this one over 10000 won't prevent crashes anymore +#define CSLIMIT_PUSHLEVEL 33 // beware: also grows with alias execution nesting as arg1 gets pushed +#define CSLIMIT_ITERATION 33333 // determines length of endlees loop (while 1) +#define CSLIMIT_STRINGLEN 333333 // yet another arbitrary limit + inline bool identaccessdenied(ident *id); +void cslimiterr(const char*msg); + char *exchangestr(char *o, const char *n) { delete[] o; return newstring(n); } vector executionstack; // keep history of recursive command execution (to write to log in case of a crash) @@ -15,7 +23,7 @@ int loop_level = 0; // avoid bad calls of br hashtable *idents = NULL; // contains ALL vars/commands/aliases bool persistidents = false; -bool currentcontextisolated = false; +bool currentcontextisolated = false; // true for map and model config files COMMANDF(persistidents, "s", (char *on) { @@ -39,6 +47,8 @@ void clearstack(ident &id) void pushident(ident &id, char *val, int context = execcontext) { if(id.type != ID_ALIAS) { delete[] val; return; } + int d = 0; + for(identstack *s = id.stack; s; s = s->next, d++) { if(d > CSLIMIT_PUSHLEVEL) { cslimiterr("push level"); delete[] val; return; } } identstack *stack = new identstack; stack->action = id.executing==id.action ? newstring(id.action) : id.action; stack->context = id.context; @@ -316,8 +326,9 @@ inline char *parsequotes(const char *&p) char *lookup(char *n, int levels); -char *parseexp(const char *&p, int right) // parse any nested set of () or [] +char *parseexp(const char *&p, int right, int rec) // parse any nested set of () or [] { + if(rec > CSLIMIT_RECURSION) { cslimiterr("recursion depth"); return NULL; } vector res; int left = *p++; const char *word = p; @@ -347,10 +358,10 @@ char *parseexp(const char *&p, int right) // parse any nested set of char *n, a = *p; switch(a) { - case '(': n = parseexp(p, ')'); break; + case '(': n = parseexp(p, ')', rec + 1); break; case '"': n = parsequotes(p); break; case '[': - n = parseexp(p, ']'); + n = parseexp(p, ']', rec + 1); if(n) n = lookup(n, 1); break; default: @@ -370,6 +381,7 @@ char *parseexp(const char *&p, int right) // parse any nested set of if(res.length()) { res.put(word, p - word - 1); + if(res.length() > CSLIMIT_STRINGLEN) { cslimiterr("string length"); return newstring(""); } return newstring(res.getbuf(), res.length()); } s = newstring(word, p-word-1); @@ -389,7 +401,7 @@ screrr: char *lookup(char *n, int levels) // find value of ident referenced with $ in exp { - if(levels > 1) n = exchangestr(n, lookup(newstring(n), min(levels - 1, 2))); // nested ("$$var"), limit to three levels + if(levels > 1) n = exchangestr(n, lookup(newstring(n), min(levels - 1, CSLIMIT_LOOKUPNESTING - 1))); // nested ("$$var"), limit to three levels ident *id = idents->access(n); if(id) switch(id->type) { @@ -404,18 +416,18 @@ char *lookup(char *n, int levels) // find value of ide return n; } -char *parseword(const char *&p, int arg, int *infix) // parse single argument, including expressions +char *parseword(const char *&p, int arg, int *infix, int rec) // parse single argument, including expressions { p += strspn(p, " \t"); if(p[0]=='/' && p[1]=='/') p += strcspn(p, "\n\r\0"); if(*p=='"') return parsequotes(p); - if(*p=='(' && !currentcontextisolated) return parseexp(p, ')'); - if(*p=='[' && !currentcontextisolated) return parseexp(p, ']'); + if(*p=='(' && !currentcontextisolated) return parseexp(p, ')', rec + 1); + if(*p=='[' && !currentcontextisolated) return parseexp(p, ']', rec + 1); int lvls = currentcontextisolated ? 0 : strspn(p, "$"); if(lvls && (p[lvls]=='(' || p[lvls]=='[')) { // $() $[] p += lvls; - char *b = parseexp(p, *p == '(' ? ')' : ']'); + char *b = parseexp(p, *p == '(' ? ')' : ']', rec + 1); if(b) return lookup(b, lvls); } const char *word = p; @@ -436,6 +448,7 @@ char *conc(const char **w, int n, bool space) wlen.setsize(0); int len = space ? max(n-1, 0) : 0; loopj(n) len += wlen.add((int)strlen(w[j])); + if(len > CSLIMIT_STRINGLEN) { cslimiterr("string length"); return newstring(""); } char *r = newstring("", len), *res = r; loopi(n) { @@ -479,12 +492,14 @@ COMMAND(result, "s"); void resultcharvector(const vector &res, int adj) // use char vector as result, optionally remove some bytes at the end { adj += res.length(); + if(adj > CSLIMIT_STRINGLEN) { cslimiterr("string length"); commandret = newstring(""); return; } commandret = adj <= 0 ? newstring("", 0) : newstring(res.getbuf(), (size_t) adj); } -char *executeret(const char *p) // all evaluation happens here, recursively +char *executeret(const char *p) // all evaluation happens here, recursively { if(!p || !p[0]) return NULL; + if(executionstack.length() > CSLIMIT_RECURSION) { cslimiterr("recursion depth"); return NULL; } executionstack.add(p); const int MAXWORDS = 25; // limit, remove char *w[MAXWORDS], emptychar = '\0'; @@ -498,7 +513,7 @@ char *executeret(const char *p) // all evaluation hap { w[i] = &emptychar; if(i>numargs) continue; - char *s = parseword(p, i, &infix); // parse and evaluate exps + char *s = parseword(p, i, &infix, executionstack.length()); // parse and evaluate exps if(s) w[i] = s; else numargs = i; } @@ -924,6 +939,30 @@ void complete(char *s, bool reversedirection) } #endif +void cleancubescript(char *buf) // drop easy to spot surplus whitespace and comments +{ + if(!buf || !*buf) return; + char *src = buf, c; + bool quot = false; + for(char l = 0; (c = *src++); l = c) + { + if(c == '\r') c = '\n'; + if(quot) + { + if((c == '"' && l != '\\') || c == '\n') quot = false; + } + else + { + if(c == l && isspace(c)) continue; + if(c == ' ' && l == '\n') continue; + if(c == '/' && *src == '/') { src += strcspn(src, "\n\r\0"); continue; } + if(c == '"') quot = true; + } + *buf++ = c; + } + *buf = '\0'; +} + bool execfile(const char *cfgfile) { string s; @@ -936,6 +975,7 @@ bool execfile(const char *cfgfile) resetcontext(); return false; } + if(!currentcontextisolated && !strstr(s, "docs.cfg")) cleancubescript(buf); execute(buf); delete[] buf; resetcontext(); @@ -992,10 +1032,12 @@ COMMAND(execdir, "s"); // () and [] expressions, any control construct can be defined trivially. void ifthen(char *cond, char *thenp, char *elsep) { commandret = executeret(cond[0]!='0' ? thenp : elsep); } -COMMANDN(if, ifthen, "sss"); +bool __dummy_ifthen = addcommand("if", (void (*)())ifthen, "sss"); +//COMMANDN(if, ifthen, "sss"); // CB seriously trips over this one ;) void loopa(char *var, int *times, char *body) { + if(*times > CSLIMIT_ITERATION) { cslimiterr("loop iterations"); return; } int t = *times; if(t<=0) return; ident *id = newident(var, execcontext); @@ -1033,6 +1075,7 @@ COMMANDN(loop, loopa, "sis"); void whilea(char *cond, char *body) { loop_level++; + int its = 0; while(execute(cond)) { execute(body); @@ -1042,6 +1085,7 @@ void whilea(char *cond, char *body) loop_break = false; break; } + if(++its > CSLIMIT_ITERATION) { cslimiterr("loop iterations"); return; } } loop_level--; } @@ -1105,9 +1149,9 @@ void explodelist(const char *s, vector &elems) #ifdef STANDALONE char *newelem = *elem == '"' ? newstring(elem + 1, s - elem - (s[-1]=='"' ? 2 : 1)) : newstring(elem, s-elem); #else - size_t len; + size_t len = 0; char *newelem = *elem == '"' ? newstring(elem + 1, (len = s - elem - (s[-1]=='"' ? 2 : 1))) : newstring(elem, s-elem); - if(*elem == '\"') filterrichtext(newelem, newelem, len); + if(*elem == '"') filterrichtext(newelem, newelem, len); #endif elems.add(newelem); whitespaceskip; @@ -1119,7 +1163,7 @@ void looplist(char *list, char *varlist, char *body, bool withi) vector vars; explodelist(varlist, vars); if(vars.length() < 1) return; - int varslen = vars.length(); + int columns = vars.length(); if(withi) vars.add(newstring("i")); vector ids; bool ok = true; @@ -1131,12 +1175,13 @@ void looplist(char *list, char *varlist, char *body, bool withi) explodelist(list, elems); loopv(ids) pushident(*ids[i], newstring("")); loop_level++; - for(int i = 0; i <= elems.length() - varslen; i += varslen) + if(elems.length() / columns > CSLIMIT_ITERATION) cslimiterr("loop iterations"); + else for(int i = 0; i <= elems.length() - columns; i += columns) { loopj(vars.length()) { if(ids[j]->action != ids[j]->executing) delete[] ids[j]->action; - if(j < varslen) + if(j < columns) { ids[j]->action = elems[i + j]; elems[i + j] = NULL; @@ -1706,6 +1751,8 @@ void scripterr() ASSERT(execcontext >= 0 && execcontext < IEXC_NUM); if(curcontext) conoutf("(%s: %s [%s])", curcontext, curinfo, contextnames[execcontext]); else conoutf("(from console or builtin [%s])", contextnames[execcontext]); + clientlogf("exec nesting level: %d", executionstack.length()); + clientlogf("%s", executionstack.length() ? executionstack.last() : ":::nevermind:::"); } void setcontext(const char *context, const char *info) @@ -1730,6 +1777,12 @@ void dumpexecutionstack(stream *f) } } +void cslimiterr(const char*msg) +{ + conoutf("\f3[ERROR] cubescript hard limit reached: max %s exceeded (check log for details)", msg); + scripterr(); +} + #ifndef STANDALONE void listoptions(char *s) diff --git a/source/src/protos.h b/source/src/protos.h index c057c855..a9455f3e 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -312,7 +312,7 @@ struct serverinfo extern serverinfo *getconnectedserverinfo(); extern void pingservers(); -extern void updatefrommaster(int force); +extern void updatefrommaster(int *force); // http struct httpget diff --git a/source/src/serverbrowser.cpp b/source/src/serverbrowser.cpp index 798cd3b4..5af6173e 100644 --- a/source/src/serverbrowser.cpp +++ b/source/src/serverbrowser.cpp @@ -1170,7 +1170,7 @@ void refreshservers(void *menu, bool init) bool serverskey(void *menu, int code, bool isdown, int unicode) { - const int fk[] = { SDLK_1, SDLK_2, SDLK_3, SDLK_4, SDLK_5, SDLK_6, SDLK_7, SDLK_8, SDLK_9, SDLK_0 }; + int fk[] = { SDLK_1, SDLK_2, SDLK_3, SDLK_4, SDLK_5, SDLK_6, SDLK_7, SDLK_8, SDLK_9, SDLK_0 }, gogetem = 1; if(!isdown) return false; loopi(sizeof(fk)/sizeof(fk[0])) if(code == fk[i] && favcats.inrange(i)) { @@ -1225,7 +1225,7 @@ bool serverskey(void *menu, int code, bool isdown, int unicode) return true; case SDLK_F5: - updatefrommaster(1); + updatefrommaster(&gogetem); return true; case SDLK_F6: @@ -1405,7 +1405,7 @@ void retrieveservers(vector &data) VARP(masterupdatefrequency, 1, 60*60, 24*60*60); VAR(msctrl, 1, 1, INT_MAX); -void updatefrommaster(int force) +void updatefrommaster(int *force) { static int lastupdate = 0; if(lastupdate==0) cllock = true; @@ -1439,7 +1439,7 @@ void updatefrommaster(int force) COMMANDF(addserver, "sii", (char *n, int *p, int *w) { addserver(n, *p, *w); }); COMMAND(clearservers, ""); -COMMANDF(updatefrommaster, "i", (int *f) { updatefrommaster(*f); }); +COMMAND(updatefrommaster, "i"); void writeservercfg() { diff --git a/source/src/zip.cpp b/source/src/zip.cpp index 65f31d07..41a4ce1a 100644 --- a/source/src/zip.cpp +++ b/source/src/zip.cpp @@ -798,4 +798,3 @@ void listzipfiles(const char *dir, const char *ext, vector &files) // (d }); } -