changeset 13171:84ccd9321eb7

6897938 zonecfg(1M) hemorrhages memory after Yacc processes commands with syntax errors
author jv227347 <Jordan.Vaughan@Sun.com>
date Tue, 17 Aug 2010 13:22:25 -0700
parents 420a09381f6f
children bb6d135b32d1
files usr/src/cmd/zonecfg/zonecfg.h usr/src/cmd/zonecfg/zonecfg_grammar.y usr/src/cmd/zonecfg/zonecfg_lex.l
diffstat 3 files changed, 180 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- 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)) {