render / rpms / libvirt

Forked from rpms/libvirt 4 months ago
Clone
79b470
From 7ba9a0bbdf30984c13e7719e0646fffc7539f853 Mon Sep 17 00:00:00 2001
79b470
Message-Id: <7ba9a0bbdf30984c13e7719e0646fffc7539f853@dist-git>
79b470
From: Michal Privoznik <mprivozn@redhat.com>
79b470
Date: Mon, 27 Jul 2020 12:36:52 +0200
79b470
Subject: [PATCH] virdevmapper: Don't use libdevmapper to obtain dependencies
79b470
MIME-Version: 1.0
79b470
Content-Type: text/plain; charset=UTF-8
79b470
Content-Transfer-Encoding: 8bit
79b470
79b470
CVE-2020-14339
79b470
79b470
When building domain's private /dev in a namespace, libdevmapper
79b470
is consulted for getting full dependency tree of domain's disks.
79b470
The reason is that for a multipath devices all dependent devices
79b470
must be created in the namespace and allowed in CGroups.
79b470
79b470
However, this approach is very fragile as building of namespace
79b470
happens in the forked off child process, after mass close of FDs
79b470
and just before dropping privileges and execing QEMU. And it so
79b470
happens that when calling libdevmapper APIs, one of them opens
79b470
/dev/mapper/control and saves the FD into a global variable. The
79b470
FD is kept open until the lib is unlinked or dm_lib_release() is
79b470
called explicitly. We are doing neither.
79b470
79b470
However, the virDevMapperGetTargets() function is called also
79b470
from libvirtd (when setting up CGroups) and thus has to be thread
79b470
safe. Unfortunately, libdevmapper APIs are not thread safe (nor
79b470
async signal safe) and thus we can't use them. Reimplement what
79b470
libdevmapper would do using plain C (ioctl()-s, /proc/devices
79b470
parsing, /dev/mapper dirwalking, and so on).
79b470
79b470
Fixes: a30078cb832646177defd256e77c632905f1e6d0
79b470
Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1858260
79b470
79b470
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
79b470
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
79b470
(cherry picked from commit 22494556542c676d1b9e7f1c1f2ea13ac17e1e3e)
79b470
79b470
https://bugzilla.redhat.com/show_bug.cgi?id=1860421
79b470
79b470
Conflicts:
79b470
- po/POTFILES.in - In c6a0d3ff8b4ead3b1f38a40668df65f152cc2f32
79b470
  (po: change the format of POTFILES.in) we've changed the format
79b470
  of the file and haven't backported it, yet.
79b470
79b470
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
79b470
Message-Id: <bf0ad2c93723aab2579368496aab4289701a94d8.1595846084.git.mprivozn@redhat.com>
79b470
Reviewed-by: Jiri Denemark <jdenemar@redhat.com>
79b470
---
79b470
 po/POTFILES.in          |   1 +
79b470
 src/util/virdevmapper.c | 300 +++++++++++++++++++++++++++++-----------
79b470
 2 files changed, 217 insertions(+), 84 deletions(-)
79b470
79b470
diff --git a/po/POTFILES.in b/po/POTFILES.in
79b470
index 29984042f4..49348292eb 100644
79b470
--- a/po/POTFILES.in
79b470
+++ b/po/POTFILES.in
79b470
@@ -238,6 +238,7 @@
79b470
 @SRCDIR@/src/util/vircrypto.c
79b470
 @SRCDIR@/src/util/virdbus.c
79b470
 @SRCDIR@/src/util/virdnsmasq.c
79b470
+@SRCDIR@/src/util/virdevmapper.c
79b470
 @SRCDIR@/src/util/virerror.c
79b470
 @SRCDIR@/src/util/virerror.h
79b470
 @SRCDIR@/src/util/virevent.c
79b470
diff --git a/src/util/virdevmapper.c b/src/util/virdevmapper.c
79b470
index 44c4731fb4..a471504176 100644
79b470
--- a/src/util/virdevmapper.c
79b470
+++ b/src/util/virdevmapper.c
79b470
@@ -20,38 +20,67 @@
79b470
 
79b470
 #include <config.h>
79b470
 
