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 } |
