diff mbox

[RFC,2/2] qga: add --getenv option to get env. vars from fw_cfg

Message ID 1424806987-24790-3-git-send-email-somlo@cmu.edu
State New
Headers show

Commit Message

Gabriel L. Somlo Feb. 24, 2015, 7:43 p.m. UTC
The new "-g" (or "--getenv") command line option causes qemu-ga to extract
and parse the "etc/guestenv" blob from fw_cfg, and return the value of
the requested key (if available) on stdout.

Warnings and error messages are printed to stderr, and only the actual
value portion of a "key=value" string matching the "--getenv key" argument
will be printed to stdout. Strings are searched in reverse order to
implement a sort of "last dupe wins" policy -- but I expect to refine
things quite a bit after receiving some feedback.

I've added a flag to qemu-ga instead of creating a completely separate
binary, but I don't feel strongly about keeping it that way. I just need
something that would end up tightly integrated with the "qemu guest tools"
package...

I also haven't built this on cygwin yet, so it's only tested on Linux
and gcc 4.9.2 (which stubbornly optimizes away one of my "true" if branches
with -O2, if I let it, but that's something else I'm planning to have figured
out by the time the dust settles on this :)

Signed-off-by: Gabriel Somlo <somlo@cmu.edu>
---
 qga/Makefile.objs      |   1 +
 qga/getenv.c           | 140 +++++++++++++++++++++++++++++++++++++++++++++++++
 qga/guest-agent-core.h |   2 +
 qga/main.c             |   7 ++-
 4 files changed, 149 insertions(+), 1 deletion(-)
 create mode 100644 qga/getenv.c

Comments

Daniel P. Berrangé Feb. 25, 2015, 9:09 a.m. UTC | #1
On Tue, Feb 24, 2015 at 02:43:07PM -0500, Gabriel L. Somlo wrote:
> The new "-g" (or "--getenv") command line option causes qemu-ga to extract
> and parse the "etc/guestenv" blob from fw_cfg, and return the value of
> the requested key (if available) on stdout.
> 
> Warnings and error messages are printed to stderr, and only the actual
> value portion of a "key=value" string matching the "--getenv key" argument
> will be printed to stdout. Strings are searched in reverse order to
> implement a sort of "last dupe wins" policy -- but I expect to refine
> things quite a bit after receiving some feedback.
> 
> I've added a flag to qemu-ga instead of creating a completely separate
> binary, but I don't feel strongly about keeping it that way. I just need
> something that would end up tightly integrated with the "qemu guest tools"
> package...

It should really be a separate binary. Overloading multiple functions in
one binary makes it very hard to write a security policy to confine what
the binary can do, as you can't get the kernel to apply different policies
based on what CLI flag was passed.

Regards,
Daniel
diff mbox

Patch

diff --git a/qga/Makefile.objs b/qga/Makefile.objs
index 1c5986c..6392b93 100644
--- a/qga/Makefile.objs
+++ b/qga/Makefile.objs
@@ -4,5 +4,6 @@  qga-obj-$(CONFIG_WIN32) += commands-win32.o channel-win32.o service-win32.o
 qga-obj-$(CONFIG_WIN32) += vss-win32.o
 qga-obj-y += qapi-generated/qga-qapi-types.o qapi-generated/qga-qapi-visit.o
 qga-obj-y += qapi-generated/qga-qmp-marshal.o
+qga-obj-y += getenv.o
 
 qga-vss-dll-obj-$(CONFIG_QGA_VSS) += vss-win32/
