psss / rpms / libguestfs

Forked from rpms/libguestfs 5 years ago
Clone
Blob Blame History Raw
From 52af6f30a0efc4990ea2302e2467851af3ffd61d Mon Sep 17 00:00:00 2001
From: Richard W.M. Jones <rjones@redhat.com>
Date: Mon, 18 Jun 2012 16:06:34 +0100
Subject: [PATCH 14/35] EPEL 5: Add "null vmchannel" back for qemu without virtio-serial support.

---
 appliance/init         |    4 +-
 daemon/guestfsd.c      |   99 ++++++++++++++++++++++--
 src/guestfs-internal.h |    2 +-
 src/launch-appliance.c |  196 ++++++++++++++++++++++++++++++++++--------------
 src/launch.c           |    4 +-
 5 files changed, 236 insertions(+), 69 deletions(-)

diff --git a/appliance/init b/appliance/init
index 290d6ac..20c8870 100755
--- a/appliance/init
+++ b/appliance/init
@@ -80,10 +80,10 @@ hwclock -u -s
 ip addr add 127.0.0.1/8 brd + dev lo scope host
 ip link set dev lo up
 
-ip addr add 169.254.2.10/16 brd + dev eth0 scope global
+ip addr add 10.0.2.0/24 brd + dev eth0 scope global
 ip link set dev eth0 up
 
-ip route add default via 169.254.2.2
+ip route add default via 10.0.2.2
 
 # Scan for MDs.
 mdadm -As --auto=yes --run
diff --git a/daemon/guestfsd.c b/daemon/guestfsd.c
index 254e0ea..bcb836a 100644
--- a/daemon/guestfsd.c
+++ b/daemon/guestfsd.c
@@ -118,9 +118,6 @@ int autosync_umount = 1;
 /* Not used explicitly, but required by the gnulib 'error' module. */
 const char *program_name = "guestfsd";
 
-/* Name of the virtio-serial channel. */
-#define VIRTIO_SERIAL_CHANNEL "/dev/virtio-ports/org.libguestfs.channel.0"
-
 static void
 usage (void)
 {
@@ -139,6 +136,7 @@ main (int argc, char *argv[])
   };
   int c;
   char *cmdline;
+  char *vmchannel = NULL;
 
   ignore_value (chdir ("/"));
 
@@ -210,8 +208,6 @@ main (int argc, char *argv[])
       printf ("could not read linux command line\n");
   }
 
-  free (cmdline);
-
 #ifndef WIN32
   /* Make sure SIGPIPE doesn't kill us. */
   struct sigaction sa;
@@ -253,9 +249,94 @@ main (int argc, char *argv[])
    */
   copy_lvm ();
 
