|
|
016a62 |
From 6abc65aaa666bf41070fa772293982cb0d1ae835 Mon Sep 17 00:00:00 2001
|
|
|
016a62 |
From: Laszlo Ersek <lersek@redhat.com>
|
|
|
016a62 |
Date: Thu, 12 Sep 2019 13:05:00 +0100
|
|
|
016a62 |
Subject: [PATCH 03/22] opts: don't silently truncate long option values
|
|
|
016a62 |
MIME-Version: 1.0
|
|
|
016a62 |
Content-Type: text/plain; charset=UTF-8
|
|
|
016a62 |
Content-Transfer-Encoding: 8bit
|
|
|
016a62 |
|
|
|
016a62 |
RH-Author: Laszlo Ersek <lersek@redhat.com>
|
|
|
016a62 |
Message-id: <20190912130503.14094-4-lersek@redhat.com>
|
|
|
016a62 |
Patchwork-id: 90436
|
|
|
016a62 |
O-Subject: [RHEL-8.2.0 qemu-kvm PATCH 3/6] opts: don't silently truncate long option values
|
|
|
016a62 |
Bugzilla: 1749022
|
|
|
016a62 |
RH-Acked-by: Stefano Garzarella <sgarzare@redhat.com>
|
|
|
016a62 |
RH-Acked-by: Philippe Mathieu-Daudé <philmd@redhat.com>
|
|
|
016a62 |
RH-Acked-by: Eduardo Habkost <ehabkost@redhat.com>
|
|
|
016a62 |
|
|
|
016a62 |
From: Daniel P. Berrangé <berrange@redhat.com>
|
|
|
016a62 |
|
|
|
016a62 |
The existing QemuOpts parsing code uses a fixed size 1024 byte buffer
|
|
|
016a62 |
for storing the option values. If a value exceeded this size it was
|
|
|
016a62 |
silently truncated and no error reported to the user. Long option values
|
|
|
016a62 |
is not a common scenario, but it is conceivable that they will happen.
|
|
|
016a62 |
eg if the user has a very deeply nested filesystem it would be possible
|
|
|
016a62 |
to come up with a disk path that was > 1024 bytes. Most of the time if
|
|
|
016a62 |
such data was silently truncated, the user would get an error about
|
|
|
016a62 |
opening a non-existant disk. If they're unlucky though, QEMU might use a
|
|
|
016a62 |
completely different disk image from another VM, which could be
|
|
|
016a62 |
considered a security issue. Another example program was in using the
|
|
|
016a62 |
-smbios command line arg with very large data blobs. In this case the
|
|
|
016a62 |
silent truncation will be providing semantically incorrect data to the
|
|
|
016a62 |
guest OS for SMBIOS tables.
|
|
|
016a62 |
|
|
|
016a62 |
If the operating system didn't limit the user's argv when spawning QEMU,
|
|
|
016a62 |
the code should honour whatever length arguments were given without
|
|
|
016a62 |
imposing its own length restrictions. This patch thus changes the code
|
|
|
016a62 |
to use a heap allocated buffer for storing the values during parsing,
|
|
|
016a62 |
lifting the arbitrary length restriction.
|
|
|
016a62 |
|
|
|
016a62 |
RHEL8 notes:
|
|
|
016a62 |
|
|
|
016a62 |
- Fix up upstream's obviously garbled UTF8 sequences in Dan's name (Author
|
|
|
016a62 |
meta-datum, Signed-off-by tags).
|
|
|
016a62 |
|
|
|
016a62 |
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
|
|
|
016a62 |
Message-Id: <20180416111743.8473-4-berrange@redhat.com>
|
|
|
016a62 |
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
|
|
|
016a62 |
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
|
|
|
016a62 |
(cherry picked from commit 950c4e6c94b15cd0d8b63891dddd7a8dbf458e6a)
|
|
|
016a62 |
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
|
|
|
016a62 |
Signed-off-by: Danilo C. L. de Paula <ddepaula@redhat.com>
|
|
|
016a62 |
---
|
|
|
016a62 |
hw/i386/multiboot.c | 33 +++++++++------
|
|
|
016a62 |
include/qemu/option.h | 2 +-
|
|
|
016a62 |
util/qemu-option.c | 111 +++++++++++++++++++++++++++-----------------------
|
|
|
016a62 |
3 files changed, 81 insertions(+), 65 deletions(-)
|
|
|
016a62 |
|
|
|
016a62 |
diff --git a/hw/i386/multiboot.c b/hw/i386/multiboot.c
|
|
|
016a62 |
index 5bc0a2c..7a2953e 100644
|
|
|
016a62 |
--- a/hw/i386/multiboot.c
|
|
|
016a62 |
+++ b/hw/i386/multiboot.c
|
|
|
016a62 |
@@ -291,12 +291,16 @@ int load_multiboot(FWCfgState *fw_cfg,
|
|
|
016a62 |
cmdline_len = strlen(kernel_filename) + 1;
|
|
|
016a62 |
cmdline_len += strlen(kernel_cmdline) + 1;
|
|
|
016a62 |
if (initrd_filename) {
|
|
|
016a62 |
- const char *r = initrd_filename;
|
|
|
016a62 |
+ const char *r = get_opt_value(initrd_filename, NULL);
|
|
|
016a62 |
cmdline_len += strlen(r) + 1;
|
|
|
016a62 |
mbs.mb_mods_avail = 1;
|
|
|
016a62 |
- while (*(r = get_opt_value(NULL, 0, r))) {
|
|
|
016a62 |
- mbs.mb_mods_avail++;
|
|
|
016a62 |
- r++;
|
|
|
016a62 |
+ while (1) {
|
|
|
016a62 |
+ mbs.mb_mods_avail++;
|
|
|
016a62 |
+ r = get_opt_value(r, NULL);
|
|
|
016a62 |
+ if (!*r) {
|
|
|
016a62 |
+ break;
|
|
|
016a62 |
+ }
|
|
|
016a62 |
+ r++;
|
|
|
016a62 |
}
|
|
|
016a62 |
}
|
|
|
016a62 |
|
|
|
016a62 |
@@ -313,7 +317,8 @@ int load_multiboot(FWCfgState *fw_cfg,
|
|
|
016a62 |
|
|
|
016a62 |
if (initrd_filename) {
|
|
|
016a62 |
const char *next_initrd;
|
|
|
016a62 |
- char not_last, tmpbuf[strlen(initrd_filename) + 1];
|
|
|
016a62 |
+ char not_last;
|
|
|
016a62 |
+ char *one_file = NULL;
|
|
|
016a62 |
|
|
|
016a62 |
mbs.offset_mods = mbs.mb_buf_size;
|
|
|
016a62 |
|
|
|
016a62 |
@@ -322,24 +327,26 @@ int load_multiboot(FWCfgState *fw_cfg,
|
|
|
016a62 |
int mb_mod_length;
|
|
|
016a62 |
uint32_t offs = mbs.mb_buf_size;
|
|
|
016a62 |
|
|
|
016a62 |
- next_initrd = get_opt_value(tmpbuf, sizeof(tmpbuf), initrd_filename);
|
|
|
016a62 |
+ next_initrd = get_opt_value(initrd_filename, &one_file);
|
|
|
016a62 |
not_last = *next_initrd;
|
|
|
016a62 |
/* if a space comes after the module filename, treat everything
|
|
|
016a62 |
after that as parameters */
|
|
|
016a62 |
- hwaddr c = mb_add_cmdline(&mbs, tmpbuf);
|
|
|
016a62 |
- if ((next_space = strchr(tmpbuf, ' ')))
|
|
|
016a62 |
+ hwaddr c = mb_add_cmdline(&mbs, one_file);
|
|
|
016a62 |
+ next_space = strchr(one_file, ' ');
|
|
|
016a62 |
+ if (next_space) {
|
|
|
016a62 |
*next_space = '\0';
|
|
|
016a62 |
- mb_debug("multiboot loading module: %s", tmpbuf);
|
|
|
016a62 |
- mb_mod_length = get_image_size(tmpbuf);
|
|
|
016a62 |
+ }
|
|
|
016a62 |
+ mb_debug("multiboot loading module: %s", one_file);
|
|
|
016a62 |
+ mb_mod_length = get_image_size(one_file);
|
|
|
016a62 |
if (mb_mod_length < 0) {
|
|
|
016a62 |
- error_report("Failed to open file '%s'", tmpbuf);
|
|
|
016a62 |
+ error_report("Failed to open file '%s'", one_file);
|
|
|
016a62 |
exit(1);
|
|
|
016a62 |
}
|
|
|
016a62 |
|
|
|
016a62 |
mbs.mb_buf_size = TARGET_PAGE_ALIGN(mb_mod_length + mbs.mb_buf_size);
|
|
|
016a62 |
mbs.mb_buf = g_realloc(mbs.mb_buf, mbs.mb_buf_size);
|
|
|
016a62 |
|
|
|
016a62 |
- load_image(tmpbuf, (unsigned char *)mbs.mb_buf + offs);
|
|
|
016a62 |
+ load_image(one_file, (unsigned char *)mbs.mb_buf + offs);
|
|
|
016a62 |
mb_add_mod(&mbs, mbs.mb_buf_phys + offs,
|
|
|
016a62 |
mbs.mb_buf_phys + offs + mb_mod_length, c);
|
|
|
016a62 |
|
|
|
016a62 |
@@ -347,6 +354,8 @@ int load_multiboot(FWCfgState *fw_cfg,
|
|
|
016a62 |
(char *)mbs.mb_buf + offs,
|
|
|
016a62 |
(char *)mbs.mb_buf + offs + mb_mod_length, c);
|
|
|
016a62 |
initrd_filename = next_initrd+1;
|
|
|
016a62 |
+ g_free(one_file);
|
|
|
016a62 |
+ one_file = NULL;
|
|
|
016a62 |
} while (not_last);
|
|
|
016a62 |
}
|
|
|
016a62 |
|
|
|
016a62 |
diff --git a/include/qemu/option.h b/include/qemu/option.h
|
|
|
016a62 |
index 1cfe5cb..3dfb449 100644
|
|
|
016a62 |
--- a/include/qemu/option.h
|
|
|
016a62 |
+++ b/include/qemu/option.h
|
|
|
016a62 |
@@ -28,7 +28,7 @@
|
|
|
016a62 |
|
|
|
016a62 |
#include "qemu/queue.h"
|
|
|
016a62 |
|
|
|
016a62 |
-const char *get_opt_value(char *buf, int buf_size, const char *p);
|
|
|
016a62 |
+const char *get_opt_value(const char *p, char **value);
|
|
|
016a62 |
|
|
|
016a62 |
void parse_option_size(const char *name, const char *value,
|
|
|
016a62 |
uint64_t *ret, Error **errp);
|
|
|
016a62 |
diff --git a/util/qemu-option.c b/util/qemu-option.c
|
|
|
016a62 |
index b99568f..ba44a08 100644
|
|
|
016a62 |
--- a/util/qemu-option.c
|
|
|
016a62 |
+++ b/util/qemu-option.c
|
|
|
016a62 |
@@ -70,25 +70,37 @@ static const char *get_opt_name(const char *p, char **option, char delim)
|
|
|
016a62 |
* delimiter is fixed to be comma which starts a new option. To specify an
|
|
|
016a62 |
* option value that contains commas, double each comma.
|
|
|
016a62 |
*/
|
|
|
016a62 |
-const char *get_opt_value(char *buf, int buf_size, const char *p)
|
|
|
016a62 |
+const char *get_opt_value(const char *p, char **value)
|
|
|
016a62 |
{
|
|
|
016a62 |
- char *q;
|
|
|
016a62 |
+ size_t capacity = 0, length;
|
|
|
016a62 |
+ const char *offset;
|
|
|
016a62 |
+
|
|
|
016a62 |
+ *value = NULL;
|
|
|
016a62 |
+ while (1) {
|
|
|
016a62 |
+ offset = strchr(p, ',');
|
|
|
016a62 |
+ if (!offset) {
|
|
|
016a62 |
+ offset = p + strlen(p);
|
|
|
016a62 |
+ }
|
|
|
016a62 |
|
|
|
016a62 |
- q = buf;
|
|
|
016a62 |
- while (*p != '\0') {
|
|
|
016a62 |
- if (*p == ',') {
|
|
|
016a62 |
- if (*(p + 1) != ',')
|
|
|
016a62 |
- break;
|
|
|
016a62 |
- p++;
|
|
|
016a62 |
+ length = offset - p;
|
|
|
016a62 |
+ if (*offset != '\0' && *(offset + 1) == ',') {
|
|
|
016a62 |
+ length++;
|
|
|
016a62 |
+ }
|
|
|
016a62 |
+ if (value) {
|
|
|
016a62 |
+ *value = g_renew(char, *value, capacity + length + 1);
|
|
|
016a62 |
+ strncpy(*value + capacity, p, length);
|
|
|
016a62 |
+ (*value)[capacity + length] = '\0';
|
|
|
016a62 |
+ }
|
|
|
016a62 |
+ capacity += length;
|
|
|
016a62 |
+ if (*offset == '\0' ||
|
|
|
016a62 |
+ *(offset + 1) != ',') {
|
|
|
016a62 |
+ break;
|
|
|
016a62 |
}
|
|
|
016a62 |
- if (q && (q - buf) < buf_size - 1)
|
|
|
016a62 |
- *q++ = *p;
|
|
|
016a62 |
- p++;
|
|
|
016a62 |
+
|
|
|
016a62 |
+ p += (offset - p) + 2;
|
|
|
016a62 |
}
|
|
|
016a62 |
- if (q)
|
|
|
016a62 |
- *q = '\0';
|
|
|
016a62 |
|
|
|
016a62 |
- return p;
|
|
|
016a62 |
+ return offset;
|
|
|
016a62 |
}
|
|
|
016a62 |
|
|
|
016a62 |
static void parse_option_bool(const char *name, const char *value, bool *ret,
|
|
|
016a62 |
@@ -162,50 +174,43 @@ void parse_option_size(const char *name, const char *value,
|
|
|
016a62 |
|
|
|
016a62 |
bool has_help_option(const char *param)
|
|
|
016a62 |
{
|
|
|
016a62 |
- size_t buflen = strlen(param) + 1;
|
|
|
016a62 |
- char *buf = g_malloc(buflen);
|
|
|
016a62 |
const char *p = param;
|
|
|
016a62 |
bool result = false;
|
|
|
016a62 |
|
|
|
016a62 |
- while (*p) {
|
|
|
016a62 |
- p = get_opt_value(buf, buflen, p);
|
|
|
016a62 |
+ while (*p && !result) {
|
|
|
016a62 |
+ char *value;
|
|
|
016a62 |
+
|
|
|
016a62 |
+ p = get_opt_value(p, &value);
|
|
|
016a62 |
if (*p) {
|
|
|
016a62 |
p++;
|
|
|
016a62 |
}
|
|
|
016a62 |
|
|
|
016a62 |
- if (is_help_option(buf)) {
|
|
|
016a62 |
- result = true;
|
|
|
016a62 |
- goto out;
|
|
|
016a62 |
- }
|
|
|
016a62 |
+ result = is_help_option(value);
|
|
|
016a62 |
+ g_free(value);
|
|
|
016a62 |
}
|
|
|
016a62 |
|
|
|
016a62 |
-out:
|
|
|
016a62 |
- g_free(buf);
|
|
|
016a62 |
return result;
|
|
|
016a62 |
}
|
|
|
016a62 |
|
|
|
016a62 |
-bool is_valid_option_list(const char *param)
|
|
|
016a62 |
+bool is_valid_option_list(const char *p)
|
|
|
016a62 |
{
|
|
|
016a62 |
- size_t buflen = strlen(param) + 1;
|
|
|
016a62 |
- char *buf = g_malloc(buflen);
|
|
|
016a62 |
- const char *p = param;
|
|
|
016a62 |
- bool result = true;
|
|
|
016a62 |
+ char *value = NULL;
|
|
|
016a62 |
+ bool result = false;
|
|
|
016a62 |
|
|
|
016a62 |
while (*p) {
|
|
|
016a62 |
- p = get_opt_value(buf, buflen, p);
|
|
|
016a62 |
- if (*p && !*++p) {
|
|
|
016a62 |
- result = false;
|
|
|
016a62 |
+ p = get_opt_value(p, &value);
|
|
|
016a62 |
+ if ((*p && !*++p) ||
|
|
|
016a62 |
+ (!*value || *value == ',')) {
|
|
|
016a62 |
goto out;
|
|
|
016a62 |
}
|
|
|
016a62 |
|
|
|
016a62 |
- if (!*buf || *buf == ',') {
|
|
|
016a62 |
- result = false;
|
|
|
016a62 |
- goto out;
|
|
|
016a62 |
- }
|
|
|
016a62 |
+ g_free(value);
|
|
|
016a62 |
+ value = NULL;
|
|
|
016a62 |
}
|
|
|
016a62 |
|
|
|
016a62 |
+ result = true;
|
|
|
016a62 |
out:
|
|
|
016a62 |
- g_free(buf);
|
|
|
016a62 |
+ g_free(value);
|
|
|
016a62 |
return result;
|
|
|
016a62 |
}
|
|
|
016a62 |
|
|
|
016a62 |
@@ -486,7 +491,7 @@ int qemu_opt_unset(QemuOpts *opts, const char *name)
|
|
|
016a62 |
}
|
|
|
016a62 |
}
|
|
|
016a62 |
|
|
|
016a62 |
-static void opt_set(QemuOpts *opts, const char *name, const char *value,
|
|
|
016a62 |
+static void opt_set(QemuOpts *opts, const char *name, char *value,
|
|
|
016a62 |
bool prepend, Error **errp)
|
|
|
016a62 |
{
|
|
|
016a62 |
QemuOpt *opt;
|
|
|
016a62 |
@@ -495,6 +500,7 @@ static void opt_set(QemuOpts *opts, const char *name, const char *value,
|
|
|
016a62 |
|
|
|
016a62 |
desc = find_desc_by_name(opts->list->desc, name);
|
|
|
016a62 |
if (!desc && !opts_accepts_any(opts)) {
|
|
|
016a62 |
+ g_free(value);
|
|
|
016a62 |
error_setg(errp, QERR_INVALID_PARAMETER, name);
|
|
|
016a62 |
return;
|
|
|
016a62 |
}
|
|
|
016a62 |
@@ -508,8 +514,7 @@ static void opt_set(QemuOpts *opts, const char *name, const char *value,
|
|
|
016a62 |
QTAILQ_INSERT_TAIL(&opts->head, opt, next);
|
|
|
016a62 |
}
|
|
|
016a62 |
opt->desc = desc;
|
|
|
016a62 |
- opt->str = g_strdup(value);
|
|
|
016a62 |
- assert(opt->str);
|
|
|
016a62 |
+ opt->str = value;
|
|
|
016a62 |
qemu_opt_parse(opt, &local_err);
|
|
|
016a62 |
if (local_err) {
|
|
|
016a62 |
error_propagate(errp, local_err);
|
|
|
016a62 |
@@ -520,7 +525,7 @@ static void opt_set(QemuOpts *opts, const char *name, const char *value,
|
|
|
016a62 |
void qemu_opt_set(QemuOpts *opts, const char *name, const char *value,
|
|
|
016a62 |
Error **errp)
|
|
|
016a62 |
{
|
|
|
016a62 |
- opt_set(opts, name, value, false, errp);
|
|
|
016a62 |
+ opt_set(opts, name, g_strdup(value), false, errp);
|
|
|
016a62 |
}
|
|
|
016a62 |
|
|
|
016a62 |
void qemu_opt_set_bool(QemuOpts *opts, const char *name, bool val,
|
|
|
016a62 |
@@ -754,7 +759,7 @@ static void opts_do_parse(QemuOpts *opts, const char *params,
|
|
|
016a62 |
const char *firstname, bool prepend, Error **errp)
|
|
|
016a62 |
{
|
|
|
016a62 |
char *option = NULL;
|
|
|
016a62 |
- char value[1024];
|
|
|
016a62 |
+ char *value = NULL;
|
|
|
016a62 |
const char *p,*pe,*pc;
|
|
|
016a62 |
Error *local_err = NULL;
|
|
|
016a62 |
|
|
|
016a62 |
@@ -766,15 +771,15 @@ static void opts_do_parse(QemuOpts *opts, const char *params,
|
|
|
016a62 |
if (p == params && firstname) {
|
|
|
016a62 |
/* implicitly named first option */
|
|
|
016a62 |
option = g_strdup(firstname);
|
|
|
016a62 |
- p = get_opt_value(value, sizeof(value), p);
|
|
|
016a62 |
+ p = get_opt_value(p, &value);
|
|
|
016a62 |
} else {
|
|
|
016a62 |
/* option without value, probably a flag */
|
|
|
016a62 |
p = get_opt_name(p, &option, ',');
|
|
|
016a62 |
if (strncmp(option, "no", 2) == 0) {
|
|
|
016a62 |
memmove(option, option+2, strlen(option+2)+1);
|
|
|
016a62 |
- pstrcpy(value, sizeof(value), "off");
|
|
|
016a62 |
+ value = g_strdup("off");
|
|
|
016a62 |
} else {
|
|
|
016a62 |
- pstrcpy(value, sizeof(value), "on");
|
|
|
016a62 |
+ value = g_strdup("on");
|
|
|
016a62 |
}
|
|
|
016a62 |
}
|
|
|
016a62 |
} else {
|
|
|
016a62 |
@@ -782,11 +787,12 @@ static void opts_do_parse(QemuOpts *opts, const char *params,
|
|
|
016a62 |
p = get_opt_name(p, &option, '=');
|
|
|
016a62 |
assert(*p == '=');
|
|
|
016a62 |
p++;
|
|
|
016a62 |
- p = get_opt_value(value, sizeof(value), p);
|
|
|
016a62 |
+ p = get_opt_value(p, &value);
|
|
|
016a62 |
}
|
|
|
016a62 |
if (strcmp(option, "id") != 0) {
|
|
|
016a62 |
/* store and parse */
|
|
|
016a62 |
opt_set(opts, option, value, prepend, &local_err);
|
|
|
016a62 |
+ value = NULL;
|
|
|
016a62 |
if (local_err) {
|
|
|
016a62 |
error_propagate(errp, local_err);
|
|
|
016a62 |
goto cleanup;
|
|
|
016a62 |
@@ -796,11 +802,13 @@ static void opts_do_parse(QemuOpts *opts, const char *params,
|
|
|
016a62 |
break;
|
|
|
016a62 |
}
|
|
|
016a62 |
g_free(option);
|
|
|
016a62 |
- option = NULL;
|
|
|
016a62 |
+ g_free(value);
|
|
|
016a62 |
+ option = value = NULL;
|
|
|
016a62 |
}
|
|
|
016a62 |
|
|
|
016a62 |
cleanup:
|
|
|
016a62 |
g_free(option);
|
|
|
016a62 |
+ g_free(value);
|
|
|
016a62 |
}
|
|
|
016a62 |
|
|
|
016a62 |
/**
|
|
|
016a62 |
@@ -819,7 +827,7 @@ static QemuOpts *opts_parse(QemuOptsList *list, const char *params,
|
|
|
016a62 |
bool permit_abbrev, bool defaults, Error **errp)
|
|
|
016a62 |
{
|
|
|
016a62 |
const char *firstname;
|
|
|
016a62 |
- char value[1024], *id = NULL;
|
|
|
016a62 |
+ char *id = NULL;
|
|
|
016a62 |
const char *p;
|
|
|
016a62 |
QemuOpts *opts;
|
|
|
016a62 |
Error *local_err = NULL;
|
|
|
016a62 |
@@ -828,11 +836,9 @@ static QemuOpts *opts_parse(QemuOptsList *list, const char *params,
|
|
|
016a62 |
firstname = permit_abbrev ? list->implied_opt_name : NULL;
|
|
|
016a62 |
|
|
|
016a62 |
if (strncmp(params, "id=", 3) == 0) {
|
|
|
016a62 |
- get_opt_value(value, sizeof(value), params+3);
|
|
|
016a62 |
- id = value;
|
|
|
016a62 |
+ get_opt_value(params + 3, &id;;
|
|
|
016a62 |
} else if ((p = strstr(params, ",id=")) != NULL) {
|
|
|
016a62 |
- get_opt_value(value, sizeof(value), p+4);
|
|
|
016a62 |
- id = value;
|
|
|
016a62 |
+ get_opt_value(p + 4, &id;;
|
|
|
016a62 |
}
|
|
|
016a62 |
|
|
|
016a62 |
/*
|
|
|
016a62 |
@@ -844,6 +850,7 @@ static QemuOpts *opts_parse(QemuOptsList *list, const char *params,
|
|
|
016a62 |
*/
|
|
|
016a62 |
assert(!defaults || list->merge_lists);
|
|
|
016a62 |
opts = qemu_opts_create(list, id, !defaults, &local_err);
|
|
|
016a62 |
+ g_free(id);
|
|
|
016a62 |
if (opts == NULL) {
|
|
|
016a62 |
error_propagate(errp, local_err);
|
|
|
016a62 |
return NULL;
|
|
|
016a62 |
--
|
|
|
016a62 |
1.8.3.1
|
|
|
016a62 |
|