diff options
author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2019-02-20 11:11:12 -0300 |
---|---|---|
committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2019-02-20 11:11:12 -0300 |
commit | f53caf1f863f140de1c1af51906e658c9fb7d7d6 (patch) | |
tree | 440889bf7622e7706e6bebe91d7d13ade83a89cc | |
parent | cc583a17df76a363e419c960d72422169fae816d (diff) | |
download | lpeg-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.c | 52 | ||||
-rw-r--r-- | lpcap.h | 1 | ||||
-rw-r--r-- | lpvm.c | 3 | ||||
-rw-r--r-- | makefile | 4 | ||||
-rwxr-xr-x | test.lua | 10 |
5 files changed, 50 insertions, 20 deletions
@@ -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 | */ |
448 | static int pushcapture (CapState *cs) { | 453 | static 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); |
@@ -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 | ||
@@ -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 */ |
@@ -29,11 +29,11 @@ FILES = lpvm.o lpcap.o lptree.o lpcode.o lpprint.o | |||
29 | 29 | ||
30 | # For Linux | 30 | # For Linux |
31 | linux: | 31 | linux: |
32 | make lpeg.so "DLLFLAGS = -shared -fPIC" | 32 | $(MAKE) lpeg.so "DLLFLAGS = -shared -fPIC" |
33 | 33 | ||
34 | # For Mac OS | 34 | # For Mac OS |
35 | macosx: | 35 | macosx: |
36 | make lpeg.so "DLLFLAGS = -bundle -undefined dynamic_lookup" | 36 | $(MAKE) lpeg.so "DLLFLAGS = -bundle -undefined dynamic_lookup" |
37 | 37 | ||
38 | lpeg.so: $(FILES) | 38 | lpeg.so: $(FILES) |
39 | env $(CC) $(DLLFLAGS) $(FILES) -o lpeg.so | 39 | env $(CC) $(DLLFLAGS) $(FILES) -o lpeg.so |
@@ -424,6 +424,16 @@ do | |||
424 | end | 424 | end |
425 | 425 | ||
426 | 426 | ||
427 | do | ||
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") | ||
434 | end | ||
435 | |||
436 | |||
427 | -- tests for non-pattern as arguments to pattern functions | 437 | -- tests for non-pattern as arguments to pattern functions |
428 | 438 | ||
429 | p = { ('a' * m.V(1))^-1 } * m.P'b' * { 'a' * m.V(2); m.V(1)^-1 } | 439 | p = { ('a' * m.V(1))^-1 } * m.P'b' * { 'a' * m.V(2); m.V(1)^-1 } |