-  /* Connect to virtio-serial channel. */
-  int sock = open (VIRTIO_SERIAL_CHANNEL, O_RDWR|O_CLOEXEC);
+  /* Get the vmchannel string.
+   *
+   * Sources:
+   *   --channel/-c option on the command line
+   *   guestfs_vmchannel=... from the kernel command line
+   *   guestfs=... from the kernel command line
+   *   built-in default
+   *
+   * At the moment we expect this to contain "tcp:ip:port" but in
+   * future it might contain a device name, eg. "/dev/vcon4" for
+   * virtio-console vmchannel.
+   */
+  if (cmdline) {
+    char *p;
+    size_t len;
+
+    p = strstr (cmdline, "guestfs_vmchannel=");
+    if (p) {
+      len = strcspn (p + 18, " \t\n");
+      vmchannel = strndup (p + 18, len);
+      if (!vmchannel) {
+        perror ("strndup");
+        exit (EXIT_FAILURE);
+      }
+    }
+  }
+
+  /* Default vmchannel. */
+  if (vmchannel == NULL)
+    goto no_vmchannel;
+
+  if (verbose)
+    printf ("vmchannel: %s\n", vmchannel);
+
+  /* Connect to vmchannel. */
+  int sock = -1;
+
+  if (STREQLEN (vmchannel, "tcp:", 4)) {
+    /* Resolve the hostname. */
+    struct addrinfo *res, *rr;
+    struct addrinfo hints;
+    int r;
+    char *host, *port;
+
+    host = vmchannel+4;
+    port = strchr (host, ':');
+    if (port) {
+      port[0] = '\0';
+      port++;
+    } else {
+      fprintf (stderr, "vmchannel: expecting \"tcp:<ip>:<port>\": %s\n",
+               vmchannel);
+      exit (EXIT_FAILURE);
+    }
+
+    memset (&hints, 0, sizeof hints);
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_flags = AI_ADDRCONFIG;
+    r = getaddrinfo (host, port, &hints, &res);
+    if (r != 0) {
+      fprintf (stderr, "%s:%s: %s\n",
+               host, port, gai_strerror (r));
+      exit (EXIT_FAILURE);
+    }
+
+    /* Connect to the given TCP socket. */
+    for (rr = res; rr != NULL; rr = rr->ai_next) {
+      sock = socket (rr->ai_family, rr->ai_socktype, rr->ai_protocol);
+      if (sock != -1) {
+        if (connect (sock, rr->ai_addr, rr->ai_addrlen) == 0)
+          break;
+        perror ("connect");
+
+        close (sock);
+        sock = -1;
+      }
+    }
+    freeaddrinfo (res);
+  } else {
+    fprintf (stderr,
+             "unknown vmchannel connection type: %s\n"
+             "expecting \"tcp:<ip>:<port>\"\n",
+             vmchannel);
+    exit (EXIT_FAILURE);
+  }
+
   if (sock == -1) {
+  no_vmchannel:
     fprintf (stderr,
              "\n"
              "Failed to connect to virtio-serial channel.\n"
@@ -269,7 +350,7 @@ main (int argc, char *argv[])
              "output to the libguestfs developers, either in a bug report\n"
              "or on the libguestfs redhat com mailing list.\n"
              "\n");
-    perror (VIRTIO_SERIAL_CHANNEL);
+    perror ("last error");
     exit (EXIT_FAILURE);
   }
 
@@ -300,6 +381,8 @@ main (int argc, char *argv[])
 
   xdr_destroy (&xdr);
 
+  free (cmdline);
+
   /* Enter the main loop, reading and performing actions. */
   main_loop (sock);
 
diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h
index aaa9d41..cb812ed 100644
--- a/src/guestfs-internal.h
+++ b/src/guestfs-internal.h
@@ -516,7 +516,7 @@ extern int64_t guestfs___timeval_diff (const struct timeval *x, const struct tim
 extern void guestfs___print_timestamped_message (guestfs_h *g, const char *fs, ...) __attribute__((format (printf,2,3)));
 extern void guestfs___launch_send_progress (guestfs_h *g, int perdozen);
 extern void guestfs___launch_failed_error (guestfs_h *g);
-extern char *guestfs___appliance_command_line (guestfs_h *g, const char *appliance_dev, int flags);
+extern char *guestfs___appliance_command_line (guestfs_h *g, const char *appliance_dev, int flags, const char *vmchannel);
 #define APPLIANCE_COMMAND_LINE_IS_TCG 1
 
 /* launch-appliance.c */
diff --git a/src/launch-appliance.c b/src/launch-appliance.c
index 5de3b0a..71d7aa6 100644
--- a/src/launch-appliance.c
+++ b/src/launch-appliance.c
@@ -74,12 +74,16 @@ free_regexps (void)
   pcre_free (re_major_minor);
 }
 
+#define NETWORK "10.0.2.0/24"
+#define ROUTER "10.0.2.2"
+
 static int is_openable (guestfs_h *g, const char *path, int flags);
 static void print_qemu_command_line (guestfs_h *g, char **argv);
 static int qemu_supports (guestfs_h *g, const char *option);
 static int qemu_supports_device (guestfs_h *g, const char *device_name);
 static int qemu_supports_virtio_scsi (guestfs_h *g);
 static char *qemu_drive_param (guestfs_h *g, const struct drive *drv, size_t index);