79b470
+#include "virdevmapper.h"
79b470
+#include "internal.h"
79b470
+
79b470
 #ifdef __linux__
79b470
 # include <sys/sysmacros.h>
79b470
-#endif
79b470
+# include <linux/dm-ioctl.h>
79b470
+# include <sys/ioctl.h>
79b470
+# include <sys/types.h>
79b470
+# include <sys/stat.h>
79b470
+# include <fcntl.h>
79b470
 
79b470
-#ifdef WITH_DEVMAPPER
79b470
-# include <libdevmapper.h>
79b470
-#endif
79b470
+# include "virthread.h"
79b470
+# include "viralloc.h"
79b470
+# include "virstring.h"
79b470
+# include "virfile.h"
79b470
+
79b470
+# define VIR_FROM_THIS VIR_FROM_STORAGE
79b470
+
79b470
+# define PROC_DEVICES "/proc/devices"
79b470
+# define DM_NAME "device-mapper"
79b470
+# define DEV_DM_DIR "/dev/" DM_DIR
79b470
+# define CONTROL_PATH DEV_DM_DIR "/" DM_CONTROL_NODE
79b470
+# define BUF_SIZE (16 * 1024)
79b470
+
79b470
+G_STATIC_ASSERT(BUF_SIZE > sizeof(struct dm_ioctl));
79b470
+
79b470
+static unsigned int virDMMajor;
79b470
 
79b470
-#include "virdevmapper.h"
79b470
-#include "internal.h"
79b470
-#include "virthread.h"
79b470
-#include "viralloc.h"
79b470
-#include "virstring.h"
79b470
-
79b470
-#ifdef WITH_DEVMAPPER
79b470
-static void
79b470
-virDevMapperDummyLogger(int level G_GNUC_UNUSED,
79b470
-                        const char *file G_GNUC_UNUSED,
79b470
-                        int line G_GNUC_UNUSED,
79b470
-                        int dm_errno G_GNUC_UNUSED,
79b470
-                        const char *fmt G_GNUC_UNUSED,
79b470
-                        ...)
79b470
-{
79b470
-    return;
79b470
-}
79b470
 
79b470
 static int
79b470
 virDevMapperOnceInit(void)
79b470
 {
79b470
-    /* Ideally, we would not need this. But libdevmapper prints
79b470
-     * error messages to stderr by default. Sad but true. */
79b470
-    dm_log_with_errno_init(virDevMapperDummyLogger);
79b470
+    g_autofree char *buf = NULL;
79b470
+    VIR_AUTOSTRINGLIST lines = NULL;
79b470
+    size_t i;
79b470
+
79b470
+    if (virFileReadAll(PROC_DEVICES, BUF_SIZE, &buf) < 0)
79b470
+        return -1;
79b470
+
79b470
+    lines = virStringSplit(buf, "\n", 0);
79b470
+    if (!lines)
79b470
+        return -1;
79b470
+
79b470
+    for (i = 0; lines[i]; i++) {
79b470
+        g_autofree char *dev = NULL;
79b470
+        unsigned int maj;
79b470
+
79b470
+        if (sscanf(lines[i], "%u %ms\n", &maj, &dev) == 2 &&
79b470
+            STREQ(dev, DM_NAME)) {
79b470
+            virDMMajor = maj;
79b470
+            break;
79b470
+        }
79b470
+    }
79b470
+
79b470
+    if (!lines[i]) {
79b470
+        virReportError(VIR_ERR_INTERNAL_ERROR,
79b470
+                       _("Unable to find major for %s"),
79b470
+                       DM_NAME);
79b470
+        return -1;
79b470
+    }
79b470
+
79b470
     return 0;
79b470
 }
79b470
 
79b470
@@ -59,94 +88,190 @@ virDevMapperOnceInit(void)
79b470
 VIR_ONCE_GLOBAL_INIT(virDevMapper);
79b470
 
79b470
 
