xref: /OK3568_Linux_fs/buildroot/boot/grub2/0132-kern-parser-Fix-a-stack-buffer-overflow.patch (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1From 4ea7bae51f97e49c84dc67ea30b466ca8633b9f6 Mon Sep 17 00:00:00 2001
2From: Chris Coulson <chris.coulson@canonical.com>
3Date: Thu, 7 Jan 2021 19:21:03 +0000
4Subject: [PATCH] kern/parser: Fix a stack buffer overflow
5
6grub_parser_split_cmdline() expands variable names present in the supplied
7command line in to their corresponding variable contents and uses a 1 kiB
8stack buffer for temporary storage without sufficient bounds checking. If
9the function is called with a command line that references a variable with
10a sufficiently large payload, it is possible to overflow the stack
11buffer via tab completion, corrupt the stack frame and potentially
12control execution.
13
14Fixes: CVE-2020-27749
15
16Reported-by: Chris Coulson <chris.coulson@canonical.com>
17Signed-off-by: Chris Coulson <chris.coulson@canonical.com>
18Signed-off-by: Darren Kenny <darren.kenny@oracle.com>
19Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
20Signed-off-by: Stefan Sørensen <stefan.sorensen@spectralink.com>
21---
22 grub-core/kern/parser.c | 110 +++++++++++++++++++++++++++++-------------------
23 1 file changed, 67 insertions(+), 43 deletions(-)
24
25diff --git a/grub-core/kern/parser.c b/grub-core/kern/parser.c
26index e010eaa..6ab7aa4 100644
27--- a/grub-core/kern/parser.c
28+++ b/grub-core/kern/parser.c
29@@ -18,6 +18,7 @@
30  */
31
32 #include <grub/parser.h>
33+#include <grub/buffer.h>
34 #include <grub/env.h>
35 #include <grub/misc.h>
36 #include <grub/mm.h>
37@@ -107,8 +108,8 @@ check_varstate (grub_parser_state_t s)
38 }
39
40
41-static void
42-add_var (char *varname, char **bp, char **vp,
43+static grub_err_t
44+add_var (grub_buffer_t varname, grub_buffer_t buf,
45 	 grub_parser_state_t state, grub_parser_state_t newstate)
46 {
47   const char *val;
48@@ -116,31 +117,41 @@ add_var (char *varname, char **bp, char **vp,
49   /* Check if a variable was being read in and the end of the name
50      was reached.  */
51   if (!(check_varstate (state) && !check_varstate (newstate)))
52-    return;
53+    return GRUB_ERR_NONE;
54+
55+  if (grub_buffer_append_char (varname, '\0') != GRUB_ERR_NONE)
56+    return grub_errno;
57
58-  *((*vp)++) = '\0';
59-  val = grub_env_get (varname);
60-  *vp = varname;
61+  val = grub_env_get ((const char *) grub_buffer_peek_data (varname));
62+  grub_buffer_reset (varname);
63   if (!val)
64-    return;
65+    return GRUB_ERR_NONE;
66
67   /* Insert the contents of the variable in the buffer.  */
68-  for (; *val; val++)
69-    *((*bp)++) = *val;
70+  return grub_buffer_append_data (buf, val, grub_strlen (val));
71 }
72
73-static void
74-terminate_arg (char *buffer, char **bp, int *argc)
75+static grub_err_t
76+terminate_arg (grub_buffer_t buffer, int *argc)
77 {
78-  if (*bp != buffer && *((*bp) - 1) != '\0')
79-    {
80-      *((*bp)++) = '\0';
81-      (*argc)++;
82-    }
83+  grub_size_t unread = grub_buffer_get_unread_bytes (buffer);
84+
85+  if (unread == 0)
86+    return GRUB_ERR_NONE;
87+
88+  if (*(const char *) grub_buffer_peek_data_at (buffer, unread - 1) == '\0')
89+    return GRUB_ERR_NONE;
90+
91+  if (grub_buffer_append_char (buffer, '\0') != GRUB_ERR_NONE)
92+    return grub_errno;
93+
94+  (*argc)++;
95+
96+  return GRUB_ERR_NONE;
97 }
98
99 static grub_err_t
100-process_char (char c, char *buffer, char **bp, char *varname, char **vp,
101+process_char (char c, grub_buffer_t buffer, grub_buffer_t varname,
102 	      grub_parser_state_t state, int *argc,
103 	      grub_parser_state_t *newstate)
104 {
105@@ -153,12 +164,13 @@ process_char (char c, char *buffer, char **bp, char *varname, char **vp,
106    * not describe the variable anymore, write the variable to
107    * the buffer.
108    */
109-  add_var (varname, bp, vp, state, *newstate);
110+  if (add_var (varname, buffer, state, *newstate) != GRUB_ERR_NONE)
111+    return grub_errno;
112
113   if (check_varstate (*newstate))
114     {
115       if (use)
116-	*((*vp)++) = use;
117+        return grub_buffer_append_char (varname, use);
118     }
119   else if (*newstate == GRUB_PARSER_STATE_TEXT &&
120 	   state != GRUB_PARSER_STATE_ESC && grub_isspace (use))
121@@ -167,10 +179,10 @@ process_char (char c, char *buffer, char **bp, char *varname, char **vp,
122        * Don't add more than one argument if multiple
123        * spaces are used.
124        */
125-      terminate_arg (buffer, bp, argc);
126+      return terminate_arg (buffer, argc);
127     }
128   else if (use)
129-    *((*bp)++) = use;
130+    return grub_buffer_append_char (buffer, use);
131
132   return GRUB_ERR_NONE;
133 }
134@@ -181,19 +193,22 @@ grub_parser_split_cmdline (const char *cmdline,
135 			   int *argc, char ***argv)
136 {
137   grub_parser_state_t state = GRUB_PARSER_STATE_TEXT;
138-  /* XXX: Fixed size buffer, perhaps this buffer should be dynamically
139-     allocated.  */
140-  char buffer[1024];
141-  char *bp = buffer;
142+  grub_buffer_t buffer, varname;
143   char *rd = (char *) cmdline;
144   char *rp = rd;
145-  char varname[200];
146-  char *vp = varname;
147-  char *args;
148   int i;
149
150   *argc = 0;
151   *argv = NULL;
152+
153+  buffer = grub_buffer_new (1024);
154+  if (buffer == NULL)
155+    return grub_errno;
156+
157+  varname = grub_buffer_new (200);
158+  if (varname == NULL)
159+    goto fail;
160+
161   do
162     {
163       if (rp == NULL || *rp == '\0')
164@@ -219,7 +234,7 @@ grub_parser_split_cmdline (const char *cmdline,
165 	{
166 	  grub_parser_state_t newstate;
167
168-	  if (process_char (*rp, buffer, &bp, varname, &vp, state, argc,
169+	  if (process_char (*rp, buffer, varname, state, argc,
170 			    &newstate) != GRUB_ERR_NONE)
171 	    goto fail;
172
173@@ -230,10 +245,12 @@ grub_parser_split_cmdline (const char *cmdline,
174
175   /* A special case for when the last character was part of a
176      variable.  */
177-  add_var (varname, &bp, &vp, state, GRUB_PARSER_STATE_TEXT);
178+  if (add_var (varname, buffer, state, GRUB_PARSER_STATE_TEXT) != GRUB_ERR_NONE)
179+    goto fail;
180
181   /* Ensure that the last argument is terminated. */
182-  terminate_arg (buffer, &bp, argc);
183+  if (terminate_arg (buffer, argc) != GRUB_ERR_NONE)
184+    goto fail;
185
186   /* If there are no args, then we're done. */
187   if (!*argc)
188@@ -242,38 +259,45 @@ grub_parser_split_cmdline (const char *cmdline,
189       goto out;
190     }
191
192-  /* Reserve memory for the return values.  */
193-  args = grub_malloc (bp - buffer);
194-  if (!args)
195-    goto fail;
196-  grub_memcpy (args, buffer, bp - buffer);
197-
198   *argv = grub_calloc (*argc + 1, sizeof (char *));
199   if (!*argv)
200     goto fail;
201
202   /* The arguments are separated with 0's, setup argv so it points to
203      the right values.  */
204-  bp = args;
205   for (i = 0; i < *argc; i++)
206     {
207-      (*argv)[i] = bp;
208-      while (*bp)
209-	bp++;
210-      bp++;
211+      char *arg;
212+
213+      if (i > 0)
214+	{
215+	  if (grub_buffer_advance_read_pos (buffer, 1) != GRUB_ERR_NONE)
216+	    goto fail;
217+	}
218+
219+      arg = (char *) grub_buffer_peek_data (buffer);
220+      if (arg == NULL ||
221+	  grub_buffer_advance_read_pos (buffer, grub_strlen (arg)) != GRUB_ERR_NONE)
222+	goto fail;
223+
224+      (*argv)[i] = arg;
225     }
226
227+  /* Keep memory for the return values. */
228+  grub_buffer_take_data (buffer);
229+
230   grub_errno = GRUB_ERR_NONE;
231
232  out:
233   if (rd != cmdline)
234     grub_free (rd);
235+  grub_buffer_free (buffer);
236+  grub_buffer_free (varname);
237
238   return grub_errno;
239
240  fail:
241   grub_free (*argv);
242-  grub_free (args);
243   goto out;
244 }
245
246--
2472.14.2
248
249