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