+static int check_peer_euid (guestfs_h *g, int sock, uid_t *rtn);
 
 /* Functions to build up the qemu command line.  These are only run
  * in the child process so no clean-up is required.
@@ -169,7 +173,9 @@ launch_appliance (guestfs_h *g, const char *arg)
   int r;
   int wfd[2], rfd[2];
   char guestfsd_sock[256];
-  struct sockaddr_un addr;
+  struct sockaddr_in addr;
+  socklen_t addrlen = sizeof addr;
+  int null_vmchannel_port;
   CLEANUP_FREE char *kernel = NULL, *initrd = NULL, *appliance = NULL;
   int has_appliance_drive;
   uint32_t size;
@@ -203,37 +209,43 @@ launch_appliance (guestfs_h *g, const char *arg)
   if (qemu_supports (g, NULL) == -1)
     goto cleanup0;
 
-  /* Using virtio-serial, we need to create a local Unix domain socket
-   * for qemu to connect to.
+  /* "Null vmchannel" implementation: We allocate a random port
+   * number on the host, and the daemon connects back to it.  To
+   * make this secure, we check that the peer UID is the same as our
+   * UID.  This requires SLIRP (user mode networking in qemu).
    */
-  snprintf (guestfsd_sock, sizeof guestfsd_sock, "%s/guestfsd.sock", g->tmpdir);
-  unlink (guestfsd_sock);
-
-  g->sock = socket (AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
+  g->sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
   if (g->sock == -1) {
     perrorf (g, "socket");
     goto cleanup0;
   }
 
-  if (fcntl (g->sock, F_SETFL, O_NONBLOCK) == -1) {
-    perrorf (g, "fcntl");
+  addr.sin_family = AF_INET;
+  addr.sin_port = htons (0);
+  addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+  if (bind (g->sock, (struct sockaddr *) &addr, addrlen) == -1) {
+    perrorf (g, "bind");
     goto cleanup0;
   }
 
-  addr.sun_family = AF_UNIX;
-  strncpy (addr.sun_path, guestfsd_sock, UNIX_PATH_MAX);
-  addr.sun_path[UNIX_PATH_MAX-1] = '\0';
+  if (listen (g->sock, 256) == -1) {
+    perrorf (g, "listen");
+    goto cleanup0;
+  }
 
-  if (bind (g->sock, &addr, sizeof addr) == -1) {
-    perrorf (g, "bind");
+  if (getsockname (g->sock, (struct sockaddr *) &addr, &addrlen) == -1) {
+    perrorf (g, "getsockname");
     goto cleanup0;
   }
 
-  if (listen (g->sock, 1) == -1) {
-    perrorf (g, "listen");
+  if (fcntl (g->sock, F_SETFL, O_NONBLOCK) == -1) {
+    perrorf (g, "fcntl");
     goto cleanup0;
   }
 
+  null_vmchannel_port = ntohs (addr.sin_port);
+  debug (g, "null_vmchannel_port = %d", null_vmchannel_port);
+
   if (!g->direct) {
     if (pipe (wfd) == -1 || pipe (rfd) == -1) {
       perrorf (g, "pipe");
@@ -398,23 +410,9 @@ launch_appliance (guestfs_h *g, const char *arg)
     if (qemu_supports (g, "-rtc-td-hack"))
       add_cmdline (g, "-rtc-td-hack");
 
-    /* Create the virtio serial bus. */
-    add_cmdline (g, "-device");
-    add_cmdline (g, "virtio-serial");
-
-#if 0
-    /* Use virtio-console (a variant form of virtio-serial) for the
-     * guest's serial console.
-     */
-    add_cmdline (g, "-chardev");
-    add_cmdline (g, "stdio,id=console");
-    add_cmdline (g, "-device");
-    add_cmdline (g, "virtconsole,chardev=console,name=org.libguestfs.console.0");
-#else
-    /* When the above works ...  until then: */
+    /* Serial console. */
     add_cmdline (g, "-serial");
     add_cmdline (g, "stdio");
-#endif
 
     if (qemu_supports_device (g, "Serial Graphics Adapter")) {
       /* Use sgabios instead of vgabios.  This means we'll see BIOS
@@ -427,12 +425,16 @@ launch_appliance (guestfs_h *g, const char *arg)
       add_cmdline (g, "sga");
     }
 
-    /* Set up virtio-serial for the communications channel. */
-    add_cmdline (g, "-chardev");
-    snprintf (buf, sizeof buf, "socket,path=%s,id=channel0", guestfsd_sock);
-    add_cmdline (g, buf);
-    add_cmdline (g, "-device");
-    add_cmdline (g, "virtserialport,chardev=channel0,name=org.libguestfs.channel.0");
+    /* Null vmchannel. */
+    add_cmdline (g, "-net");
+    add_cmdline (g, "user,vlan=0,net=" NETWORK);
+    add_cmdline (g, "-net");
+    add_cmdline (g, "nic,model=virtio,vlan=0");
+
+    snprintf (buf, sizeof buf,
+              "guestfs_vmchannel=tcp:" ROUTER ":%d",
+              null_vmchannel_port);
+    char *vmchannel = strdup (buf);
 
 #ifdef VALGRIND_DAEMON
     /* Set up virtio-serial channel for valgrind messages. */
@@ -444,14 +446,6 @@ launch_appliance (guestfs_h *g, const char *arg)
     add_cmdline (g, "virtserialport,chardev=valgrind,name=org.libguestfs.valgrind");
 #endif
 
-    /* Enable user networking. */
-    if (g->enable_network) {
-      add_cmdline (g, "-netdev");
-      add_cmdline (g, "user,id=usernet,net=169.254.0.0/16");
-      add_cmdline (g, "-device");
-      add_cmdline (g, "virtio-net-pci,netdev=usernet");
-    }
-
     add_cmdline (g, "-kernel");
     add_cmdline (g, kernel);
     add_cmdline (g, "-initrd");
@@ -459,7 +453,7 @@ launch_appliance (guestfs_h *g, const char *arg)
 
     add_cmdline (g, "-append");
     CLEANUP_FREE char *cmdline =
-      guestfs___appliance_command_line (g, appliance_dev, 0);
+      guestfs___appliance_command_line (g, appliance_dev, 0, vmchannel);
     add_cmdline (g, cmdline);
 
     /* Note: custom command line parameters must come last so that
@@ -620,19 +614,30 @@ launch_appliance (guestfs_h *g, const char *arg)
 
   g->state = LAUNCHING;
 
-  /* Wait for qemu to start and to connect back to us via
-   * virtio-serial and send the GUESTFS_LAUNCH_FLAG message.
+  /* Null vmchannel implementation: We listen on g->sock for a
+   * connection.  The connection could come from any local process
+   * so we must check it comes from the appliance (or at least
+   * from our UID) for security reasons.
    */
-  r = guestfs___accept_from_daemon (g);
-  if (r == -1)
-    goto cleanup1;
+  r = -1;
+  while (r == -1) {
+    uid_t uid;
 
-  /* NB: We reach here just because qemu has opened the socket.  It
-   * does not mean the daemon is up until we read the
-   * GUESTFS_LAUNCH_FLAG below.  Failures in qemu startup can still
-   * happen even if we reach here, even early failures like not being
-   * able to open a drive.
-   */
+    r = guestfs___accept_from_daemon (g);
+    if (r == -1)
+      goto cleanup1;
+
+    if (check_peer_euid (g, r, &uid) == -1)
+      goto cleanup1;
+    if (uid != geteuid ()) {
+      fprintf (stderr,
+               "libguestfs: warning: unexpected connection from UID %d to port %d\n",
+               uid, null_vmchannel_port);
+      close (r);
+      r = -1;
+      continue;
+    }
+  }
 
   /* Close the listening socket. */
   if (close (g->sock) != 0) {
@@ -987,6 +992,83 @@ guestfs___drive_name (size_t index, char *ret)
   return ret;
 }
 
+/* Check the peer effective UID for a TCP socket.  Ideally we'd like
+ * SO_PEERCRED for a loopback TCP socket.  This isn't possible on
+ * Linux (but it is on Solaris!) so we read /proc/net/tcp instead.
+ */
+static int
+check_peer_euid (guestfs_h *g, int sock, uid_t *rtn)
+{
+  struct sockaddr_in peer;
+  socklen_t addrlen = sizeof peer;
+
+  if (getpeername (sock, (struct sockaddr *) &peer, &addrlen) == -1) {
+    perrorf (g, "getpeername");
+    return -1;
+  }
+
+  if (peer.sin_family != AF_INET ||
+      ntohl (peer.sin_addr.s_addr) != INADDR_LOOPBACK) {
+    error (g, "check_peer_euid: unexpected connection from non-IPv4, non-loopback peer (family = %d, addr = %s)",
+           peer.sin_family, inet_ntoa (peer.sin_addr));
+    return -1;
+  }
+
+  struct sockaddr_in our;
+  addrlen = sizeof our;
+  if (getsockname (sock, (struct sockaddr *) &our, &addrlen) == -1) {
+    perrorf (g, "getsockname");
+    return -1;
+  }
+
+  FILE *fp = fopen ("/proc/net/tcp", "r");
+  if (fp == NULL) {
+    perrorf (g, "/proc/net/tcp");
+    return -1;
+  }
+
+  char line[256];
+  if (fgets (line, sizeof line, fp) == NULL) { /* Drop first line. */
+    error (g, "unexpected end of file in /proc/net/tcp");
+    fclose (fp);
+    return -1;
+  }
+
+  while (fgets (line, sizeof line, fp) != NULL) {
+    unsigned line_our_addr, line_our_port, line_peer_addr, line_peer_port;
+    int dummy0, dummy1, dummy2, dummy3, dummy4, dummy5, dummy6;
+    int line_uid;
+
+    if (sscanf (line, "%d:%08X:%04X %08X:%04X %02X %08X:%08X %02X:%08X %08X %d",
+                &dummy0,
+                &line_our_addr, &line_our_port,
+                &line_peer_addr, &line_peer_port,
+                &dummy1, &dummy2, &dummy3, &dummy4, &dummy5, &dummy6,
+                &line_uid) == 12) {
+      /* Note about /proc/net/tcp: local_address and rem_address are
+       * always in network byte order.  However the port part is
+       * always in host byte order.
+       *
+       * The sockname and peername that we got above are in network
+       * byte order.  So we have to byte swap the port but not the
+       * address part.
+       */
+      if (line_our_addr == our.sin_addr.s_addr &&
+          line_our_port == ntohs (our.sin_port) &&
+          line_peer_addr == peer.sin_addr.s_addr &&
+          line_peer_port == ntohs (peer.sin_port)) {
+        *rtn = line_uid;
+        fclose (fp);
+        return 0;
+      }
+    }
+  }
+
+  error (g, "check_peer_euid: no matching TCP connection found in /proc/net/tcp");
+  fclose (fp);
+  return -1;
+}
+
 static int
 shutdown_appliance (guestfs_h *g, int check_for_errors)
 {
diff --git a/src/launch.c b/src/launch.c
index 4333821..c77c229 100644
--- a/src/launch.c
+++ b/src/launch.c
@@ -316,7 +316,7 @@ guestfs__config (guestfs_h *g,
 
 char *
 guestfs___appliance_command_line (guestfs_h *g, const char *appliance_dev,
-                                  int flags)
+                                  int flags, const char *vmchannel)
 {
   char *term = getenv ("TERM");
   char *ret;
@@ -344,12 +344,14 @@ guestfs___appliance_command_line (guestfs_h *g, const char *appliance_dev,
      " cgroup_disable=memory"   /* saves us about 5 MB of RAM */
      " root=%s"                 /* root (appliance_dev) */
      " %s"                      /* selinux */
+     " %s"                      /* vmchannel */
      "%s"                       /* verbose */
      " TERM=%s"                 /* TERM environment variable */
      "%s%s",                    /* append */
      lpj_s,
      appliance_dev,
      g->selinux ? "selinux=1 enforcing=0" : "selinux=0",
+     vmchannel,
      g->verbose ? " guestfs_verbose=1" : "",
      term ? term : "linux",
      g->append ? " " : "", g->append ? g->append : "");
-- 
1.7.4.1