summaryrefslogtreecommitdiffstats
path: root/meta/recipes-bsp/grub/files/CVE-2020-27749.patch
diff options
context:
space:
mode:
Diffstat (limited to 'meta/recipes-bsp/grub/files/CVE-2020-27749.patch')
-rw-r--r--meta/recipes-bsp/grub/files/CVE-2020-27749.patch609
1 files changed, 609 insertions, 0 deletions
diff --git a/meta/recipes-bsp/grub/files/CVE-2020-27749.patch b/meta/recipes-bsp/grub/files/CVE-2020-27749.patch
new file mode 100644
index 0000000000..a2566b2ded
--- /dev/null
+++ b/meta/recipes-bsp/grub/files/CVE-2020-27749.patch
@@ -0,0 +1,609 @@
+From 4ea7bae51f97e49c84dc67ea30b466ca8633b9f6 Mon Sep 17 00:00:00 2001
+From: Chris Coulson <chris.coulson@canonical.com>
+Date: Thu, 7 Jan 2021 19:21:03 +0000
+Subject: kern/parser: Fix a stack buffer overflow
+
+grub_parser_split_cmdline() expands variable names present in the supplied
+command line in to their corresponding variable contents and uses a 1 kiB
+stack buffer for temporary storage without sufficient bounds checking. If
+the function is called with a command line that references a variable with
+a sufficiently large payload, it is possible to overflow the stack
+buffer via tab completion, corrupt the stack frame and potentially
+control execution.
+
+Fixes: CVE-2020-27749
+
+Reported-by: Chris Coulson <chris.coulson@canonical.com>
+Signed-off-by: Chris Coulson <chris.coulson@canonical.com>
+Signed-off-by: Darren Kenny <darren.kenny@oracle.com>
+Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
+
+Upstream-Status: Backport [https://git.savannah.gnu.org/cgit/grub.git/commit/?h=grub-2.06&id=c6c426e5ab6ea715153b72584de6bd8c82f698ec && https://git.savannah.gnu.org/cgit/grub.git/commit/?h=grub-2.06&id=b1c9e9e889e4273fb15712051c887e6078511448 && https://git.savannah.gnu.org/cgit/grub.git/commit/?h=grub-2.06&id=3d157bbd06506b170fde5ec23980c4bf9f7660e2 && https://git.savannah.gnu.org/cgit/grub.git/commit/?h=grub-2.06&id=8bc817014ce3d7a498db44eae33c8b90e2430926 && https://git.savannah.gnu.org/cgit/grub.git/commit/?h=grub-2.06&id=030fb6c4fa354cdbd6a8d6903dfed5d36eaf3cb2 && https://git.savannah.gnu.org/cgit/grub.git/commit/?h=grub-2.06&id=4ea7bae51f97e49c84dc67ea30b466ca8633b9f6]
+CVE: CVE-2020-27749
+
+Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com>
+---
+ grub-core/Makefile.core.def | 1 +
+ grub-core/kern/buffer.c | 117 +++++++++++++++++++++
+ grub-core/kern/parser.c | 204 +++++++++++++++++++++++-------------
+ include/grub/buffer.h | 144 +++++++++++++++++++++++++
+ 4 files changed, 395 insertions(+), 71 deletions(-)
+ create mode 100644 grub-core/kern/buffer.c
+ create mode 100644 include/grub/buffer.h
+
+diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
+index 651ea2a..823cd57 100644
+--- a/grub-core/Makefile.core.def
++++ b/grub-core/Makefile.core.def
+@@ -123,6 +123,7 @@ kernel = {
+ riscv32_efi_startup = kern/riscv/efi/startup.S;
+ riscv64_efi_startup = kern/riscv/efi/startup.S;
+
++ common = kern/buffer.c;
+ common = kern/command.c;
+ common = kern/corecmd.c;
+ common = kern/device.c;
+diff --git a/grub-core/kern/buffer.c b/grub-core/kern/buffer.c
+new file mode 100644
+index 0000000..9f5f8b8
+--- /dev/null
++++ b/grub-core/kern/buffer.c
+@@ -0,0 +1,117 @@
++/*
++ * GRUB -- GRand Unified Bootloader
++ * Copyright (C) 2021 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/buffer.h>
++#include <grub/err.h>
++#include <grub/misc.h>
++#include <grub/mm.h>
++#include <grub/safemath.h>
++#include <grub/types.h>
++
++grub_buffer_t
++grub_buffer_new (grub_size_t sz)
++{
++ struct grub_buffer *ret;
++
++ ret = (struct grub_buffer *) grub_malloc (sizeof (*ret));
++ if (ret == NULL)
++ return NULL;
++
++ ret->data = (grub_uint8_t *) grub_malloc (sz);
++ if (ret->data == NULL)
++ {
++ grub_free (ret);
++ return NULL;
++ }
++
++ ret->sz = sz;
++ ret->pos = 0;
++ ret->used = 0;
++
++ return ret;
++}
++
++void
++grub_buffer_free (grub_buffer_t buf)
++{
++ grub_free (buf->data);
++ grub_free (buf);
++}
++
++grub_err_t
++grub_buffer_ensure_space (grub_buffer_t buf, grub_size_t req)
++{
++ grub_uint8_t *d;
++ grub_size_t newsz = 1;
++
++ /* Is the current buffer size adequate? */
++ if (buf->sz >= req)
++ return GRUB_ERR_NONE;
++
++ /* Find the smallest power-of-2 size that satisfies the request. */
++ while (newsz < req)
++ {
++ if (newsz == 0)
++ return grub_error (GRUB_ERR_OUT_OF_RANGE,
++ N_("requested buffer size is too large"));
++ newsz <<= 1;
++ }
++
++ d = (grub_uint8_t *) grub_realloc (buf->data, newsz);
++ if (d == NULL)
++ return grub_errno;
++
++ buf->data = d;
++ buf->sz = newsz;
++
++ return GRUB_ERR_NONE;
++}
++
++void *
++grub_buffer_take_data (grub_buffer_t buf)
++{
++ void *data = buf->data;
++
++ buf->data = NULL;
++ buf->sz = buf->pos = buf->used = 0;
++
++ return data;
++}
++
++void
++grub_buffer_reset (grub_buffer_t buf)
++{
++ buf->pos = buf->used = 0;
++}
++
++grub_err_t
++grub_buffer_advance_read_pos (grub_buffer_t buf, grub_size_t n)
++{
++ grub_size_t newpos;
++
++ if (grub_add (buf->pos, n, &newpos))
++ return grub_error (GRUB_ERR_OUT_OF_RANGE, N_("overflow is detected"));
++
++ if (newpos > buf->used)
++ return grub_error (GRUB_ERR_OUT_OF_RANGE,
++ N_("new read is position beyond the end of the written data"));
++
++ buf->pos = newpos;
++
++ return GRUB_ERR_NONE;
++}
+diff --git a/grub-core/kern/parser.c b/grub-core/kern/parser.c
+index d1cf061..6ab7aa4 100644
+--- a/grub-core/kern/parser.c
++++ b/grub-core/kern/parser.c
+@@ -1,7 +1,7 @@
+ /* parser.c - the part of the parser that can return partial tokens */
+ /*
+ * GRUB -- GRand Unified Bootloader
+- * Copyright (C) 2005,2007,2009 Free Software Foundation, Inc.
++ * Copyright (C) 2005,2007,2009,2021 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
+@@ -18,6 +18,7 @@
+ */
+
+ #include <grub/parser.h>
++#include <grub/buffer.h>
+ #include <grub/env.h>
+ #include <grub/misc.h>
+ #include <grub/mm.h>
+@@ -107,8 +108,8 @@ check_varstate (grub_parser_state_t s)
+ }
+
+
+-static void
+-add_var (char *varname, char **bp, char **vp,
++static grub_err_t
++add_var (grub_buffer_t varname, grub_buffer_t buf,
+ grub_parser_state_t state, grub_parser_state_t newstate)
+ {
+ const char *val;
+@@ -116,17 +117,74 @@ add_var (char *varname, char **bp, char **vp,
+ /* Check if a variable was being read in and the end of the name
+ was reached. */
+ if (!(check_varstate (state) && !check_varstate (newstate)))
+- return;
++ return GRUB_ERR_NONE;
++
++ if (grub_buffer_append_char (varname, '\0') != GRUB_ERR_NONE)
++ return grub_errno;
+
+- *((*vp)++) = '\0';
+- val = grub_env_get (varname);
+- *vp = varname;
++ val = grub_env_get ((const char *) grub_buffer_peek_data (varname));
++ grub_buffer_reset (varname);
+ if (!val)
+- return;
++ return GRUB_ERR_NONE;
+
+ /* Insert the contents of the variable in the buffer. */
+- for (; *val; val++)
+- *((*bp)++) = *val;
++ return grub_buffer_append_data (buf, val, grub_strlen (val));
++}
++
++static grub_err_t
++terminate_arg (grub_buffer_t buffer, int *argc)
++{
++ grub_size_t unread = grub_buffer_get_unread_bytes (buffer);
++
++ if (unread == 0)
++ return GRUB_ERR_NONE;
++
++ if (*(const char *) grub_buffer_peek_data_at (buffer, unread - 1) == '\0')
++ return GRUB_ERR_NONE;
++
++ if (grub_buffer_append_char (buffer, '\0') != GRUB_ERR_NONE)
++ return grub_errno;
++
++ (*argc)++;
++
++ return GRUB_ERR_NONE;
++}
++
++static grub_err_t
++process_char (char c, grub_buffer_t buffer, grub_buffer_t varname,
++ grub_parser_state_t state, int *argc,
++ grub_parser_state_t *newstate)
++{
++ char use;
++
++ *newstate = grub_parser_cmdline_state (state, c, &use);
++
++ /*
++ * If a variable was being processed and this character does
++ * not describe the variable anymore, write the variable to
++ * the buffer.
++ */
++ if (add_var (varname, buffer, state, *newstate) != GRUB_ERR_NONE)
++ return grub_errno;
++
++ if (check_varstate (*newstate))
++ {
++ if (use)
++ return grub_buffer_append_char (varname, use);
++ }
++ else if (*newstate == GRUB_PARSER_STATE_TEXT &&
++ state != GRUB_PARSER_STATE_ESC && grub_isspace (use))
++ {
++ /*
++ * Don't add more than one argument if multiple
++ * spaces are used.
++ */
++ return terminate_arg (buffer, argc);
++ }
++ else if (use)
++ return grub_buffer_append_char (buffer, use);
++
++ return GRUB_ERR_NONE;
+ }
+
+ grub_err_t
+@@ -135,24 +193,36 @@ grub_parser_split_cmdline (const char *cmdline,
+ int *argc, char ***argv)
+ {
+ grub_parser_state_t state = GRUB_PARSER_STATE_TEXT;
+- /* XXX: Fixed size buffer, perhaps this buffer should be dynamically
+- allocated. */
+- char buffer[1024];
+- char *bp = buffer;
++ grub_buffer_t buffer, varname;
+ char *rd = (char *) cmdline;
+- char varname[200];
+- char *vp = varname;
+- char *args;
++ char *rp = rd;
+ int i;
+
+ *argc = 0;
+ *argv = NULL;
++
++ buffer = grub_buffer_new (1024);
++ if (buffer == NULL)
++ return grub_errno;
++
++ varname = grub_buffer_new (200);
++ if (varname == NULL)
++ goto fail;
++
+ do
+ {
+- if (!rd || !*rd)
++ if (rp == NULL || *rp == '\0')
+ {
++ if (rd != cmdline)
++ {
++ grub_free (rd);
++ rd = rp = NULL;
++ }
+ if (getline)
+- getline (&rd, 1, getline_data);
++ {
++ getline (&rd, 1, getline_data);
++ rp = rd;
++ }
+ else
+ break;
+ }
+@@ -160,39 +230,14 @@ grub_parser_split_cmdline (const char *cmdline,
+ if (!rd)
+ break;
+
+- for (; *rd; rd++)
++ for (; *rp != '\0'; rp++)
+ {
+ grub_parser_state_t newstate;
+- char use;
+
+- newstate = grub_parser_cmdline_state (state, *rd, &use);
++ if (process_char (*rp, buffer, varname, state, argc,
++ &newstate) != GRUB_ERR_NONE)
++ goto fail;
+
+- /* If a variable was being processed and this character does
+- not describe the variable anymore, write the variable to
+- the buffer. */
+- add_var (varname, &bp, &vp, state, newstate);
+-
+- if (check_varstate (newstate))
+- {
+- if (use)
+- *(vp++) = use;
+- }
+- else
+- {
+- if (newstate == GRUB_PARSER_STATE_TEXT
+- && state != GRUB_PARSER_STATE_ESC && grub_isspace (use))
+- {
+- /* Don't add more than one argument if multiple
+- spaces are used. */
+- if (bp != buffer && *(bp - 1))
+- {
+- *(bp++) = '\0';
+- (*argc)++;
+- }
+- }
+- else if (use)
+- *(bp++) = use;
+- }
+ state = newstate;
+ }
+ }
+@@ -200,43 +245,60 @@ grub_parser_split_cmdline (const char *cmdline,
+
+ /* A special case for when the last character was part of a
+ variable. */
+- add_var (varname, &bp, &vp, state, GRUB_PARSER_STATE_TEXT);
++ if (add_var (varname, buffer, state, GRUB_PARSER_STATE_TEXT) != GRUB_ERR_NONE)
++ goto fail;
+
+- if (bp != buffer && *(bp - 1))
+- {
+- *(bp++) = '\0';
+- (*argc)++;
+- }
++ /* Ensure that the last argument is terminated. */
++ if (terminate_arg (buffer, argc) != GRUB_ERR_NONE)
++ goto fail;
+
+ /* If there are no args, then we're done. */
+ if (!*argc)
+- return 0;
+-
+- /* Reserve memory for the return values. */
+- args = grub_malloc (bp - buffer);
+- if (!args)
+- return grub_errno;
+- grub_memcpy (args, buffer, bp - buffer);
++ {
++ grub_errno = GRUB_ERR_NONE;
++ goto out;
++ }
+
+ *argv = grub_calloc (*argc + 1, sizeof (char *));
+ if (!*argv)
+- {
+- grub_free (args);
+- return grub_errno;
+- }
++ goto fail;
+
+ /* The arguments are separated with 0's, setup argv so it points to
+ the right values. */
+- bp = args;
+ for (i = 0; i < *argc; i++)
+ {
+- (*argv)[i] = bp;
+- while (*bp)
+- bp++;
+- bp++;
++ char *arg;
++
++ if (i > 0)
++ {
++ if (grub_buffer_advance_read_pos (buffer, 1) != GRUB_ERR_NONE)
++ goto fail;
++ }
++
++ arg = (char *) grub_buffer_peek_data (buffer);
++ if (arg == NULL ||
++ grub_buffer_advance_read_pos (buffer, grub_strlen (arg)) != GRUB_ERR_NONE)
++ goto fail;
++
++ (*argv)[i] = arg;
+ }
+
+- return 0;
++ /* Keep memory for the return values. */
++ grub_buffer_take_data (buffer);
++
++ grub_errno = GRUB_ERR_NONE;
++
++ out:
++ if (rd != cmdline)
++ grub_free (rd);
++ grub_buffer_free (buffer);
++ grub_buffer_free (varname);
++
++ return grub_errno;
++
++ fail:
++ grub_free (*argv);
++ goto out;
+ }
+
+ /* Helper for grub_parser_execute. */
+diff --git a/include/grub/buffer.h b/include/grub/buffer.h
+new file mode 100644
+index 0000000..f4b10cf
+--- /dev/null
++++ b/include/grub/buffer.h
+@@ -0,0 +1,144 @@
++/*
++ * GRUB -- GRand Unified Bootloader
++ * Copyright (C) 2021 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/>.
++ */
++
++#ifndef GRUB_BUFFER_H
++#define GRUB_BUFFER_H 1
++
++#include <grub/err.h>
++#include <grub/misc.h>
++#include <grub/mm.h>
++#include <grub/safemath.h>
++#include <grub/types.h>
++
++struct grub_buffer
++{
++ grub_uint8_t *data;
++ grub_size_t sz;
++ grub_size_t pos;
++ grub_size_t used;
++};
++
++/*
++ * grub_buffer_t represents a simple variable sized byte buffer with
++ * read and write cursors. It currently only implements
++ * functionality required by the only user in GRUB (append byte[s],
++ * peeking data at a specified position and updating the read cursor.
++ * Some things that this doesn't do yet are:
++ * - Reading a portion of the buffer by copying data from the current
++ * read position in to a caller supplied destination buffer and then
++ * automatically updating the read cursor.
++ * - Dropping the read part at the start of the buffer when an append
++ * requires more space.
++ */
++typedef struct grub_buffer *grub_buffer_t;
++
++/* Allocate a new buffer with the specified initial size. */
++extern grub_buffer_t grub_buffer_new (grub_size_t sz);
++
++/* Free the buffer and its resources. */
++extern void grub_buffer_free (grub_buffer_t buf);
++
++/* Return the number of unread bytes in this buffer. */
++static inline grub_size_t
++grub_buffer_get_unread_bytes (grub_buffer_t buf)
++{
++ return buf->used - buf->pos;
++}
++
++/*
++ * Ensure that the buffer size is at least the requested
++ * number of bytes.
++ */
++extern grub_err_t grub_buffer_ensure_space (grub_buffer_t buf, grub_size_t req);
++
++/*
++ * Append the specified number of bytes from the supplied
++ * data to the buffer.
++ */
++static inline grub_err_t
++grub_buffer_append_data (grub_buffer_t buf, const void *data, grub_size_t len)
++{
++ grub_size_t req;
++
++ if (grub_add (buf->used, len, &req))
++ return grub_error (GRUB_ERR_OUT_OF_RANGE, N_("overflow is detected"));
++
++ if (grub_buffer_ensure_space (buf, req) != GRUB_ERR_NONE)
++ return grub_errno;
++
++ grub_memcpy (&buf->data[buf->used], data, len);
++ buf->used = req;
++
++ return GRUB_ERR_NONE;
++}
++
++/* Append the supplied character to the buffer. */
++static inline grub_err_t
++grub_buffer_append_char (grub_buffer_t buf, char c)
++{
++ return grub_buffer_append_data (buf, &c, 1);
++}
++
++/*
++ * Forget and return the underlying data buffer. The caller
++ * becomes the owner of this buffer, and must free it when it
++ * is no longer required.
++ */
++extern void *grub_buffer_take_data (grub_buffer_t buf);
++
++/* Reset this buffer. Note that this does not deallocate any resources. */
++void grub_buffer_reset (grub_buffer_t buf);
++
++/*
++ * Return a pointer to the underlying data buffer at the specified
++ * offset from the current read position. Note that this pointer may
++ * become invalid if the buffer is mutated further.
++ */
++static inline void *
++grub_buffer_peek_data_at (grub_buffer_t buf, grub_size_t off)
++{
++ if (grub_add (buf->pos, off, &off))
++ {
++ grub_error (GRUB_ERR_OUT_OF_RANGE, N_("overflow is detected."));
++ return NULL;
++ }
++
++ if (off >= buf->used)
++ {
++ grub_error (GRUB_ERR_OUT_OF_RANGE, N_("peek out of range"));
++ return NULL;
++ }
++
++ return &buf->data[off];
++}
++
++/*
++ * Return a pointer to the underlying data buffer at the current
++ * read position. Note that this pointer may become invalid if the
++ * buffer is mutated further.
++ */
++static inline void *
++grub_buffer_peek_data (grub_buffer_t buf)
++{
++ return grub_buffer_peek_data_at (buf, 0);
++}
++
++/* Advance the read position by the specified number of bytes. */
++extern grub_err_t grub_buffer_advance_read_pos (grub_buffer_t buf, grub_size_t n);
++
++#endif /* GRUB_BUFFER_H */
+--
+2.25.1
+