6897938 zonecfg(1M) hemorrhages memory after Yacc processes commands with syntax errors
--- a/usr/src/cmd/zonecfg/zonecfg.h Tue Aug 17 11:03:38 2010 -0700
+++ b/usr/src/cmd/zonecfg/zonecfg.h Tue Aug 17 13:22:25 2010 -0700
@@ -228,6 +228,12 @@
extern char *res_types[];
extern char *prop_types[];
+/*
+ * NOTE: Only Lex and YACC should use the following functions.
+ */
+extern void assert_no_unclaimed_tokens(void);
+extern char *claim_token(char *);
+
#ifdef __cplusplus
}
#endif
--- a/usr/src/cmd/zonecfg/zonecfg_grammar.y Tue Aug 17 11:03:38 2010 -0700
+++ b/usr/src/cmd/zonecfg/zonecfg_grammar.y Tue Aug 17 13:22:25 2010 -0700
@@ -24,6 +24,14 @@
* Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
*/
+/*
+ * This file defines zonecfg(1M)'s grammar.
+ *
+ * Reduction rules that consume TOKENs must invoke claim_token() immediately
+ * before freeing the TOKENs or adding them to data structures (e.g., cmd) that
+ * will be cleaned up when the parser finishes or encounters errors.
+ */
+
#include <stdio.h>
#include <strings.h>
@@ -159,6 +167,11 @@
%%
+/*
+ * NOTE: Each commands reduction rule must invoke assert_no_unclaimed_tokens()
+ * before it completes if it isn't processing an error. This ensures that
+ * reduction rules properly consume TOKENs.
+ */
commands: command terminator
{
if ($1 != NULL) {
@@ -168,6 +181,7 @@
bzero(list, sizeof (list_property_t));
num_prop_vals = 0;
}
+ assert_no_unclaimed_tokens();
return (0);
}
| command error terminator
@@ -191,6 +205,7 @@
}
| terminator
{
+ assert_no_unclaimed_tokens();
return (0);
}
@@ -228,7 +243,7 @@
cmd = $$;
$$->cmd_handler = &add_func;
$$->cmd_argc = 1;
- $$->cmd_argv[0] = $2;
+ $$->cmd_argv[0] = claim_token($2);
$$->cmd_argv[1] = NULL;
}
| ADD resource_type
@@ -269,7 +284,7 @@
cmd = $$;
$$->cmd_handler = &cancel_func;
$$->cmd_argc = 1;
- $$->cmd_argv[0] = $2;
+ $$->cmd_argv[0] = claim_token($2);
$$->cmd_argv[1] = NULL;
}
@@ -289,7 +304,7 @@
cmd = $$;
$$->cmd_handler = &create_func;
$$->cmd_argc = 1;
- $$->cmd_argv[0] = $2;
+ $$->cmd_argv[0] = claim_token($2);
$$->cmd_argv[1] = NULL;
}
| CREATE TOKEN TOKEN
@@ -299,8 +314,8 @@
cmd = $$;
$$->cmd_handler = &create_func;
$$->cmd_argc = 2;
- $$->cmd_argv[0] = $2;
- $$->cmd_argv[1] = $3;
+ $$->cmd_argv[0] = claim_token($2);
+ $$->cmd_argv[1] = claim_token($3);
$$->cmd_argv[2] = NULL;
}
| CREATE TOKEN TOKEN TOKEN
@@ -310,9 +325,9 @@
cmd = $$;
$$->cmd_handler = &create_func;
$$->cmd_argc = 3;
- $$->cmd_argv[0] = $2;
- $$->cmd_argv[1] = $3;
- $$->cmd_argv[2] = $4;
+ $$->cmd_argv[0] = claim_token($2);
+ $$->cmd_argv[1] = claim_token($3);
+ $$->cmd_argv[2] = claim_token($4);
$$->cmd_argv[3] = NULL;
}
@@ -332,7 +347,7 @@
cmd = $$;
$$->cmd_handler = &commit_func;
$$->cmd_argc = 1;
- $$->cmd_argv[0] = $2;
+ $$->cmd_argv[0] = claim_token($2);
$$->cmd_argv[1] = NULL;
}
@@ -352,7 +367,7 @@
cmd = $$;
$$->cmd_handler = &delete_func;
$$->cmd_argc = 1;
- $$->cmd_argv[0] = $2;
+ $$->cmd_argv[0] = claim_token($2);
$$->cmd_argv[1] = NULL;
}
@@ -372,7 +387,7 @@
cmd = $$;
$$->cmd_handler = &end_func;
$$->cmd_argc = 1;
- $$->cmd_argv[0] = $2;
+ $$->cmd_argv[0] = claim_token($2);
$$->cmd_argv[1] = NULL;
}
@@ -392,7 +407,7 @@
cmd = $$;
$$->cmd_handler = &exit_func;
$$->cmd_argc = 1;
- $$->cmd_argv[0] = $2;
+ $$->cmd_argv[0] = claim_token($2);
$$->cmd_argv[1] = NULL;
}
@@ -412,7 +427,7 @@
cmd = $$;
$$->cmd_handler = &export_func;
$$->cmd_argc = 1;
- $$->cmd_argv[0] = $2;
+ $$->cmd_argv[0] = claim_token($2);
$$->cmd_argv[1] = NULL;
}
| EXPORT TOKEN TOKEN
@@ -422,8 +437,8 @@
cmd = $$;
$$->cmd_handler = &export_func;
$$->cmd_argc = 2;
- $$->cmd_argv[0] = $2;
- $$->cmd_argv[1] = $3;
+ $$->cmd_argv[0] = claim_token($2);
+ $$->cmd_argv[1] = claim_token($3);
$$->cmd_argv[2] = NULL;
}
@@ -443,7 +458,7 @@
cmd = $$;
$$->cmd_handler = &help_func;
$$->cmd_argc = 1;
- $$->cmd_argv[0] = $2;
+ $$->cmd_argv[0] = claim_token($2);
$$->cmd_argv[1] = NULL;
}
@@ -461,7 +476,7 @@
short_usage(CMD_INFO);
(void) fputs("\n", stderr);
usage(B_FALSE, HELP_RES_PROPS);
- free($2);
+ free(claim_token($2));
YYERROR;
}
| INFO resource_type
@@ -687,6 +702,7 @@
short_usage(CMD_REMOVE);
(void) fputs("\n", stderr);
usage(B_FALSE, HELP_RES_PROPS);
+ free(claim_token($2));
YYERROR;
}
| REMOVE resource_type
@@ -705,7 +721,7 @@
$$->cmd_handler = &remove_func;
$$->cmd_res_type = $3;
$$->cmd_argc = 1;
- $$->cmd_argv[0] = $2;
+ $$->cmd_argv[0] = claim_token($2);
$$->cmd_argv[1] = NULL;
}
| REMOVE property_name property_value
@@ -774,7 +790,7 @@
cmd = $$;
$$->cmd_handler = &revert_func;
$$->cmd_argc = 1;
- $$->cmd_argv[0] = $2;
+ $$->cmd_argv[0] = claim_token($2);
$$->cmd_argv[1] = NULL;
}
@@ -889,7 +905,7 @@
YYERROR;
cmd = $$;
$$->cmd_argc = 1;
- $$->cmd_argv[0] = $2;
+ $$->cmd_argv[0] = claim_token($2);
$$->cmd_argv[1] = NULL;
$$->cmd_handler = &set_func;
$$->cmd_prop_nv_pairs = 1;
@@ -929,7 +945,7 @@
cmd = $$;
$$->cmd_handler = &verify_func;
$$->cmd_argc = 1;
- $$->cmd_argv[0] = $2;
+ $$->cmd_argv[0] = claim_token($2);
$$->cmd_argv[1] = NULL;
}
@@ -1046,7 +1062,7 @@
simple_prop_val: TOKEN
{
$$ = simple_prop_val_func($1);
- free($1);
+ free(claim_token($1));
if ($$ == NULL)
YYERROR;
}
@@ -1081,7 +1097,7 @@
complex_piece: property_name EQUAL TOKEN
{
$$ = complex_piece_func($1, $3, NULL);
- free($3);
+ free(claim_token($3));
if ($$ == NULL)
YYERROR;
}
@@ -1098,7 +1114,7 @@
| property_name EQUAL TOKEN COMMA complex_piece
{
$$ = complex_piece_func($1, $3, complex);
- free($3);
+ free(claim_token($3));
if ($$ == NULL)
YYERROR;
}
--- a/usr/src/cmd/zonecfg/zonecfg_lex.l Tue Aug 17 11:03:38 2010 -0700
+++ b/usr/src/cmd/zonecfg/zonecfg_lex.l Tue Aug 17 13:22:25 2010 -0700
@@ -24,17 +24,37 @@
* Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
*/
+#include <assert.h>
#include <string.h>
#include <libintl.h>
#include "zonecfg.h"
#include "zonecfg_grammar.tab.h"
+/*
+ * This constant defines the number of entries added to unclaimed_tokens[]
+ * when it runs out of space.
+ */
+#define UNCLAIMED_TOKENS_BUFFER_GROWTH 4
+
+/*
+ * Invariants:
+ *
+ * unclaimed_tokens == NULL IFF unclaimed_tokens_size == 0
+ * unclaimed_tokens_size == 0 IFF num_unclaimed_tokens == 0
+ */
+static char **unclaimed_tokens; /* TOKENs produced by Lex (see below) */
+ /* but not claimed by YACC reduction */
+ /* rules */
+static unsigned int unclaimed_tokens_size; /* size of unclaimed_tokens */
+static unsigned int num_unclaimed_tokens; /* number of unclaimed TOKENs */
+
int lex_lineno = 1; /* line number for error reporting */
static int state = INITIAL;
extern boolean_t cmd_file_mode;
extern boolean_t saw_error;
extern void yyerror(char *s);
-char *safe_strdup(char *s);
+
+static char *create_token(char *s);
%}
%a 7000
@@ -323,29 +343,29 @@
<CSTATE>"," { return COMMA; }
<TSTATE>[^ \t\n\";=\[\]\(\)]+ {
- yylval.strval = safe_strdup(yytext);
+ yylval.strval = create_token(yytext);
return TOKEN;
}
<LSTATE>[^ \t\n\",;=\[\]\(\)]+ {
- yylval.strval = safe_strdup(yytext);
+ yylval.strval = create_token(yytext);
return TOKEN;
}
<CSTATE>[^ \t\n\",;=\(\)]+ {
- yylval.strval = safe_strdup(yytext);
+ yylval.strval = create_token(yytext);
return TOKEN;
}
<TSTATE>\"[^\"\n]*[\"\n] {
- yylval.strval = safe_strdup(yytext + 1);
+ yylval.strval = create_token(yytext + 1);
if (yylval.strval[yyleng - 2] == '"')
yylval.strval[yyleng - 2] = 0;
return TOKEN;
}
<LSTATE>\"[^\"\n]*[\"\n] {
- yylval.strval = safe_strdup(yytext + 1);
+ yylval.strval = create_token(yytext + 1);
if (yylval.strval[yyleng - 2] == '"')
yylval.strval[yyleng - 2] = 0;
return TOKEN;
@@ -370,8 +390,88 @@
%%
+/*
+ * Assert that there are no unclaimed tokens. This function enforces the
+ * invariants mentioned at the top of this file.
+ */
+void
+assert_no_unclaimed_tokens(void)
+{
+ assert(num_unclaimed_tokens == 0);
+ assert(unclaimed_tokens == NULL);
+ assert(unclaimed_tokens_size == 0);
+}
+
+/*
+ * Claim the specified unclaimed TOKEN. YACC reduction rules that
+ * use TOKENs should invoke this function immediately before freeing the TOKENs
+ * or adding them to data structures that will be cleaned up when the YACC
+ * parser finishes or encounters errors. Reduction rules should only claim the
+ * TOKENs that they use.
+ *
+ * This function returns its argument but does not free its memory.
+ */
char *
-safe_strdup(char *s)
+claim_token(char *token)
+{
+ unsigned int index;
+
+ /*
+ * Find the token in the list of unclaimed tokens.
+ */
+ assert(num_unclaimed_tokens > 0);
+ for (index = 0; index < num_unclaimed_tokens; index++) {
+ if (unclaimed_tokens[index] == token)
+ break;
+ }
+
+ /*
+ * Abort if we didn't find the token.
+ */
+ assert(index != num_unclaimed_tokens);
+
+ /*
+ * Replace the token with the last unclaimed token.
+ */
+ num_unclaimed_tokens--;
+ unclaimed_tokens[index] = unclaimed_tokens[num_unclaimed_tokens];
+
+ /*
+ * Delete the list of unclaimed tokens if it's empty.
+ */
+ if (num_unclaimed_tokens == 0) {
+ free(unclaimed_tokens);
+ unclaimed_tokens = NULL;
+ unclaimed_tokens_size = 0;
+ }
+
+ return (token);
+}
+
+/*
+ * Free all unclaimed TOKENs. This should only be invoked when the YACC
+ * parser encounters errors.
+ */
+static void
+free_tokens(void)
+{
+ if (unclaimed_tokens != NULL) {
+ while (num_unclaimed_tokens > 0)
+ free(unclaimed_tokens[--num_unclaimed_tokens]);
+ free(unclaimed_tokens);
+ unclaimed_tokens = NULL;
+ unclaimed_tokens_size = 0;
+ }
+ assert_no_unclaimed_tokens();
+}
+
+/*
+ * Create a TOKEN from the specified string. The TOKEN is merely a duplicate
+ * of the specified string. TOKENs must be claimed by the YACC reduction rules
+ * that use them; see claim_token() above.
+ */
+char *
+create_token(char *s)
{
char *result;
@@ -379,12 +479,39 @@
yyerror("Out of memory");
exit(Z_ERR);
}
+
+ /*
+ * Add the new TOKEN to the list of unclaimed TOKENs. The list might
+ * have to be resized.
+ *
+ * Reduction rules should claim TOKENs via claim_token() (see above).
+ */
+ if (num_unclaimed_tokens == unclaimed_tokens_size) {
+ char **new_unclaimed_tokens;
+
+ unclaimed_tokens_size += UNCLAIMED_TOKENS_BUFFER_GROWTH;
+ new_unclaimed_tokens = (char **)realloc(unclaimed_tokens,
+ unclaimed_tokens_size * sizeof (char *));
+ if (new_unclaimed_tokens == NULL) {
+ yyerror("Out of memory");
+ free(result);
+ exit(Z_ERR);
+ }
+ unclaimed_tokens = new_unclaimed_tokens;
+ }
+ unclaimed_tokens[num_unclaimed_tokens] = result;
+ num_unclaimed_tokens++;
return (result);
}
void
yyerror(char *s)
{
+ /*
+ * Ensure that we won't leak unclaimed tokens.
+ */
+ free_tokens();
+
/* feof(yyin) is not an error; anything else is, so we set saw_error */
if (yytext[0] == '\0') {
if (!feof(yyin)) {