0f3600bf1b
If an existing variable is set with a value whose length is smaller than the current value, a memory corruption can happen due copying padding '#' characters outside of the environment block buffer. This is caused by a wrong calculation of the previous free space position after moving backward the characters that followed the old variable value. That position is calculated to fill the remaining of the buffer with the padding '#' characters. But since isn't calculated correctly, it can lead to copies outside of the buffer. The issue can be reproduced by creating a variable with a large value and then try to set a new value that is much smaller: $ grub2-editenv --version grub2-editenv (GRUB) 2.04 $ grub2-editenv env create $ grub2-editenv env set a="$(for i in {1..500}; do var="b$var"; done; echo $var)" $ wc -c env 1024 grubenv $ grub2-editenv env set a="$(for i in {1..50}; do var="b$var"; done; echo $var)" malloc(): corrupted top size Aborted (core dumped) $ wc -c env 0 grubenv Reported-by: Renaud Métrich <rmetrich@redhat.com> Signed-off-by: Javier Martinez Canillas <javierm@redhat.com> Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
297 lines
6.3 KiB
C
297 lines
6.3 KiB
C
/* envblk.c - Common functions for environment block. */
|
|
/*
|
|
* GRUB -- GRand Unified Bootloader
|
|
* Copyright (C) 2008,2009 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 <config.h>
|
|
#include <grub/types.h>
|
|
#include <grub/misc.h>
|
|
#include <grub/mm.h>
|
|
#include <grub/lib/envblk.h>
|
|
|
|
grub_envblk_t
|
|
grub_envblk_open (char *buf, grub_size_t size)
|
|
{
|
|
grub_envblk_t envblk;
|
|
|
|
if (size < sizeof (GRUB_ENVBLK_SIGNATURE)
|
|
|| grub_memcmp (buf, GRUB_ENVBLK_SIGNATURE,
|
|
sizeof (GRUB_ENVBLK_SIGNATURE) - 1))
|
|
{
|
|
grub_error (GRUB_ERR_BAD_FILE_TYPE, "invalid environment block");
|
|
return 0;
|
|
}
|
|
|
|
envblk = grub_malloc (sizeof (*envblk));
|
|
if (envblk)
|
|
{
|
|
envblk->buf = buf;
|
|
envblk->size = size;
|
|
}
|
|
|
|
return envblk;
|
|
}
|
|
|
|
void
|
|
grub_envblk_close (grub_envblk_t envblk)
|
|
{
|
|
grub_free (envblk->buf);
|
|
grub_free (envblk);
|
|
}
|
|
|
|
static int
|
|
escaped_value_len (const char *value)
|
|
{
|
|
int n = 0;
|
|
char *p;
|
|
|
|
for (p = (char *) value; *p; p++)
|
|
{
|
|
if (*p == '\\' || *p == '\n')
|
|
n += 2;
|
|
else
|
|
n++;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
static char *
|
|
find_next_line (char *p, const char *pend)
|
|
{
|
|
while (p < pend)
|
|
{
|
|
if (*p == '\\')
|
|
p += 2;
|
|
else if (*p == '\n')
|
|
break;
|
|
else
|
|
p++;
|
|
}
|
|
|
|
return p + 1;
|
|
}
|
|
|
|
int
|
|
grub_envblk_set (grub_envblk_t envblk, const char *name, const char *value)
|
|
{
|
|
char *p, *pend;
|
|
char *space;
|
|
int found = 0;
|
|
int nl;
|
|
int vl;
|
|
int i;
|
|
|
|
nl = grub_strlen (name);
|
|
vl = escaped_value_len (value);
|
|
p = envblk->buf + sizeof (GRUB_ENVBLK_SIGNATURE) - 1;
|
|
pend = envblk->buf + envblk->size;
|
|
|
|
/* First, look at free space. */
|
|
for (space = pend - 1; *space == '#'; space--)
|
|
;
|
|
|
|
if (*space != '\n')
|
|
/* Broken. */
|
|
return 0;
|
|
|
|
space++;
|
|
|
|
while (p + nl + 1 < space)
|
|
{
|
|
if (grub_memcmp (p, name, nl) == 0 && p[nl] == '=')
|
|
{
|
|
int len;
|
|
|
|
/* Found the same name. */
|
|
p += nl + 1;
|
|
|
|
/* Check the length of the current value. */
|
|
len = 0;
|
|
while (p + len < pend && p[len] != '\n')
|
|
{
|
|
if (p[len] == '\\')
|
|
len += 2;
|
|
else
|
|
len++;
|
|
}
|
|
|
|
if (p + len >= pend)
|
|
/* Broken. */
|
|
return 0;
|
|
|
|
if (pend - space < vl - len)
|
|
/* No space. */
|
|
return 0;
|
|
|
|
if (vl < len)
|
|
{
|
|
/* Move the following characters backward, and fill the new
|
|
space with harmless characters. */
|
|
grub_memmove (p + vl, p + len, pend - (p + len));
|
|
grub_memset (space - (len - vl), '#', len - vl);
|
|
}
|
|
else
|
|
/* Move the following characters forward. */
|
|
grub_memmove (p + vl, p + len, pend - (p + vl));
|
|
|
|
found = 1;
|
|
break;
|
|
}
|
|
|
|
p = find_next_line (p, pend);
|
|
}
|
|
|
|
if (! found)
|
|
{
|
|
/* Append a new variable. */
|
|
|
|
if (pend - space < nl + 1 + vl + 1)
|
|
/* No space. */
|
|
return 0;
|
|
|
|
grub_memcpy (space, name, nl);
|
|
p = space + nl;
|
|
*p++ = '=';
|
|
}
|
|
|
|
/* Write the value. */
|
|
for (i = 0; value[i]; i++)
|
|
{
|
|
if (value[i] == '\\' || value[i] == '\n')
|
|
*p++ = '\\';
|
|
|
|
*p++ = value[i];
|
|
}
|
|
|
|
*p = '\n';
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
grub_envblk_delete (grub_envblk_t envblk, const char *name)
|
|
{
|
|
char *p, *pend;
|
|
int nl;
|
|
|
|
nl = grub_strlen (name);
|
|
p = envblk->buf + sizeof (GRUB_ENVBLK_SIGNATURE) - 1;
|
|
pend = envblk->buf + envblk->size;
|
|
|
|
while (p + nl + 1 < pend)
|
|
{
|
|
if (grub_memcmp (p, name, nl) == 0 && p[nl] == '=')
|
|
{
|
|
/* Found. */
|
|
int len = nl + 1;
|
|
|
|
while (p + len < pend)
|
|
{
|
|
if (p[len] == '\n')
|
|
break;
|
|
else if (p[len] == '\\')
|
|
len += 2;
|
|
else
|
|
len++;
|
|
}
|
|
|
|
if (p + len >= pend)
|
|
/* Broken. */
|
|
return;
|
|
|
|
len++;
|
|
grub_memmove (p, p + len, pend - (p + len));
|
|
grub_memset (pend - len, '#', len);
|
|
break;
|
|
}
|
|
|
|
p = find_next_line (p, pend);
|
|
}
|
|
}
|
|
|
|
void
|
|
grub_envblk_iterate (grub_envblk_t envblk,
|
|
void *hook_data,
|
|
int hook (const char *name, const char *value, void *hook_data))
|
|
{
|
|
char *p, *pend;
|
|
|
|
p = envblk->buf + sizeof (GRUB_ENVBLK_SIGNATURE) - 1;
|
|
pend = envblk->buf + envblk->size;
|
|
|
|
while (p < pend)
|
|
{
|
|
if (*p != '#')
|
|
{
|
|
char *name;
|
|
char *value;
|
|
char *name_start, *name_end, *value_start;
|
|
char *q;
|
|
int ret;
|
|
|
|
name_start = p;
|
|
while (p < pend && *p != '=')
|
|
p++;
|
|
if (p == pend)
|
|
/* Broken. */
|
|
return;
|
|
name_end = p;
|
|
|
|
p++;
|
|
value_start = p;
|
|
while (p < pend)
|
|
{
|
|
if (*p == '\n')
|
|
break;
|
|
else if (*p == '\\')
|
|
p += 2;
|
|
else
|
|
p++;
|
|
}
|
|
|
|
if (p >= pend)
|
|
/* Broken. */
|
|
return;
|
|
|
|
name = grub_malloc (p - name_start + 1);
|
|
if (! name)
|
|
/* out of memory. */
|
|
return;
|
|
|
|
value = name + (value_start - name_start);
|
|
|
|
grub_memcpy (name, name_start, name_end - name_start);
|
|
name[name_end - name_start] = '\0';
|
|
|
|
for (p = value_start, q = value; *p != '\n'; ++p)
|
|
{
|
|
if (*p == '\\')
|
|
*q++ = *++p;
|
|
else
|
|
*q++ = *p;
|
|
}
|
|
*q = '\0';
|
|
|
|
ret = hook (name, value, hook_data);
|
|
grub_free (name);
|
|
if (ret)
|
|
return;
|
|
}
|
|
|
|
p = find_next_line (p, pend);
|
|
}
|
|
}
|