mirror of
				https://git.proxmox.com/git/grub2
				synced 2025-10-31 19:15:14 +00:00 
			
		
		
		
	 2f533a89a8
			
		
	
	
		2f533a89a8
		
	
	
	
	
		
			
			Commit9acdcbf325(use single quotes in menuentry setparams command) says that expressing a quoted single quote will require 3 characters. It actually requires (and always did require!) 4 characters: str: a'b => a'\''b len: 3 => 6 (2 for the letters + 4 for the quote) This leads to not allocating enough memory and thus out of bounds writes that have been observed to cause heap corruption. Allocate 4 bytes for each single quote. Commit22e7dbb2bb(Fix quoting in legacy parser.) does the same quoting, but it adds 3 as extra overhead on top of the single byte that the quote already needs. So it's correct. Fixes:9acdcbf325(use single quotes in menuentry setparams command) Fixes: CVE-2021-20233 Reported-by: Daniel Axtens <dja@axtens.net> Signed-off-by: Daniel Axtens <dja@axtens.net> Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
		
			
				
	
	
		
			338 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			338 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* menuentry.c - menuentry command */
 | |
| /*
 | |
|  *  GRUB  --  GRand Unified Bootloader
 | |
|  *  Copyright (C) 2010  Free Software Foundation, Inc.
 | |
|  *
 | |
|  *  GRUB is free software: you can redistribute it and/or modify
 | |
|  *  it under the terms of the GNU General Public License as published by
 | |
|  *  the Free Software Foundation, either version 3 of the License, or
 | |
|  *  (at your option) any later version.
 | |
|  *
 | |
|  *  GRUB is distributed in the hope that it will be useful,
 | |
|  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|  *  GNU General Public License for more details.
 | |
|  *
 | |
|  *  You should have received a copy of the GNU General Public License
 | |
|  *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
 | |
|  */
 | |
| 
 | |
| #include <grub/types.h>
 | |
| #include <grub/misc.h>
 | |
| #include <grub/err.h>
 | |
| #include <grub/dl.h>
 | |
| #include <grub/extcmd.h>
 | |
| #include <grub/i18n.h>
 | |
| #include <grub/normal.h>
 | |
| 
 | |
| static const struct grub_arg_option options[] =
 | |
|   {
 | |
|     {"class", 1, GRUB_ARG_OPTION_REPEATABLE,
 | |
|      N_("Menu entry type."), N_("STRING"), ARG_TYPE_STRING},
 | |
|     {"users", 2, 0,
 | |
|      N_("List of users allowed to boot this entry."), N_("USERNAME[,USERNAME]"),
 | |
|      ARG_TYPE_STRING},
 | |
|     {"hotkey", 3, 0,
 | |
|      N_("Keyboard key to quickly boot this entry."), N_("KEYBOARD_KEY"), ARG_TYPE_STRING},
 | |
|     {"source", 4, 0,
 | |
|      N_("Use STRING as menu entry body."), N_("STRING"), ARG_TYPE_STRING},
 | |
|     {"id", 0, 0, N_("Menu entry identifier."), N_("STRING"), ARG_TYPE_STRING},
 | |
|     /* TRANSLATORS: menu entry can either be bootable by anyone or only by
 | |
|        handful of users. By default when security is active only superusers can
 | |
|        boot a given menu entry. With --unrestricted (this option)
 | |
|        anyone can boot it.  */
 | |
|     {"unrestricted", 0, 0, N_("This entry can be booted by any user."),
 | |
|      0, ARG_TYPE_NONE},
 | |
|     {0, 0, 0, 0, 0, 0}
 | |
|   };
 | |
| 
 | |
| static struct
 | |
| {
 | |
|   const char *name;
 | |
|   int key;
 | |
| } hotkey_aliases[] =
 | |
