Files
libguestfs/daemon/md.c
Richard W.M. Jones 07c496c53c Use less stack.
GCC has two warnings related to large stack frames.  We were already
using the -Wframe-larger-than warning, but this reduces the threshold
from 10000 to 5000 bytes.

However that warning only covers the static part of frames (not
alloca).  So this change also enables -Wstack-usage=10000 which covers
both the static and dynamic usage (alloca and variable length arrays).

Multiple changes are made throughout the code to reduce frames to fit
within these new limits.

Note that stack allocation of large strings can be a security issue.
For example, we had code like:

 size_t len = strlen (fs->windows_systemroot) + 64;
 char software[len];
 snprintf (software, len, "%s/system32/config/software",
           fs->windows_systemroot);

where fs->windows_systemroot is guest controlled.  It's not clear what
the effects might be of allowing the guest to allocate potentially
very large stack frames, but at best it allows the guest to cause
libguestfs to segfault.  It turns out we are very lucky that
fs->windows_systemroot cannot be set arbitrarily large (see checks in
is_systemroot).

This commit changes those to large heap allocations instead.
2016-03-07 17:36:24 +00:00

540 lines
13 KiB
C

/* libguestfs - the guestfsd daemon
* Copyright (C) 2011 Red Hat Inc.
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>
#include <glob.h>
#ifdef HAVE_LINUX_RAID_MD_U_H
#include <sys/ioctl.h>
#include <linux/major.h>
#include <linux/raid/md_u.h>
#endif /* HAVE_LINUX_RAID_MD_U_H */
#include "daemon.h"
#include "actions.h"
#include "optgroups.h"
#include "c-ctype.h"
GUESTFSD_EXT_CMD(str_mdadm, mdadm);
int
optgroup_mdadm_available (void)
{
return prog_exists (str_mdadm);
}
static size_t
count_bits (uint64_t bitmap)
{
size_t c;
if (bitmap == 0)
return 0;
c = bitmap & 1 ? 1 : 0;
bitmap >>= 1;
return c + count_bits (bitmap);
}
#if defined(__GNUC__) && GUESTFS_GCC_VERSION >= 40800 /* gcc >= 4.8.0 */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstack-usage="
#endif
/* Takes optional arguments, consult optargs_bitmask. */
int
do_md_create (const char *name, char *const *devices,
int64_t missingbitmap, int nrdevices, int spare,
int64_t chunk, const char *level)
{
char nrdevices_s[32];
char spare_s[32];
char chunk_s[32];
size_t j;
int r;
CLEANUP_FREE char *err = NULL;
uint64_t umissingbitmap = (uint64_t) missingbitmap;
/* Check the optional parameters and set defaults where appropriate. */
if (!(optargs_bitmask & GUESTFS_MD_CREATE_MISSINGBITMAP_BITMASK))
umissingbitmap = 0;
if (optargs_bitmask & GUESTFS_MD_CREATE_SPARE_BITMASK) {
if (spare < 0) {
reply_with_error ("spare must not be negative");
return -1;
}
}
else
spare = 0;
if (optargs_bitmask & GUESTFS_MD_CREATE_NRDEVICES_BITMASK) {
if (nrdevices < 2) {
reply_with_error ("nrdevices is less than 2");
return -1;
}
}
else
nrdevices = count_strings (devices) + count_bits (umissingbitmap);
if (optargs_bitmask & GUESTFS_MD_CREATE_LEVEL_BITMASK) {
if (STRNEQ (level, "linear") && STRNEQ (level, "raid0") &&
STRNEQ (level, "0") && STRNEQ (level, "stripe") &&
STRNEQ (level, "raid1") && STRNEQ (level, "1") &&
STRNEQ (level, "mirror") &&
STRNEQ (level, "raid4") && STRNEQ (level, "4") &&
STRNEQ (level, "raid5") && STRNEQ (level, "5") &&
STRNEQ (level, "raid6") && STRNEQ (level, "6") &&
STRNEQ (level, "raid10") && STRNEQ (level, "10")) {
reply_with_error ("unknown level parameter: %s", level);
return -1;
}
}
else
level = "raid1";
if (optargs_bitmask & GUESTFS_MD_CREATE_CHUNK_BITMASK) {
/* chunk is bytes in the libguestfs API, but K when we pass it to mdadm */
if ((chunk & 1023) != 0) {
reply_with_error ("chunk size must be a multiple of 1024 bytes");
return -1;
}
}
/* Check invariant. */
if (count_strings (devices) + count_bits (umissingbitmap) !=
(size_t) (nrdevices + spare)) {
reply_with_error ("devices (%zu) + bits set in missingbitmap (%zu) is not equal to nrdevices (%d) + spare (%d)",
count_strings (devices), count_bits (umissingbitmap),
nrdevices, spare);
return -1;
}
size_t MAX_ARGS = nrdevices + 16;
const char *argv[MAX_ARGS];
size_t i = 0;
ADD_ARG (argv, i, str_mdadm);
ADD_ARG (argv, i, "--create");
/* --run suppresses "Continue creating array" question */
ADD_ARG (argv, i, "--run");
ADD_ARG (argv, i, name);
ADD_ARG (argv, i, "--level");
ADD_ARG (argv, i, level);
ADD_ARG (argv, i, "--raid-devices");
snprintf (nrdevices_s, sizeof nrdevices_s, "%d", nrdevices);
ADD_ARG (argv, i, nrdevices_s);
if (optargs_bitmask & GUESTFS_MD_CREATE_SPARE_BITMASK) {
ADD_ARG (argv, i, "--spare-devices");
snprintf (spare_s, sizeof spare_s, "%d", spare);
ADD_ARG (argv, i, spare_s);
}
if (optargs_bitmask & GUESTFS_MD_CREATE_CHUNK_BITMASK) {
ADD_ARG (argv, i, "--chunk");
snprintf (chunk_s, sizeof chunk_s, "%" PRIi64, chunk / 1024);
ADD_ARG (argv, i, chunk_s);
}
/* Add devices and "missing". */
j = 0;
while (devices[j] != NULL || umissingbitmap != 0) {
if (umissingbitmap & 1)
ADD_ARG (argv, i, "missing");
else {
ADD_ARG (argv, i, devices[j]);
j++;
}
umissingbitmap >>= 1;
}
ADD_ARG (argv, i, NULL);
r = commandv (NULL, &err, argv);
if (r == -1) {
reply_with_error ("mdadm: %s: %s", name, err);
return -1;
}
udev_settle ();
return 0;
}
#if defined(__GNUC__) && GUESTFS_GCC_VERSION >= 40800 /* gcc >= 4.8.0 */
#pragma GCC diagnostic pop
#endif
static int
glob_errfunc (const char *epath, int eerrno)
{
fprintf (stderr, "glob: failure reading %s: %s\n", epath, strerror (eerrno));
return 1;
}
/* Check if 'dev' is a real RAID device, because in the case where md
* is linked directly into the kernel (not a module), /dev/md0 is
* sometimes created.
*/
static int
is_raid_device (const char *dev)
{
int ret = 1;
#if defined(HAVE_LINUX_RAID_MD_U_H) && defined(GET_ARRAY_INFO)
int fd;
mdu_array_info_t array;
fd = open (dev, O_RDONLY);
if (fd >= 0) {
if (ioctl (fd, GET_ARRAY_INFO, &array) == -1 && errno == ENODEV)
ret = 0;
close (fd);
}
#endif
return ret;
}
char **
do_list_md_devices (void)
{
DECLARE_STRINGSBUF (ret);
glob_t mds;
memset (&mds, 0, sizeof mds);
#define PREFIX "/sys/block/md"
#define SUFFIX "/md"
/* Look for directories under /sys/block matching md[0-9]*
* As an additional check, we also make sure they have a md subdirectory.
*/
int err = glob (PREFIX "[0-9]*" SUFFIX, GLOB_ERR, glob_errfunc, &mds);
if (err == GLOB_NOSPACE) {
reply_with_error ("glob: returned GLOB_NOSPACE: "
"rerun with LIBGUESTFS_DEBUG=1");
goto error;
} else if (err == GLOB_ABORTED) {
reply_with_error ("glob: returned GLOB_ABORTED: "
"rerun with LIBGUESTFS_DEBUG=1");
goto error;
}
for (size_t i = 0; i < mds.gl_pathc; i++) {
size_t len;
char *dev, *n;
len = strlen (mds.gl_pathv[i]) - strlen (PREFIX) - strlen (SUFFIX);
#define DEV "/dev/md"
dev = malloc (strlen (DEV) + len + 1);
if (NULL == dev) {
reply_with_perror ("malloc");
goto error;
}
n = dev;
n = mempcpy (n, DEV, strlen (DEV));
n = mempcpy (n, &mds.gl_pathv[i][strlen (PREFIX)], len);
*n = '\0';
if (!is_raid_device (dev)) {
free (dev);
continue;
}
if (add_string_nodup (&ret, dev) == -1) goto error;
}
if (end_stringsbuf (&ret) == -1) goto error;
globfree (&mds);
return ret.argv;
error:
globfree (&mds);
if (ret.argv != NULL)
free_stringslen (ret.argv, ret.size);
return NULL;
}
char **
do_md_detail (const char *md)
{
size_t i;
int r;
CLEANUP_FREE char *out = NULL, *err = NULL;
CLEANUP_FREE_STRING_LIST char **lines = NULL;
DECLARE_STRINGSBUF (ret);
const char *mdadm[] = { str_mdadm, "-D", "--export", md, NULL };
r = commandv (&out, &err, mdadm);
if (r == -1) {
reply_with_error ("%s", err);
goto error;
}
/* Split the command output into lines */
lines = split_lines (out);
if (lines == NULL)
goto error;
/* Parse the output of mdadm -D --export:
* MD_LEVEL=raid1
* MD_DEVICES=2
* MD_METADATA=1.0
* MD_UUID=cfa81b59:b6cfbd53:3f02085b:58f4a2e1
* MD_NAME=localhost.localdomain:0
*/
for (i = 0; lines[i] != NULL; ++i) {
char *line = lines[i];
/* Skip blank lines (shouldn't happen) */
if (line[0] == '\0') continue;
/* Split the line in 2 at the equals sign */
char *eq = strchr (line, '=');
if (eq) {
*eq = '\0'; eq++;
/* Remove the MD_ prefix from the key and translate the remainder to lower
* case */
if (STRPREFIX (line, "MD_")) {
line += 3;
for (char *j = line; *j != '\0'; j++) {
*j = c_tolower (*j);
}
}
/* Add the key/value pair to the output */
if (add_string (&ret, line) == -1 ||
add_string (&ret, eq) == -1) goto error;
} else {
/* Ignore lines with no equals sign (shouldn't happen). Log to stderr so
* it will show up in LIBGUESTFS_DEBUG. */
fprintf (stderr, "md-detail: unexpected mdadm output ignored: %s", line);
}
}
if (end_stringsbuf (&ret) == -1)
return NULL;
return ret.argv;
error:
if (ret.argv != NULL)
free_stringslen (ret.argv, ret.size);
return NULL;
}
int
do_md_stop (const char *md)
{
int r;
CLEANUP_FREE char *err = NULL;
const char *mdadm[] = { str_mdadm, "--stop", md, NULL};
r = commandv (NULL, &err, mdadm);
if (r == -1) {
reply_with_error ("%s", err);
return -1;
}
return 0;
}
static size_t
count_spaces (const char *line)
{
size_t r = 0;
while (*line) {
if (*line == ' ')
r++;
line++;
}
return r;
}
/* Parse a line like: "active raid1 sdb1[0] sdc1[1](F)" */
static guestfs_int_mdstat_list *
parse_md_stat_line (char *line)
{
guestfs_int_mdstat_list *ret;
guestfs_int_mdstat *t;
size_t spaces, n, i, len;
char *next;
char *p, *q;
ret = malloc (sizeof *ret);
if (!ret) {
reply_with_perror ("malloc");
return NULL;
}
/* We don't know exactly how many entries we will need yet, but we
* can estimate it, and this will always be an over-estimate.
*/
spaces = count_spaces (line);
ret->guestfs_int_mdstat_list_val =
calloc (spaces+1, sizeof (struct guestfs_int_mdstat));
if (ret->guestfs_int_mdstat_list_val == NULL) {
reply_with_perror ("calloc");
free (ret);
return NULL;
}
for (n = 0; *line; line = next) {
len = strcspn (line, " ");
if (line[len] == '\0')
next = &line[len];
else {
line[len] = '\0';
next = &line[len+1];
}
if (verbose)
printf ("mdstat: %s\n", line);
/* Looking for entries that contain "[..]", skip ones which don't. */
p = strchr (line, '[');
if (p == NULL)
continue;
q = strchr (line, ']');
if (q == NULL)
continue;
if (p > q)
continue;
ret->guestfs_int_mdstat_list_len = n+1;
t = &ret->guestfs_int_mdstat_list_val[n];
/* Device name is everything before the '[' character, but we
* need to prefix with /dev/.
*/
if (p == line) {
reply_with_error ("device entry is too short: %s", line);
goto error;
}
*p = '\0';
if (asprintf (&t->mdstat_device, "/dev/%s", line) == -1) {
reply_with_perror ("asprintf");
goto error;
}
/* Device index is the number after '['. */
line = p+1;
*q = '\0';
if (sscanf (line, "%" SCNi32, &t->mdstat_index) != 1) {
reply_with_error ("not a device number: %s", line);
goto error;
}
/* Looking for flags "(F)(S)...". */
line = q+1;
len = strlen (line);
t->mdstat_flags = malloc (len+1);
if (!t->mdstat_flags) {
reply_with_error ("malloc");
goto error;
}
for (i = 0; *line; line++) {
if (c_isalpha (*line))
t->mdstat_flags[i++] = *line;
}
t->mdstat_flags[i] = '\0';
n++;
}
return ret;
error:
for (i = 0; i < spaces+1; ++i) {
free (ret->guestfs_int_mdstat_list_val[i].mdstat_device);
free (ret->guestfs_int_mdstat_list_val[i].mdstat_flags);
}
free (ret->guestfs_int_mdstat_list_val);
free (ret);
return NULL;
}
extern guestfs_int_mdstat_list *
do_md_stat (const char *md)
{
size_t mdlen;
FILE *fp;
CLEANUP_FREE char *line = NULL;
size_t allocsize = 0;
ssize_t n;
guestfs_int_mdstat_list *ret = NULL;
if (STRPREFIX (md, "/dev/"))
md += 5;
mdlen = strlen (md);
fp = fopen ("/proc/mdstat", "r");
if (fp == NULL) {
reply_with_perror ("fopen: %s", "/proc/mdstat");
return NULL;
}
/* Search for a line which begins with "<md> : ". */
while ((n = getline (&line, &allocsize, fp)) != -1) {
if (STRPREFIX (line, md) &&
line[mdlen] == ' ' && line[mdlen+1] == ':' && line[mdlen+2] == ' ') {
/* Found it. */
ret = parse_md_stat_line (&line[mdlen+3]);
if (!ret) {
fclose (fp);
return NULL;
}
/* Stop parsing the mdstat file after we've found the line
* we are interested in.
*/
break;
}
}
if (fclose (fp) == EOF) {
reply_with_perror ("fclose: %s", "/proc/mdstat");
xdr_free ((xdrproc_t) xdr_guestfs_int_mdstat_list, (char *) ret);
return NULL;
}
/* Did we find the line? */
if (!ret) {
reply_with_error ("%s: MD device not found", md);
return NULL;
}
return ret;
}