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