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