79b470
+static void *
79b470
+virDMIoctl(int controlFD, int cmd, struct dm_ioctl *dm, char **buf)
79b470
+{
79b470
+    size_t bufsize = BUF_SIZE;
79b470
+
79b470
+ reread:
79b470
+    *buf = g_new0(char, bufsize);
79b470
+
79b470
+    dm->version[0] = DM_VERSION_MAJOR;
79b470
+    dm->version[1] = 0;
79b470
+    dm->version[2] = 0;
79b470
+    dm->data_size = bufsize;
79b470
+    dm->data_start = sizeof(struct dm_ioctl);
79b470
+
79b470
+    memcpy(*buf, dm, sizeof(struct dm_ioctl));
79b470
+
79b470
+    if (ioctl(controlFD, cmd, *buf) < 0) {
79b470
+        VIR_FREE(*buf);
79b470
+        return NULL;
79b470
+    }
79b470
+
79b470
+    memcpy(dm, *buf, sizeof(struct dm_ioctl));
79b470
+
79b470
+    if (dm->flags & DM_BUFFER_FULL_FLAG) {
79b470
+        bufsize += BUF_SIZE;
79b470
+        VIR_FREE(*buf);
79b470
+        goto reread;
79b470
+    }
79b470
+
79b470
+    return *buf + dm->data_start;
79b470
+}
79b470
+
79b470
+
79b470
 static int
79b470
-virDevMapperGetTargetsImpl(const char *path,
79b470
+virDMOpen(void)
79b470
+{
79b470
+    VIR_AUTOCLOSE controlFD = -1;
79b470
+    struct dm_ioctl dm;
79b470
+    g_autofree char *tmp = NULL;
79b470
+    int ret;
79b470
+
79b470
+    memset(&dm, 0, sizeof(dm));
79b470
+
79b470
+    if ((controlFD = open(CONTROL_PATH, O_RDWR)) < 0)
79b470
+        return -1;
79b470
+
79b470
+    if (!virDMIoctl(controlFD, DM_VERSION, &dm, &tmp)) {
79b470
+        virReportSystemError(errno, "%s",
79b470
+                             _("Unable to get device-mapper version"));
79b470
+        return -1;
79b470
+    }
79b470
+
79b470
+    if (dm.version[0] != DM_VERSION_MAJOR) {
79b470
+        virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
79b470
+                       _("Unsupported device-mapper version. Expected %d got %d"),
79b470
+                       DM_VERSION_MAJOR, dm.version[0]);
79b470
+        return -1;
79b470
+    }
79b470
+
79b470
+    ret = controlFD;
79b470
+    controlFD = -1;
79b470
+    return ret;
79b470
+}
79b470
+
79b470
+
79b470
+static char *
79b470
+virDMSanitizepath(const char *path)
79b470
+{
79b470
+    g_autofree char *dmDirPath = NULL;
79b470
+    struct dirent *ent = NULL;
79b470
+    struct stat sb[2];
79b470
+    DIR *dh = NULL;
79b470
+    const char *p;
79b470
+    char *ret = NULL;
79b470
+    int rc;
79b470
+
79b470
+    /* If a path is NOT provided then assume it's DM name */
79b470
+    p = strrchr(path, '/');
79b470
+
79b470
+    if (!p)
79b470
+        return g_strdup(path);
79b470
+    else
79b470
+        p++;
79b470
+
79b470
+    /* It's a path. Check if the last component is DM name */
79b470
+    if (stat(path, &sb[0]) < 0) {
79b470
+        virReportError(errno,
79b470
+                       _("Unable to stat %p"),
79b470
+                       path);
79b470
+        return NULL;
79b470
+    }
79b470
+
79b470
+    dmDirPath = g_strdup_printf(DEV_DM_DIR "/%s", p);
79b470
+
79b470
+    if (stat(dmDirPath, &sb[1]) == 0 &&
79b470
+        sb[0].st_rdev == sb[1].st_rdev) {
79b470
+        return g_strdup(p);
79b470
+    }
79b470
+
79b470
+    /* The last component of @path wasn't DM name. Let's check if
79b470
+     * there's a device under /dev/mapper/ with the same rdev. */
79b470
+    if (virDirOpen(&dh, DEV_DM_DIR) < 0)
79b470
+        return NULL;
79b470
+
79b470
+    while ((rc = virDirRead(dh, &ent, DEV_DM_DIR)) > 0) {
79b470
+        g_autofree char *tmp = g_strdup_printf(DEV_DM_DIR "/%s", ent->d_name);
79b470
+
79b470
+        if (stat(tmp, &sb[1]) == 0 &&
79b470
+            sb[0].st_rdev == sb[0].st_rdev) {
79b470
+            ret = g_steal_pointer(&tmp);
79b470
+            break;
79b470
+        }
79b470
+    }
79b470
+
79b470
+    virDirClose(&dh);
79b470
+    return ret;
79b470
+}
79b470
+
79b470
+
79b470
+static int
79b470
+virDevMapperGetTargetsImpl(int controlFD,
79b470
+                           const char *path,
79b470
                            char ***devPaths_ret,
79b470
                            unsigned int ttl)