diff --git a/qga/getenv.c b/qga/getenv.c
new file mode 100644
index 0000000..9fecb9b
--- /dev/null
+++ b/qga/getenv.c
@@ -0,0 +1,140 @@ 
+/*
+ * QEMU Guest Agent: host->guest environment variable retrieval
+ *
+ * Copyright Carnegie Mellon University 2015
+ *
+ * Author:
+ *  Gabriel L. Somlo  <somlo@cmu.edu>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/io.h>
+#include "qemu/bswap.h"
+#include "hw/nvram/fw_cfg.h"
+#include "qga/guest-agent-core.h"
+
+#define PORT_FW_CFG_CTL       0x0510
+#define PORT_FW_CFG_DATA      0x0511
+
+#define GUESTENV_FW_CFG_FILE "etc/guestenv"
+
+static char *ge_raw;
+
+static struct {
+    char *key;
+    char *val;
+} *ge_var;
+static int ge_var_cnt;
+
+static void
+fw_cfg_select(uint16_t f)
+{
+    outw(f, PORT_FW_CFG_CTL);
+}
+
+static void
+fw_cfg_read(void *buf, int len)
+{
+    insb(PORT_FW_CFG_DATA, buf, len);
+}
+
+static void
+fw_cfg_read_entry(void *buf, int e, int len)
+{
+    fw_cfg_select(e);
+    fw_cfg_read(buf, len);
+}
+
+static int
+__attribute__((optimize("O0"))) //FIXME: gcc -O2 nukes "true" if branch below!!!
+fw_cfg_grab_guestenv(void)
+{
+    int i;
+    uint32_t count, len = 0;
+    uint16_t sel;
+    uint8_t sig[] = "QEMU";
+    FWCfgFile fcfile;
+
+    /* ensure access to the fw_cfg device */
+    if (ioperm(PORT_FW_CFG_CTL, 2, 1) != 0) {
+        perror("ioperm failed");
+        return EXIT_FAILURE;
+    }
+
+    /* verify presence of fw_cfg device */
+    fw_cfg_select(FW_CFG_SIGNATURE);
+    for (i = 0; i < sizeof(sig) - 1; i++) {
+        sig[i] = inb(PORT_FW_CFG_DATA);
+    }
+    if (memcmp(sig, "QEMU", sizeof(sig)) != 0) {
+        fprintf(stderr, "fw_cfg signature not found!\n");
+        return EXIT_FAILURE;
+    }
+
+    /* read number of fw_cfg entries, then scan for guestenv entry */
+    fw_cfg_read_entry(&count, FW_CFG_FILE_DIR, sizeof(count));
+    count = be32_to_cpu(count);
+    for (i = 0; i < count; i++) {
+        fw_cfg_read(&fcfile, sizeof(fcfile));
+        //FIXME: why does gcc -O2 optimize away the whole if {} block below?!?
+        if (!strcmp(fcfile.name, GUESTENV_FW_CFG_FILE)) {
+            len = be32_to_cpu(fcfile.size);
+            sel = be16_to_cpu(fcfile.select);
+            ge_raw = g_malloc(len);
+            fw_cfg_read_entry(ge_raw, sel, len);
+            break;
+        }
+    }
+
+    if (i == count) {
+        /* guestenv entry not present in fw_cfg */
+        fprintf(stderr, "File %s not found in fw_cfg!\n", GUESTENV_FW_CFG_FILE);
+        return EXIT_FAILURE;
+    }
+
+    /* guestenv entry (concatenation of null-terminated ascii strings)
+     * found and copied to ge_raw; Expected string format is "key=val",
+     * and we attempt to extract them into the ge_var table */
+    for (i = 0; i < len;) {
+        ge_var = g_realloc(ge_var, (ge_var_cnt + 1) * sizeof(ge_var[0]));
+        ge_var[ge_var_cnt].key = ge_raw + i;
+        i += strlen(ge_raw + i) + 1;
+        ge_var[ge_var_cnt].val = strchr(ge_var[ge_var_cnt].key, '=');
+        if (ge_var[ge_var_cnt].val != NULL) {
+            ge_var[ge_var_cnt].val[0] = '\0';
+            ge_var[ge_var_cnt].val++;
+            ge_var_cnt++;
+        } else {
+            fprintf(stderr, "warning: skipping non-assignment line: \"%s\"\n",
+                            ge_var[ge_var_cnt].key);
+        }
+    }
+    return 0;
+}
+
+
+int
+ga_getenv(const char *key)
+{
+    int i;
+
+    if (fw_cfg_grab_guestenv() != 0) {
+        return EXIT_FAILURE;
+    }
+
+    /* last value for key "wins" */
+    for (i = ge_var_cnt - 1; i >= 0; i--) {
+        if (strcmp(key, ge_var[i].key) == 0) {
+            printf("%s\n", ge_var[i].val);
+            return 0;
+        }
+    }
+
+    fprintf(stderr, "Key \"%s\" not found\n", key);
+    return EXIT_FAILURE;;
+}
diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h
index e92c6ab..ef54b45 100644
--- a/qga/guest-agent-core.h
+++ b/qga/guest-agent-core.h
@@ -41,3 +41,5 @@  int64_t ga_get_fd_handle(GAState *s, Error **errp);
 #ifndef _WIN32
 void reopen_fd_to_null(int fd);
 #endif
+
+int ga_getenv(const char *key);
diff --git a/qga/main.c b/qga/main.c
index 9939a2b..9159244 100644
--- a/qga/main.c
+++ b/qga/main.c
@@ -215,6 +215,8 @@  static void usage(const char *cmd)
 #endif
 "  -b, --blacklist   comma-separated list of RPCs to disable (no spaces, \"?\"\n"
 "                    to list available RPCs)\n"
+"  -g, --getenv      display the value of a given guest environment variable\n"
+"                    (passed into the guest via the -guestenv \"name=value\" option\n"
 "  -h, --help        display this help and exit\n"
 "\n"
 "Report bugs to <mdroth@linux.vnet.ibm.com>\n"
@@ -923,7 +925,7 @@  static void ga_print_cmd(QmpCommand *cmd, void *opaque)
 
 int main(int argc, char **argv)
 {
-    const char *sopt = "hVvdm:p:l:f:F::b:s:t:";
+    const char *sopt = "hVvdm:p:l:f:F::b:s:t:g:";
     const char *method = NULL, *path = NULL;
     const char *log_filepath = NULL;
     const char *pid_filepath;
@@ -951,6 +953,7 @@  int main(int argc, char **argv)
         { "service", 1, NULL, 's' },
 #endif
         { "statedir", 1, NULL, 't' },
+        { "getenv", 1, NULL, 'g' },
         { NULL, 0, NULL, 0 }
     };
     int opt_ind = 0, ch, daemonize = 0, i, j, len;
@@ -1042,6 +1045,8 @@  int main(int argc, char **argv)
             }
             break;
 #endif
+        case 'g':
+            return ga_getenv(optarg);
         case 'h':
             usage(argv[0]);
             return 0;