aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoberto Ierusalimschy <roberto@inf.puc-rio.br>2019-02-20 11:11:12 -0300
committerRoberto Ierusalimschy <roberto@inf.puc-rio.br>2019-02-20 11:11:12 -0300
commitf53caf1f863f140de1c1af51906e658c9fb7d7d6 (patch)
tree440889bf7622e7706e6bebe91d7d13ade83a89cc
parentcc583a17df76a363e419c960d72422169fae816d (diff)
downloadlpeg-f53caf1f863f140de1c1af51906e658c9fb7d7d6.tar.gz
lpeg-f53caf1f863f140de1c1af51906e658c9fb7d7d6.tar.bz2
lpeg-f53caf1f863f140de1c1af51906e658c9fb7d7d6.zip
Avoid stack overflow when handling nested captures
The C code uses recursion to handle nested captures, so a too deep nesting could create a stack overflow. The fix limits the handling of nested captures to 'MAXRECLEVEL' (default is 200 levels).
-rw-r--r--lpcap.c52
-rw-r--r--lpcap.h1
-rw-r--r--lpvm.c3
-rw-r--r--makefile4
-rwxr-xr-xtest.lua10
5 files changed, 50 insertions, 20 deletions
diff --git a/lpcap.c b/lpcap.c
index 26058e2..1c2af0b 100644
--- a/lpcap.c
+++ b/lpcap.c
@@ -441,70 +441,88 @@ static int addonestring (luaL_Buffer *b, CapState *cs, const char *what) {
441} 441}
442 442
443 443
444#if !defined(MAXRECLEVEL)
445#define MAXRECLEVEL 200
446#endif
447
448
444/* 449/*
445** Push all values of the current capture into the stack; returns 450** Push all values of the current capture into the stack; returns
446** number of values pushed 451** number of values pushed
447*/ 452*/
448static int pushcapture (CapState *cs) { 453static int pushcapture (CapState *cs) {
449 lua_State *L = cs->L; 454 lua_State *L = cs->L;
455 int res;
450 luaL_checkstack(L, 4, "too many captures"); 456 luaL_checkstack(L, 4, "too many captures");
457 if (cs->reclevel++ > MAXRECLEVEL)
458 return luaL_error(L, "subcapture nesting too deep");
451 switch (captype(cs->cap)) { 459 switch (captype(cs->cap)) {
452 case Cposition: { 460 case Cposition: {
453 lua_pushinteger(L, cs->cap->s - cs->s + 1); 461 lua_pushinteger(L, cs->cap->s - cs->s + 1);
454 cs->cap++; 462 cs->cap++;
455 return 1; 463 res = 1;
464 break;
456 } 465 }
457 case Cconst: { 466 case Cconst: {
458 pushluaval(cs); 467 pushluaval(cs);
459 cs->cap++; 468 cs->cap++;
460 return 1; 469 res = 1;
470 break;
461 } 471 }
462 case Carg: { 472 case Carg: {
463 int arg = (cs->cap++)->idx; 473 int arg = (cs->cap++)->idx;
464 if (arg + FIXEDARGS > cs->ptop) 474 if (arg + FIXEDARGS > cs->ptop)
465 return luaL_error(L, "reference to absent extra argument #%d", arg); 475 return luaL_error(L, "reference to absent extra argument #%d", arg);
466 lua_pushvalue(L, arg + FIXEDARGS); 476 lua_pushvalue(L, arg + FIXEDARGS);
467 return 1; 477 res = 1;
478 break;
468 } 479 }
469 case Csimple: { 480 case Csimple: {
470 int k = pushnestedvalues(cs, 1); 481 int k = pushnestedvalues(cs, 1);
471 lua_insert(L, -k); /* make whole match be first result */ 482 lua_insert(L, -k); /* make whole match be first result */
472 return k; 483 res = k;
484 break;
473 } 485 }
474 case Cruntime: { 486 case Cruntime: {
475 lua_pushvalue(L, (cs->cap++)->idx); /* value is in the stack */ 487 lua_pushvalue(L, (cs->cap++)->idx); /* value is in the stack */
476 return 1; 488 res = 1;
489 break;
477 } 490 }
478 case Cstring: { 491 case Cstring: {
479 luaL_Buffer b; 492 luaL_Buffer b;
480 luaL_buffinit(L, &b); 493 luaL_buffinit(L, &b);
481 stringcap(&b, cs); 494 stringcap(&b, cs);
482 luaL_pushresult(&b); 495 luaL_pushresult(&b);
483 return 1; 496 res = 1;
497 break;
484 } 498 }
485 case Csubst: { 499 case Csubst: {
486 luaL_Buffer b; 500 luaL_Buffer b;
487 luaL_buffinit(L, &b); 501 luaL_buffinit(L, &b);
488 substcap(&b, cs); 502 substcap(&b, cs);
489 luaL_pushresult(&b); 503 luaL_pushresult(&b);
490 return 1; 504 res = 1;
505 break;
491 } 506 }
492 case Cgroup: { 507 case Cgroup: {
493 if (cs->cap->idx == 0) /* anonymous group? */ 508 if (cs->cap->idx == 0) /* anonymous group? */
494 return pushnestedvalues(cs, 0); /* add all nested values */ 509 res = pushnestedvalues(cs, 0); /* add all nested values */
495 else { /* named group: add no values */ 510 else { /* named group: add no values */
496 nextcap(cs); /* skip capture */ 511 nextcap(cs); /* skip capture */
497 return 0; 512 res = 0;
498 } 513 }
514 break;
499 } 515 }
500 case Cbackref: return backrefcap(cs); 516 case Cbackref: res = backrefcap(cs); break;
501 case Ctable: return tablecap(cs); 517 case Ctable: res = tablecap(cs); break;
502 case Cfunction: return functioncap(cs); 518 case Cfunction: res = functioncap(cs); break;
503 case Cnum: return numcap(cs); 519 case Cnum: res = numcap(cs); break;
504 case Cquery: return querycap(cs); 520 case Cquery: res = querycap(cs); break;
505 case Cfold: return foldcap(cs); 521 case Cfold: res = foldcap(cs); break;
506 default: assert(0); return 0; 522 default: assert(0); res = 0;
507 } 523 }
524 cs->reclevel--;
525 return res;
508} 526}
509 527
510 528
@@ -521,7 +539,7 @@ int getcaptures (lua_State *L, const char *s, const char *r, int ptop) {
521 int n = 0; 539 int n = 0;
522 if (!isclosecap(capture)) { /* is there any capture? */ 540 if (!isclosecap(capture)) { /* is there any capture? */
523 CapState cs; 541 CapState cs;
524 cs.ocap = cs.cap = capture; cs.L = L; 542 cs.ocap = cs.cap = capture; cs.L = L; cs.reclevel = 0;
525 cs.s = s; cs.valuecached = 0; cs.ptop = ptop; 543 cs.s = s; cs.valuecached = 0; cs.ptop = ptop;
526 do { /* collect their values */ 544 do { /* collect their values */
527 n += pushcapture(&cs); 545 n += pushcapture(&cs);
diff --git a/lpcap.h b/lpcap.h
index bf52b67..dc10d69 100644
--- a/lpcap.h
+++ b/lpcap.h
@@ -44,6 +44,7 @@ typedef struct CapState {
44 int ptop; /* index of last argument to 'match' */ 44 int ptop; /* index of last argument to 'match' */
45 const char *s; /* original string */ 45 const char *s; /* original string */
46 int valuecached; /* value stored in cache slot */ 46 int valuecached; /* value stored in cache slot */
47 int reclevel; /* recursion level */
47} CapState; 48} CapState;
48 49
49 50
diff --git a/lpvm.c b/lpvm.c
index 52c14ae..813d3fb 100644
--- a/lpvm.c
+++ b/lpvm.c
@@ -296,7 +296,8 @@ const char *match (lua_State *L, const char *o, const char *s, const char *e,
296 CapState cs; 296 CapState cs;
297 int rem, res, n; 297 int rem, res, n;
298 int fr = lua_gettop(L) + 1; /* stack index of first result */ 298 int fr = lua_gettop(L) + 1; /* stack index of first result */
299 cs.s = o; cs.L = L; cs.ocap = capture; cs.ptop = ptop; 299 cs.reclevel = 0; cs.L = L;
300 cs.s = o; cs.ocap = capture; cs.ptop = ptop;
300 n = runtimecap(&cs, capture + captop, s, &rem); /* call function */ 301 n = runtimecap(&cs, capture + captop, s, &rem); /* call function */
301 captop -= n; /* remove nested captures */ 302 captop -= n; /* remove nested captures */
302 ndyncap -= rem; /* update number of dynamic captures */ 303 ndyncap -= rem; /* update number of dynamic captures */
diff --git a/makefile b/makefile
index d803c12..9518034 100644
--- a/makefile
+++ b/makefile
@@ -29,11 +29,11 @@ FILES = lpvm.o lpcap.o lptree.o lpcode.o lpprint.o
29 29
30# For Linux 30# For Linux
31linux: 31linux:
32 make lpeg.so "DLLFLAGS = -shared -fPIC" 32 $(MAKE) lpeg.so "DLLFLAGS = -shared -fPIC"
33 33
34# For Mac OS 34# For Mac OS
35macosx: 35macosx:
36 make lpeg.so "DLLFLAGS = -bundle -undefined dynamic_lookup" 36 $(MAKE) lpeg.so "DLLFLAGS = -bundle -undefined dynamic_lookup"
37 37
38lpeg.so: $(FILES) 38lpeg.so: $(FILES)
39 env $(CC) $(DLLFLAGS) $(FILES) -o lpeg.so 39 env $(CC) $(DLLFLAGS) $(FILES) -o lpeg.so
diff --git a/test.lua b/test.lua
index 476da0a..8f9f574 100755
--- a/test.lua
+++ b/test.lua
@@ -424,6 +424,16 @@ do
424end 424end
425 425
426 426
427do
428 -- nesting of captures too deep
429 local p = m.C(1)
430 for i = 1, 300 do
431 p = m.Ct(p)
432 end
433 checkerr("too deep", p.match, p, "x")
434end
435
436
427-- tests for non-pattern as arguments to pattern functions 437-- tests for non-pattern as arguments to pattern functions
428 438
429p = { ('a' * m.V(1))^-1 } * m.P'b' * { 'a' * m.V(2); m.V(1)^-1 } 439p = { ('a' * m.V(1))^-1 } * m.P'b' * { 'a' * m.V(2); m.V(1)^-1 }