|   {
 | |
|     {"backspace", GRUB_TERM_BACKSPACE},
 | |
|     {"tab", GRUB_TERM_TAB},
 | |
|     {"delete", GRUB_TERM_KEY_DC},
 | |
|     {"insert", GRUB_TERM_KEY_INSERT},
 | |
|     {"f1", GRUB_TERM_KEY_F1},
 | |
|     {"f2", GRUB_TERM_KEY_F2},
 | |
|     {"f3", GRUB_TERM_KEY_F3},
 | |
|     {"f4", GRUB_TERM_KEY_F4},
 | |
|     {"f5", GRUB_TERM_KEY_F5},
 | |
|     {"f6", GRUB_TERM_KEY_F6},
 | |
|     {"f7", GRUB_TERM_KEY_F7},
 | |
|     {"f8", GRUB_TERM_KEY_F8},
 | |
|     {"f9", GRUB_TERM_KEY_F9},
 | |
|     {"f10", GRUB_TERM_KEY_F10},
 | |
|     {"f11", GRUB_TERM_KEY_F11},
 | |
|     {"f12", GRUB_TERM_KEY_F12},
 | |
|   };
 | |
| 
 | |
| /* Add a menu entry to the current menu context (as given by the environment
 | |
|    variable data slot `menu').  As the configuration file is read, the script
 | |
|    parser calls this when a menu entry is to be created.  */
 | |
| grub_err_t
 | |
| grub_normal_add_menu_entry (int argc, const char **args,
 | |
| 			    char **classes, const char *id,
 | |
| 			    const char *users, const char *hotkey,
 | |
| 			    const char *prefix, const char *sourcecode,
 | |
| 			    int submenu)
 | |