79b470
 {
79b470
-    struct dm_task *dmt = NULL;
79b470
-    struct dm_deps *deps;
79b470
-    struct dm_info info;
79b470
+    g_autofree char *sanitizedPath = NULL;
79b470
+    g_autofree char *buf = NULL;
79b470
+    struct dm_ioctl dm;
79b470
+    struct dm_target_deps *deps = NULL;
79b470
     VIR_AUTOSTRINGLIST devPaths = NULL;
79b470
     size_t i;
79b470
-    int ret = -1;
79b470
 
79b470
+    memset(&dm, 0, sizeof(dm));
79b470
     *devPaths_ret = NULL;
79b470
 
79b470
-    if (virDevMapperInitialize() < 0)
79b470
-        return ret;
79b470
-
79b470
     if (ttl == 0) {
79b470
         errno = ELOOP;
79b470
-        return ret;
79b470
+        return -1;
79b470
     }
79b470
 
79b470
     if (!virIsDevMapperDevice(path))
79b470
         return 0;
79b470
 
79b470
-    if (!(dmt = dm_task_create(DM_DEVICE_DEPS))) {
79b470
-        if (errno == ENOENT || errno == ENODEV) {
79b470
-            /* It's okay. Kernel is probably built without
79b470
-             * devmapper support. */
79b470
-            ret = 0;
79b470
-        }
79b470
-        return ret;
79b470
-    }
79b470
-
79b470
-    if (!dm_task_set_name(dmt, path)) {
79b470
-        if (errno == ENOENT) {
79b470
-            /* It's okay, @path is not managed by devmapper =>
79b470
-             * not a devmapper device. */
79b470
-            ret = 0;
79b470
-        }
79b470
-        goto cleanup;
79b470
-    }
79b470
-
79b470
-    dm_task_no_open_count(dmt);
79b470
+    if (!(sanitizedPath = virDMSanitizepath(path)))
79b470
+        return 0;
79b470
 
79b470
-    if (!dm_task_run(dmt)) {
79b470
-        if (errno == ENXIO) {
79b470
-            /* If @path = "/dev/mapper/control" ENXIO is returned. */
79b470
-            ret = 0;
79b470
-        }
79b470
-        goto cleanup;
79b470
+    if (virStrncpy(dm.name, sanitizedPath, -1, DM_TABLE_DEPS) < 0) {
79b470
+        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
79b470
+                       _("Resolved device mapper name too long"));
79b470
+        return -1;
79b470
     }
79b470
 
79b470
-    if (!dm_task_get_info(dmt, &info))
79b470
-        goto cleanup;
79b470
+    deps = virDMIoctl(controlFD, DM_TABLE_DEPS, &dm, &buf;;
79b470
+    if (!deps) {
79b470
+        if (errno == ENXIO)
79b470
+            return 0;
79b470
 
79b470
-    if (!info.exists) {
79b470
-        ret = 0;
79b470
-        goto cleanup;
79b470
+        virReportSystemError(errno,
79b470
+                             _("Unable to query dependencies for %s"),
79b470
+                             path);
79b470
+        return -1;
79b470
     }
79b470
 
79b470
-    if (!(deps = dm_task_get_deps(dmt)))
79b470
-        goto cleanup;
79b470
-
79b470
     if (VIR_ALLOC_N_QUIET(devPaths, deps->count + 1) < 0)
79b470
-        goto cleanup;
79b470
+        return -1;
79b470
 
79b470
     for (i = 0; i < deps->count; i++) {
79b470
         devPaths[i] = g_strdup_printf("/dev/block/%u:%u",
79b470
-                                      major(deps->device[i]),
79b470
-                                      minor(deps->device[i]));
79b470
+                                      major(deps->dev[i]),
79b470
+                                      minor(deps->dev[i]));
79b470
     }
79b470
 
79b470
     for (i = 0; i < deps->count; i++) {
79b470
         VIR_AUTOSTRINGLIST tmpPaths = NULL;
79b470
 
79b470
-        if (virDevMapperGetTargetsImpl(devPaths[i], &tmpPaths, ttl - 1) < 0)
79b470
-            goto cleanup;
79b470
+        if (virDevMapperGetTargetsImpl(controlFD, devPaths[i], &tmpPaths, ttl - 1) < 0)
79b470
+            return -1;
79b470
 
79b470
         if (virStringListMerge(&devPaths, &tmpPaths) < 0)
79b470
-            goto cleanup;
79b470
+            return -1;
79b470
     }
79b470
 
79b470
     *devPaths_ret = g_steal_pointer(&devPaths);
79b470
-    ret = 0;
79b470
- cleanup:
79b470
-    dm_task_destroy(dmt);
79b470
-    return ret;
79b470
+    return 0;
79b470
 }
79b470
 
79b470
 
79b470
@@ -165,9 +290,6 @@ virDevMapperGetTargetsImpl(const char *path,
79b470
  * If @path consists of yet another devmapper targets these are
79b470
  * consulted recursively.
79b470
  *
79b470
- * If we don't have permissions to talk to kernel, -1 is returned
79b470
- * and errno is set to EBADF.
79b470
- *
79b470
  * Returns 0 on success,
79b470
  *        -1 otherwise (with errno set, no libvirt error is
79b470
  *        reported)
79b470
@@ -176,13 +298,20 @@ int
79b470
 virDevMapperGetTargets(const char *path,
79b470
                        char ***devPaths)
79b470
 {
79b470
+    VIR_AUTOCLOSE controlFD = -1;
79b470
     const unsigned int ttl = 32;
79b470
 
79b470
     /* Arbitrary limit on recursion level. A devmapper target can
79b470
      * consist of devices or yet another targets. If that's the
79b470
      * case, we have to stop recursion somewhere. */
79b470
 
79b470
-    return virDevMapperGetTargetsImpl(path, devPaths, ttl);
79b470
+    if (virDevMapperInitialize() < 0)
79b470
+        return -1;
79b470
+
79b470
+    if ((controlFD = virDMOpen()) < 0)
79b470
+        return -1;
79b470
+
79b470
+    return virDevMapperGetTargetsImpl(controlFD, path, devPaths, ttl);
79b470
 }
79b470
 
79b470
 
79b470
@@ -191,15 +320,18 @@ virIsDevMapperDevice(const char *dev_name)
79b470
 {
79b470
     struct stat buf;
79b470
 
79b470
+    if (virDevMapperInitialize() < 0)
79b470
+        return false;
79b470
+
79b470
     if (!stat(dev_name, &buf) &&
79b470
         S_ISBLK(buf.st_mode) &&
79b470
-        dm_is_dm_major(major(buf.st_rdev)))
79b470
-            return true;
79b470
+        major(buf.st_rdev) == virDMMajor)
79b470
+        return true;
79b470
 
79b470
     return false;
79b470
 }
79b470
 
79b470
-#else /* ! WITH_DEVMAPPER */
79b470
+#else /* !defined(__linux__)  */
79b470
 
79b470
 int
79b470
 virDevMapperGetTargets(const char *path G_GNUC_UNUSED,
79b470
@@ -215,4 +347,4 @@ virIsDevMapperDevice(const char *dev_name G_GNUC_UNUSED)
79b470
 {
79b470
     return false;
79b470
 }
79b470
-#endif /* ! WITH_DEVMAPPER */
79b470
+#endif /* ! defined(__linux__) */
79b470
-- 
79b470
2.28.0
79b470