diff --git a/src/backend/regex/regc_nfa.c b/src/backend/regex/regc_nfa.c index e474e48c28c97a328a8fa0a2083d9e55e757bdb3..50a762ed7ac0b959fa5405a8dbafffbdb7db4811 100644 --- a/src/backend/regex/regc_nfa.c +++ b/src/backend/regex/regc_nfa.c @@ -683,6 +683,8 @@ delsub(struct nfa * nfa, rp->tmp = rp; /* mark end */ deltraverse(nfa, lp, lp); + if (NISERR()) + return; /* asserts might not hold after failure */ assert(lp->nouts == 0 && rp->nins == 0); /* did the job */ assert(lp->no != FREESTATE && rp->no != FREESTATE); /* no more */ @@ -702,6 +704,13 @@ deltraverse(struct nfa * nfa, struct arc *a; struct state *to; + /* Since this is recursive, it could be driven to stack overflow */ + if (STACK_TOO_DEEP(nfa->v->re)) + { + NERR(REG_ETOOBIG); + return; + } + if (s->nouts == 0) return; /* nothing to do */ if (s->tmp != NULL) @@ -713,6 +722,8 @@ deltraverse(struct nfa * nfa, { to = a->to; deltraverse(nfa, leftend, to); + if (NISERR()) + return; /* asserts might not hold after failure */ assert(to->nouts == 0 || to->tmp != NULL); freearc(nfa, a); if (to->nins == 0 && to->tmp == NULL) @@ -767,6 +778,13 @@ duptraverse(struct nfa * nfa, { struct arc *a; + /* Since this is recursive, it could be driven to stack overflow */ + if (STACK_TOO_DEEP(nfa->v->re)) + { + NERR(REG_ETOOBIG); + return; + } + if (s->tmp != NULL) return; /* already done */ @@ -796,6 +814,13 @@ cleartraverse(struct nfa * nfa, { struct arc *a; + /* Since this is recursive, it could be driven to stack overflow */ + if (STACK_TOO_DEEP(nfa->v->re)) + { + NERR(REG_ETOOBIG); + return; + } + if (s->tmp == NULL) return; s->tmp = NULL; @@ -1284,7 +1309,7 @@ fixempties(struct nfa * nfa, */ for (s = nfa->states; s != NULL && !NISERR(); s = s->next) { - for (s2 = emptyreachable(s, s); s2 != s && !NISERR(); s2 = nexts) + for (s2 = emptyreachable(nfa, s, s); s2 != s && !NISERR(); s2 = nexts) { /* * If s2 is doomed, we decide that (1) we will always push arcs @@ -1342,19 +1367,28 @@ fixempties(struct nfa * nfa, * * The maximum recursion depth here is equal to the length of the longest * loop-free chain of EMPTY arcs, which is surely no more than the size of - * the NFA, and in practice will be a lot less than that. + * the NFA ... but that could still be enough to cause trouble. */ static struct state * -emptyreachable(struct state * s, struct state * lastfound) +emptyreachable(struct nfa * nfa, + struct state * s, + struct state * lastfound) { struct arc *a; + /* Since this is recursive, it could be driven to stack overflow */ + if (STACK_TOO_DEEP(nfa->v->re)) + { + NERR(REG_ETOOBIG); + return lastfound; + } + s->tmp = lastfound; lastfound = s; for (a = s->outs; a != NULL; a = a->outchain) { if (a->type == EMPTY && a->to->tmp == NULL) - lastfound = emptyreachable(a->to, lastfound); + lastfound = emptyreachable(nfa, a->to, lastfound); } return lastfound; } @@ -1433,19 +1467,22 @@ cleanup(struct nfa * nfa) struct state *nexts; int n; + if (NISERR()) + return; + /* clear out unreachable or dead-end states */ /* use pre to mark reachable, then post to mark can-reach-post */ markreachable(nfa, nfa->pre, (struct state *) NULL, nfa->pre); markcanreach(nfa, nfa->post, nfa->pre, nfa->post); - for (s = nfa->states; s != NULL; s = nexts) + for (s = nfa->states; s != NULL && !NISERR(); s = nexts) { nexts = s->next; if (s->tmp != nfa->post && !s->flag) dropstate(nfa, s); } - assert(nfa->post->nins == 0 || nfa->post->tmp == nfa->post); + assert(NISERR() || nfa->post->nins == 0 || nfa->post->tmp == nfa->post); cleartraverse(nfa, nfa->pre); - assert(nfa->post->nins == 0 || nfa->post->tmp == NULL); + assert(NISERR() || nfa->post->nins == 0 || nfa->post->tmp == NULL); /* the nins==0 (final unreachable) case will be caught later */ /* renumber surviving states */ @@ -1466,6 +1503,13 @@ markreachable(struct nfa * nfa, { struct arc *a; + /* Since this is recursive, it could be driven to stack overflow */ + if (STACK_TOO_DEEP(nfa->v->re)) + { + NERR(REG_ETOOBIG); + return; + } + if (s->tmp != okay) return; s->tmp = mark; @@ -1485,6 +1529,13 @@ markcanreach(struct nfa * nfa, { struct arc *a; + /* Since this is recursive, it could be driven to stack overflow */ + if (STACK_TOO_DEEP(nfa->v->re)) + { + NERR(REG_ETOOBIG); + return; + } + if (s->tmp != okay) return; s->tmp = mark; @@ -1502,6 +1553,9 @@ analyze(struct nfa * nfa) struct arc *a; struct arc *aa; + if (NISERR()) + return 0; + if (nfa->pre->outs == NULL) return REG_UIMPOSSIBLE; for (a = nfa->pre->outs; a != NULL; a = a->outchain) diff --git a/src/backend/regex/regcomp.c b/src/backend/regex/regcomp.c index d137ac0d3d16194a17b5358ec9cf3a6ee96e2e45..62f6359e3452b1e4fecd0c2c2b0a0422971c21e5 100644 --- a/src/backend/regex/regcomp.c +++ b/src/backend/regex/regcomp.c @@ -34,7 +34,7 @@ #include "regex/regguts.h" -#include "miscadmin.h" /* needed by rcancelrequested() */ +#include "miscadmin.h" /* needed by rcancelrequested/rstacktoodeep */ /* * forward declarations, up here so forward datatypes etc. are defined early @@ -70,6 +70,7 @@ static int newlacon(struct vars *, struct state *, struct state *, int); static void freelacons(struct subre *, int); static void rfree(regex_t *); static int rcancelrequested(void); +static int rstacktoodeep(void); #ifdef REG_DEBUG static void dump(regex_t *, FILE *); @@ -152,7 +153,7 @@ static int push(struct nfa *, struct arc *); #define COMPATIBLE 3 /* compatible but not satisfied yet */ static int combine(struct arc *, struct arc *); static void fixempties(struct nfa *, FILE *); -static struct state *emptyreachable(struct state *, struct state *); +static struct state *emptyreachable(struct nfa *, struct state *, struct state *); static void replaceempty(struct nfa *, struct state *, struct state *); static void cleanup(struct nfa *); static void markreachable(struct nfa *, struct state *, struct state *, struct state *); @@ -279,7 +280,8 @@ struct vars /* static function list */ static const struct fns functions = { rfree, /* regfree insides */ - rcancelrequested /* check for cancel request */ + rcancelrequested, /* check for cancel request */ + rstacktoodeep /* check for stack getting dangerously deep */ }; @@ -1626,6 +1628,16 @@ subre(struct vars * v, { struct subre *ret = v->treefree; + /* + * Checking for stack overflow here is sufficient to protect parse() and + * its recursive subroutines. + */ + if (STACK_TOO_DEEP(v->re)) + { + ERR(REG_ETOOBIG); + return NULL; + } + if (ret != NULL) v->treefree = ret->left; else @@ -1938,6 +1950,22 @@ rcancelrequested(void) return InterruptPending && (QueryCancelPending || ProcDiePending); } +/* + * rstacktoodeep - check for stack getting dangerously deep + * + * Return nonzero to fail the operation with error code REG_ETOOBIG, + * zero to keep going + * + * The current implementation is Postgres-specific. If we ever get around + * to splitting the regex code out as a standalone library, there will need + * to be some API to let applications define a callback function for this. + */ +static int +rstacktoodeep(void) +{ + return stack_is_too_deep(); +} + #ifdef REG_DEBUG /* diff --git a/src/backend/regex/rege_dfa.c b/src/backend/regex/rege_dfa.c index a9305dd7ccd231e8af3ecf24b31be6991fa22b26..a37e4b0ef96660c47b01c9fe32e10a0b39d511b9 100644 --- a/src/backend/regex/rege_dfa.c +++ b/src/backend/regex/rege_dfa.c @@ -627,6 +627,13 @@ lacon(struct vars * v, struct smalldfa sd; chr *end; + /* Since this is recursive, it could be driven to stack overflow */ + if (STACK_TOO_DEEP(v->re)) + { + ERR(REG_ETOOBIG); + return 0; + } + n = co - pcnfa->ncolors; assert(n < v->g->nlacons && v->g->lacons != NULL); FDEBUG(("=== testing lacon %d\n", n)); diff --git a/src/backend/regex/regexec.c b/src/backend/regex/regexec.c index d18672c7c7c3b9bb32d84e37003fd16d5140c400..8a21f2cb7870b544165d363f62a0424f067e23cb 100644 --- a/src/backend/regex/regexec.c +++ b/src/backend/regex/regexec.c @@ -624,6 +624,9 @@ cdissect(struct vars * v, /* handy place to check for operation cancel */ if (CANCEL_REQUESTED(v->re)) return REG_CANCEL; + /* ... and stack overrun */ + if (STACK_TOO_DEEP(v->re)) + return REG_ETOOBIG; switch (t->op) { diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index d1f43c5c8a24d336c1bf524c6d1f83e09e87d391..aee13aec757b87aaba93c043ebb7528fb5a66d8a 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -3081,15 +3081,32 @@ restore_stack_base(pg_stack_base_t base) } /* - * check_stack_depth: check for excessively deep recursion + * check_stack_depth/stack_is_too_deep: check for excessively deep recursion * * This should be called someplace in any recursive routine that might possibly * recurse deep enough to overflow the stack. Most Unixen treat stack * overflow as an unrecoverable SIGSEGV, so we want to error out ourselves * before hitting the hardware limit. + * + * check_stack_depth() just throws an error summarily. stack_is_too_deep() + * can be used by code that wants to handle the error condition itself. */ void check_stack_depth(void) +{ + if (stack_is_too_deep()) + { + ereport(ERROR, + (errcode(ERRCODE_STATEMENT_TOO_COMPLEX), + errmsg("stack depth limit exceeded"), + errhint("Increase the configuration parameter \"max_stack_depth\" (currently %dkB), " + "after ensuring the platform's stack depth limit is adequate.", + max_stack_depth))); + } +} + +bool +stack_is_too_deep(void) { char stack_top_loc; long stack_depth; @@ -3115,14 +3132,7 @@ check_stack_depth(void) */ if (stack_depth > max_stack_depth_bytes && stack_base_ptr != NULL) - { - ereport(ERROR, - (errcode(ERRCODE_STATEMENT_TOO_COMPLEX), - errmsg("stack depth limit exceeded"), - errhint("Increase the configuration parameter \"max_stack_depth\" (currently %dkB), " - "after ensuring the platform's stack depth limit is adequate.", - max_stack_depth))); - } + return true; /* * On IA64 there is a separate "register" stack that requires its own @@ -3137,15 +3147,10 @@ check_stack_depth(void) if (stack_depth > max_stack_depth_bytes && register_stack_base_ptr != NULL) - { - ereport(ERROR, - (errcode(ERRCODE_STATEMENT_TOO_COMPLEX), - errmsg("stack depth limit exceeded"), - errhint("Increase the configuration parameter \"max_stack_depth\" (currently %dkB), " - "after ensuring the platform's stack depth limit is adequate.", - max_stack_depth))); - } + return true; #endif /* IA64 */ + + return false; } /* GUC check hook for max_stack_depth */ diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 80ac7329dcea18744fb67b0bbcfed3d0158c2a33..ff695aae276604157e72b9cfc573bee82bfb735c 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -268,6 +268,7 @@ typedef char *pg_stack_base_t; extern pg_stack_base_t set_stack_base(void); extern void restore_stack_base(pg_stack_base_t base); extern void check_stack_depth(void); +extern bool stack_is_too_deep(void); /* in tcop/utility.c */ extern void PreventCommandIfReadOnly(const char *cmdname); diff --git a/src/include/regex/regguts.h b/src/include/regex/regguts.h index fccaf298bf1328caea336caea4ac8ccaab6033f6..327775235514e96de2352a1665e81db2a95a5a4a 100644 --- a/src/include/regex/regguts.h +++ b/src/include/regex/regguts.h @@ -449,11 +449,15 @@ struct fns { void FUNCPTR(free, (regex_t *)); int FUNCPTR(cancel_requested, (void)); + int FUNCPTR(stack_too_deep, (void)); }; #define CANCEL_REQUESTED(re) \ ((*((struct fns *) (re)->re_fns)->cancel_requested) ()) +#define STACK_TOO_DEEP(re) \ + ((*((struct fns *) (re)->re_fns)->stack_too_deep) ()) + /* * the insides of a regex_t, hidden behind a void *