| {
 | |
|   int menu_hotkey = 0;
 | |
|   char **menu_args = NULL;
 | |
|   char *menu_users = NULL;
 | |
|   char *menu_title = NULL;
 | |
|   char *menu_sourcecode = NULL;
 | |
|   char *menu_id = NULL;
 | |
|   struct grub_menu_entry_class *menu_classes = NULL;
 | |
| 
 | |
|   grub_menu_t menu;
 | |
|   grub_menu_entry_t *last;
 | |
| 
 | |
|   menu = grub_env_get_menu ();
 | |
|   if (! menu)
 | |
|     return grub_error (GRUB_ERR_MENU, "no menu context");
 | |
| 
 | |
|   last = &menu->entry_list;
 | |
| 
 | |
|   menu_sourcecode = grub_xasprintf ("%s%s", prefix ?: "", sourcecode);
 | |
|   if (! menu_sourcecode)
 | |
|     return grub_errno;
 | |
| 
 | |
|   if (classes && classes[0])
 | |
|     {
 | |
|       int i;
 | |
|       for (i = 0; classes[i]; i++); /* count # of menuentry classes */
 | |
|       menu_classes = grub_zalloc (sizeof (struct grub_menu_entry_class)
 | |
| 				  * (i + 1));
 | |
|       if (! menu_classes)
 | |
| 	goto fail;
 | |
| 
 | |
|       for (i = 0; classes[i]; i++)
 | |
| 	{
 | |
| 	  menu_classes[i].name = grub_strdup (classes[i]);
 | |
| 	  if (! menu_classes[i].name)
 | |
| 	    goto fail;
 | |
| 	  menu_classes[i].next = classes[i + 1] ? &menu_classes[i + 1] : NULL;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   if (users)
 | |
|     {
 | |
|       menu_users = grub_strdup (users);
 | |
|       if (! menu_users)
 | |
| 	goto fail;
 | |
|     }
 | |
| 
 | |
|   if (hotkey)
 | |
|     {
 | |
|       unsigned i;
 | |
|       for (i = 0; i < ARRAY_SIZE (hotkey_aliases); i++)
 | |
| 	if (grub_strcmp (hotkey, hotkey_aliases[i].name) == 0)
 | |
| 	  {
 | |
| 	    menu_hotkey = hotkey_aliases[i].key;
 | |
| 	    break;
 | |
| 	  }
 | |
|       if (i == ARRAY_SIZE (hotkey_aliases))
 | |
| 	menu_hotkey = hotkey[0];
 | |
|     }
 | |
| 
 | |
|   if (! argc)
 | |
|     {
 | |
|       grub_error (GRUB_ERR_MENU, "menuentry is missing title");
 | |
|       goto fail;
 | |
|     }
 | |
| 
 | |
|   menu_title = grub_strdup (args[0]);
 | |
|   if (! menu_title)
 | |
|     goto fail;
 | |
| 
 | |
|   menu_id = grub_strdup (id ? : menu_title);
 | |
|   if (! menu_id)
 | |
|     goto fail;
 | |
| 
 | |
|   /* Save argc, args to pass as parameters to block arg later. */
 | |
|   menu_args = grub_calloc (argc + 1, sizeof (char *));
 | |
|   if (! menu_args)
 | |
|     goto fail;
 | |
| 
 | |
|   {
 | |
|     int i;
 | |
|     for (i = 0; i < argc; i++)
 | |
|       {
 | |
| 	menu_args[i] = grub_strdup (args[i]);
 | |
| 	if (! menu_args[i])
 | |
| 	  goto fail;
 | |
|       }
 | |
|     menu_args[argc] = NULL;
 | |
|   }
 | |
| 
 | |
|   /* Add the menu entry at the end of the list.  */
 | |
|   while (*last)
 | |
|     last = &(*last)->next;
 | |
| 
 | |
|   *last = grub_zalloc (sizeof (**last));
 | |
|   if (! *last)
 | |
|     goto fail;
 | |
| 
 | |
|   (*last)->title = menu_title;
 | |
|   (*last)->id = menu_id;
 | |
|   (*last)->hotkey = menu_hotkey;
 | |
|   (*last)->classes = menu_classes;
 | |
|   if (menu_users)
 | |
|     (*last)->restricted = 1;
 | |
|   (*last)->users = menu_users;
 | |
|   (*last)->argc = argc;
 | |
|   (*last)->args = menu_args;
 | |
|   (*last)->sourcecode = menu_sourcecode;
 | |
|   (*last)->submenu = submenu;
 | |
| 
 | |
|   menu->size++;
 | |
|   return GRUB_ERR_NONE;
 | |
| 
 | |
|  fail:
 | |
| 
 | |
|   grub_free (menu_sourcecode);
 | |
|   {
 | |
|     int i;
 | |
|     for (i = 0; menu_classes && menu_classes[i].name; i++)
 | |
|       grub_free (menu_classes[i].name);
 | |
|     grub_free (menu_classes);
 | |
|   }
 | |
| 
 | |
|   {
 | |
|     int i;
 | |
|     for (i = 0; menu_args && menu_args[i]; i++)
 | |
|       grub_free (menu_args[i]);
 | |
|     grub_free (menu_args);
 | |
|   }
 | |
| 
 | |
|   grub_free (menu_users);
 | |
|   grub_free (menu_title);
 | |
|   grub_free (menu_id);
 | |
|   return grub_errno;
 | |
| }
 | |
| 
 | |
| static char *
 | |
| setparams_prefix (int argc, char **args)
 | |
| {
 | |
|   int i;
 | |
|   int j;
 | |
|   char *p;
 | |
|   char *result;
 | |
|   grub_size_t len = 10;
 | |
| 
 | |
|   /* Count resulting string length */
 | |
|   for (i = 0; i < argc; i++)
 | |
|     {
 | |
|       len += 3; /* 3 = 1 space + 2 quotes */
 | |
|       p = args[i];
 | |
|       while (*p)
 | |
| 	len += (*p++ == '\'' ? 4 : 1);
 | |
|     }
 | |
| 
 | |
|   result = grub_malloc (len + 2);
 | |
|   if (! result)
 | |
|     return 0;
 | |
| 
 | |
|   grub_strcpy (result, "setparams");
 | |
|   p = result + 9;
 | |
| 
 | |
|   for (j = 0; j < argc; j++)
 | |
|     {
 | |
|       *p++ = ' ';
 | |
|       *p++ = '\'';
 | |
|       p = grub_strchrsub (p, args[j], '\'', "'\\''");
 | |
|       *p++ = '\'';
 | |
|     }
 | |
|   *p++ = '\n';
 | |
|   *p = '\0';
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| static grub_err_t
 | |
| grub_cmd_menuentry (grub_extcmd_context_t ctxt, int argc, char **args)
 | |
| {
 | |
|   char ch;
 | |
|   char *src;
 | |
|   char *prefix;
 | |
|   unsigned len;
 | |
|   grub_err_t r;
 | |
|   const char *users;
 | |
| 
 | |
|   if (! argc)
 | |
|     return grub_error (GRUB_ERR_BAD_ARGUMENT, "missing arguments");
 | |
| 
 | |
|   if (ctxt->state[3].set && ctxt->script)
 | |
|     return grub_error (GRUB_ERR_BAD_ARGUMENT, "multiple menuentry definitions");
 | |
| 
 | |
|   if (! ctxt->state[3].set && ! ctxt->script)
 | |
|     return grub_error (GRUB_ERR_BAD_ARGUMENT, "no menuentry definition");
 | |
| 
 | |
|   if (ctxt->state[1].set)
 | |
|     users = ctxt->state[1].arg;
 | |
|   else if (ctxt->state[5].set)
 | |
|     users = NULL;
 | |
|   else
 | |
|     users = "";
 | |
| 
 | |
|   if (! ctxt->script)
 | |
|     return grub_normal_add_menu_entry (argc, (const char **) args,
 | |
| 				       (ctxt->state[0].set ? ctxt->state[0].args
 | |
| 					: NULL),
 | |
| 				       ctxt->state[4].arg,
 | |
| 				       users,
 | |
| 				       ctxt->state[2].arg, 0,
 | |
| 				       ctxt->state[3].arg,
 | |
| 				       ctxt->extcmd->cmd->name[0] == 's');
 | |
| 
 | |
|   src = args[argc - 1];
 | |
|   args[argc - 1] = NULL;
 | |
| 
 | |
|   len = grub_strlen(src);
 | |
|   ch = src[len - 1];
 | |
|   src[len - 1] = '\0';
 | |
| 
 | |
|   prefix = setparams_prefix (argc - 1, args);
 | |
|   if (! prefix)
 | |
|     return grub_errno;
 | |
| 
 | |
|   r = grub_normal_add_menu_entry (argc - 1, (const char **) args,
 | |
| 				  ctxt->state[0].args, ctxt->state[4].arg,
 | |
| 				  users,
 | |
| 				  ctxt->state[2].arg, prefix, src + 1,
 | |
| 				  ctxt->extcmd->cmd->name[0] == 's');
 | |
| 
 | |
|   src[len - 1] = ch;
 | |
|   args[argc - 1] = src;
 | |
|   grub_free (prefix);
 | |
|   return r;
 | |
| }
 | |
| 
 | |
| static grub_extcmd_t cmd, cmd_sub;
 | |
| 
 | |
| void
 | |
| grub_menu_init (void)
 | |
| {
 | |
|   cmd = grub_register_extcmd ("menuentry", grub_cmd_menuentry,
 | |
| 			      GRUB_COMMAND_FLAG_BLOCKS
 | |
| 			      | GRUB_COMMAND_ACCEPT_DASH
 | |
| 			      | GRUB_COMMAND_FLAG_EXTRACTOR,
 | |
| 			      N_("BLOCK"), N_("Define a menu entry."), options);
 | |
|   cmd_sub = grub_register_extcmd ("submenu", grub_cmd_menuentry,
 | |
| 				  GRUB_COMMAND_FLAG_BLOCKS
 | |
| 				  | GRUB_COMMAND_ACCEPT_DASH
 | |
| 				  | GRUB_COMMAND_FLAG_EXTRACTOR,
 | |
| 				  N_("BLOCK"), N_("Define a submenu."),
 | |
| 				  options);
 | |
| }
 | |
| 
 | |
| void
 | |
| grub_menu_fini (void)
 | |
| {
 | |
|   grub_unregister_extcmd (cmd);
 | |
|   grub_unregister_extcmd (cmd_sub);
 | |
| }
 |