diff --git a/src/flexdef.h b/src/flexdef.h index adf4fd0a7..2f2f93cc1 100644 --- a/src/flexdef.h +++ b/src/flexdef.h @@ -849,6 +849,10 @@ extern void dataflush(void); /* Report an error message and terminate. */ extern void flexerror(const char *); +/* Reject `[` / `]` in a %option= value to prevent m4-quote injection. */ +extern void reject_brackets_in_option_value(const char *opt_name, + const char *val); + /* Report a fatal error message and terminate. */ extern void flexfatal(const char *); diff --git a/src/misc.c b/src/misc.c index 56d2abab6..39b5e34f3 100644 --- a/src/misc.c +++ b/src/misc.c @@ -205,6 +205,30 @@ void flexerror (const char *msg) } +/* reject_brackets_in_option_value - bail out if a %option= value contains + * `[` or `]`. Such characters break out of the m4 quoting scheme that + * flex uses to interpolate option values into m4 input (see PR #81 which + * added the same escaping for action / code-block contexts via + * ESCAPED_QSTART / ESCAPED_QEND in src/scan.l). The OPTION-state + * quoted-string rule at src/scan.l:480 was never plumbed through that + * escape, so without this guard a malicious .l file can inject arbitrary + * m4 (e.g. m4_syscmd) into the output pipeline. + * + * Mirrors the existing prefix= guard that has been in parse.y since + * before PR #81. + */ +void reject_brackets_in_option_value (const char *opt_name, const char *val) +{ + if (val != NULL && (strchr (val, '[') || strchr (val, ']'))) { + char msg[MAXLINE]; + snprintf (msg, sizeof msg, + _("%%option %s value must not contain '[' or ']'"), + opt_name); + flexerror (msg); + } +} + + /* flexfatal - report a fatal error message and terminate */ void flexfatal (const char *msg) diff --git a/src/parse.y b/src/parse.y index 80e7f0977..830002938 100644 --- a/src/parse.y +++ b/src/parse.y @@ -199,27 +199,33 @@ optionlist : optionlist option option : TOK_OUTFILE '=' NAME { + reject_brackets_in_option_value("outfile", nmstr); env.outfilename = xstrdup(nmstr); env.did_outfilename = 1; } | TOK_EXTRA_TYPE '=' NAME - { extra_type = xstrdup(nmstr); } + { reject_brackets_in_option_value("extra-type", nmstr); + extra_type = xstrdup(nmstr); } | TOK_PREFIX '=' NAME - { ctrl.prefix = xstrdup(nmstr); - if (strchr(ctrl.prefix, '[') || strchr(ctrl.prefix, ']')) - flexerror(_("Prefix must not contain [ or ]")); } + { reject_brackets_in_option_value("prefix", nmstr); + ctrl.prefix = xstrdup(nmstr); } | TOK_YYCLASS '=' NAME - { ctrl.yyclass = xstrdup(nmstr); } + { reject_brackets_in_option_value("yyclass", nmstr); + ctrl.yyclass = xstrdup(nmstr); } | TOK_HEADER_FILE '=' NAME - { env.headerfilename = xstrdup(nmstr); } + { reject_brackets_in_option_value("header-file", nmstr); + env.headerfilename = xstrdup(nmstr); } | TOK_YYLMAX '=' TOK_NUMERIC { ctrl.yylmax = nmval; } | TOK_YYDECL '=' NAME - { ctrl.yydecl = xstrdup(nmstr); } + { reject_brackets_in_option_value("yydecl", nmstr); + ctrl.yydecl = xstrdup(nmstr); } | TOK_PREACTION '=' NAME - { ctrl.preaction = xstrdup(nmstr); } + { reject_brackets_in_option_value("pre-action", nmstr); + ctrl.preaction = xstrdup(nmstr); } | TOK_POSTACTION '=' NAME - { ctrl.postaction = xstrdup(nmstr); } + { reject_brackets_in_option_value("post-action", nmstr); + ctrl.postaction = xstrdup(nmstr); } | TOK_BUFSIZE '=' TOK_NUMERIC { ctrl.bufsize = nmval; } | TOK_EMIT '=' NAME @@ -227,7 +233,8 @@ option : TOK_OUTFILE '=' NAME | TOK_USERINIT '=' NAME { ctrl.userinit = xstrdup(nmstr); } | TOK_YYTERMINATE '=' NAME - { ctrl.yyterminate = xstrdup(nmstr); } + { reject_brackets_in_option_value("yyterminate", nmstr); + ctrl.yyterminate = xstrdup(nmstr); } | TOK_TABLES_FILE '=' NAME { tablesext = true; tablesfilename = xstrdup(nmstr); } ;