3cf992
From 6bf725c07c25b56937c494b6c7e83e6ca27ec54c Mon Sep 17 00:00:00 2001
3cf992
Message-Id: <6bf725c07c25b56937c494b6c7e83e6ca27ec54c@dist-git>
3cf992
From: Michal Privoznik <mprivozn@redhat.com>
3cf992
Date: Mon, 8 Mar 2021 12:57:31 +0100
3cf992
Subject: [PATCH] virdevmapper: Don't use libdevmapper to obtain dependencies
3cf992
MIME-Version: 1.0
3cf992
Content-Type: text/plain; charset=UTF-8
3cf992
Content-Transfer-Encoding: 8bit
3cf992
3cf992
CVE-2020-14339
3cf992
3cf992
When building domain's private /dev in a namespace, libdevmapper
3cf992
is consulted for getting full dependency tree of domain's disks.
3cf992
The reason is that for a multipath devices all dependent devices
3cf992
must be created in the namespace and allowed in CGroups.
3cf992
3cf992
However, this approach is very fragile as building of namespace
3cf992
happens in the forked off child process, after mass close of FDs
3cf992
and just before dropping privileges and execing QEMU. And it so
3cf992
happens that when calling libdevmapper APIs, one of them opens
3cf992
/dev/mapper/control and saves the FD into a global variable. The
3cf992
FD is kept open until the lib is unlinked or dm_lib_release() is
3cf992
called explicitly. We are doing neither.
3cf992
3cf992
However, the virDevMapperGetTargets() function is called also
3cf992
from libvirtd (when setting up CGroups) and thus has to be thread
3cf992
safe. Unfortunately, libdevmapper APIs are not thread safe (nor
3cf992
async signal safe) and thus we can't use them. Reimplement what
3cf992
libdevmapper would do using plain C (ioctl()-s, /proc/devices
3cf992
parsing, /dev/mapper dirwalking, and so on).
3cf992
3cf992
Fixes: a30078cb832646177defd256e77c632905f1e6d0
3cf992
Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1858260
3cf992
3cf992
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
3cf992
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
3cf992
(cherry picked from commit 22494556542c676d1b9e7f1c1f2ea13ac17e1e3e)
3cf992
3cf992
Conflicts:
3cf992
- po/POTFILES.in: This file doesn't exist, only POTFILES does.
3cf992
- src/util/virdevmapper.c: Couple of problems. Firstly,
3cf992
  03c532cf9711dd6ad35380455a77141ef7d492ab isn't backported, so
3cf992
  the header files include looks a bit different. Secondly,
3cf992
  91d88aaf23994691ea94973dd988ca5825c1e1b1 isn't backported, so
3cf992
  we have to stick with virAsprintf() instead of
3cf992
  g_strdup_printf(). Thirdly, virStrncpy() behaves a bit
3cf992
  differently: with the old code it returns pointer to copied
3cf992
  string or NULL, with the new code (cherry-picked one) it
3cf992
  already returns an integer, and on the top of that, specifying
3cf992
  -1 for the source len means strlen() is used under the hood
3cf992
  (see 6c0d0210cbcd5d647f0d882c07f077d444bc707d and
3cf992
  7d70a63b947e9a654a4e3fffa0ffa355f5549ec7 - neither is
3cf992
  backported).
3cf992
3cf992
In addition, I had to de-GLib the code. In RHEL-7 there is no
3cf992
g_autofree, G_STATIC_ASSERT, g_new0, g_strdup, g_strdup_printf,
3cf992
or g_steal_pointer. I had switch these to their old counterparts
3cf992
we used to use.
3cf992
3cf992
Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1933557
3cf992
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
3cf992
Message-Id: <3c7fd3071d8a9a58c8716f850880e3a0869243d3.1615203117.git.mprivozn@redhat.com>
3cf992
Reviewed-by: Ján Tomko <jtomko@redhat.com>
3cf992
---
3cf992
 po/POTFILES             |   1 +
3cf992
 src/util/virdevmapper.c | 315 +++++++++++++++++++++++++++++-----------
3cf992
 2 files changed, 228 insertions(+), 88 deletions(-)
3cf992
3cf992
diff --git a/po/POTFILES b/po/POTFILES
3cf992
index be2874487c..82a08da04a 100644
3cf992
--- a/po/POTFILES
3cf992
+++ b/po/POTFILES
3cf992
@@ -206,6 +206,7 @@ src/util/vircommand.c
3cf992
 src/util/virconf.c
3cf992
 src/util/vircrypto.c
3cf992
 src/util/virdbus.c
3cf992
+src/util/virdevmapper.c
3cf992
 src/util/virdnsmasq.c
3cf992
 src/util/virerror.c
3cf992
 src/util/virerror.h
3cf992
diff --git a/src/util/virdevmapper.c b/src/util/virdevmapper.c
3cf992
index 50d12ea0f1..ebf446d966 100644
3cf992
--- a/src/util/virdevmapper.c
3cf992
+++ b/src/util/virdevmapper.c
3cf992
@@ -23,40 +23,67 @@
3cf992
 
3cf992
 #include <config.h>
3cf992
 
3cf992
-#ifdef MAJOR_IN_MKDEV
3cf992
-# include <sys/mkdev.h>
3cf992
-#elif MAJOR_IN_SYSMACROS
3cf992
+#include "virdevmapper.h"
3cf992
+#include "internal.h"
3cf992
+
3cf992
+#ifdef __linux__
3cf992
 # include <sys/sysmacros.h>
3cf992
-#endif
3cf992
+# include <linux/dm-ioctl.h>
3cf992
+# include <sys/ioctl.h>
3cf992
+# include <sys/types.h>
3cf992
+# include <sys/stat.h>
3cf992
+# include <fcntl.h>
3cf992
 
3cf992
-#ifdef WITH_DEVMAPPER
3cf992
-# include <libdevmapper.h>
3cf992
-#endif
3cf992
+# include "virthread.h"
3cf992
+# include "viralloc.h"
3cf992
+# include "virstring.h"
3cf992
+# include "virfile.h"
3cf992
+
3cf992
+# define VIR_FROM_THIS VIR_FROM_STORAGE
3cf992
+
3cf992
+# define PROC_DEVICES "/proc/devices"
3cf992
+# define DM_NAME "device-mapper"
3cf992
+# define DEV_DM_DIR "/dev/" DM_DIR
3cf992
+# define CONTROL_PATH DEV_DM_DIR "/" DM_CONTROL_NODE
3cf992
+# define BUF_SIZE (16 * 1024)
3cf992
+
3cf992
+verify(BUF_SIZE > sizeof(struct dm_ioctl));
3cf992
+
3cf992
+static unsigned int virDMMajor;
3cf992
 
3cf992
-#include "virdevmapper.h"
3cf992
-#include "internal.h"
3cf992
-#include "virthread.h"
3cf992
-#include "viralloc.h"
3cf992
-#include "virstring.h"
3cf992
-
3cf992
-#ifdef WITH_DEVMAPPER
3cf992
-static void
3cf992
-virDevMapperDummyLogger(int level ATTRIBUTE_UNUSED,
3cf992
-                        const char *file ATTRIBUTE_UNUSED,
3cf992
-                        int line ATTRIBUTE_UNUSED,
3cf992
-                        int dm_errno ATTRIBUTE_UNUSED,
3cf992
-                        const char *fmt ATTRIBUTE_UNUSED,
3cf992
-                        ...)
3cf992
-{
3cf992
-    return;
3cf992
-}
3cf992
 
3cf992
 static int
3cf992
 virDevMapperOnceInit(void)
3cf992
 {
3cf992
-    /* Ideally, we would not need this. But libdevmapper prints
3cf992
-     * error messages to stderr by default. Sad but true. */
3cf992
-    dm_log_with_errno_init(virDevMapperDummyLogger);
3cf992
+    VIR_AUTOFREE(char *) buf = NULL;
3cf992
+    VIR_AUTOSTRINGLIST lines = NULL;
3cf992
+    size_t i;
3cf992
+
3cf992
+    if (virFileReadAll(PROC_DEVICES, BUF_SIZE, &buf) < 0)
3cf992
+        return -1;
3cf992
+
3cf992
+    lines = virStringSplit(buf, "\n", 0);
3cf992
+    if (!lines)
3cf992
+        return -1;
3cf992
+
3cf992
+    for (i = 0; lines[i]; i++) {
3cf992
+        VIR_AUTOFREE(char *) dev = NULL;
3cf992
+        unsigned int maj;
3cf992
+
3cf992
+        if (sscanf(lines[i], "%u %ms\n", &maj, &dev) == 2 &&
3cf992
+            STREQ(dev, DM_NAME)) {
3cf992
+            virDMMajor = maj;
3cf992
+            break;
3cf992
+        }
3cf992
+    }
3cf992
+
3cf992
+    if (!lines[i]) {
3cf992
+        virReportError(VIR_ERR_INTERNAL_ERROR,
3cf992
+                       _("Unable to find major for %s"),
3cf992
+                       DM_NAME);
3cf992
+        return -1;
3cf992
+    }
3cf992
+
3cf992
     return 0;
3cf992
 }
3cf992
 
3cf992
@@ -64,95 +91,200 @@ virDevMapperOnceInit(void)
3cf992
 VIR_ONCE_GLOBAL_INIT(virDevMapper)
3cf992
 
3cf992
 
3cf992
+static void *
3cf992
+virDMIoctl(int controlFD, int cmd, struct dm_ioctl *dm, char **buf)
3cf992
+{
3cf992
+    size_t bufsize = BUF_SIZE;
3cf992
+
3cf992
+ reread:
3cf992
+    if (VIR_ALLOC_N(*buf, bufsize) < 0)
3cf992
+        return NULL;
3cf992
+
3cf992
+    dm->version[0] = DM_VERSION_MAJOR;
3cf992
+    dm->version[1] = 0;
3cf992
+    dm->version[2] = 0;
3cf992
+    dm->data_size = bufsize;
3cf992
+    dm->data_start = sizeof(struct dm_ioctl);
3cf992
+
3cf992
+    memcpy(*buf, dm, sizeof(struct dm_ioctl));
3cf992
+
3cf992
+    if (ioctl(controlFD, cmd, *buf) < 0) {
3cf992
+        VIR_FREE(*buf);
3cf992
+        return NULL;
3cf992
+    }
3cf992
+
3cf992
+    memcpy(dm, *buf, sizeof(struct dm_ioctl));
3cf992
+
3cf992
+    if (dm->flags & DM_BUFFER_FULL_FLAG) {
3cf992
+        bufsize += BUF_SIZE;
3cf992
+        VIR_FREE(*buf);
3cf992
+        goto reread;
3cf992
+    }
3cf992
+
3cf992
+    return *buf + dm->data_start;
3cf992
+}
3cf992
+
3cf992
+
3cf992
 static int
3cf992
-virDevMapperGetTargetsImpl(const char *path,
3cf992
-                           char ***devPaths_ret,
3cf992
-                           unsigned int ttl)
3cf992
+virDMOpen(void)
3cf992
 {
3cf992
-    struct dm_task *dmt = NULL;
3cf992
-    struct dm_deps *deps;
3cf992
-    struct dm_info info;
3cf992
-    VIR_AUTOSTRINGLIST devPaths = NULL;
3cf992
-    size_t i;
3cf992
-    int ret = -1;
3cf992
+    VIR_AUTOCLOSE controlFD = -1;
3cf992
+    struct dm_ioctl dm;
3cf992
+    VIR_AUTOFREE(char *) tmp = NULL;
3cf992
+    int ret;
3cf992
 
3cf992
-    *devPaths_ret = NULL;
3cf992
+    memset(&dm, 0, sizeof(dm));
3cf992
 
3cf992
-    if (virDevMapperInitialize() < 0)
3cf992
-        return ret;
3cf992
+    if ((controlFD = open(CONTROL_PATH, O_RDWR)) < 0)
3cf992
+        return -1;
3cf992
 
3cf992
-    if (ttl == 0) {
3cf992
-        errno = ELOOP;
3cf992
+    if (!virDMIoctl(controlFD, DM_VERSION, &dm, &tmp)) {
3cf992
+        virReportSystemError(errno, "%s",
3cf992
+                             _("Unable to get device-mapper version"));
3cf992
+        return -1;
3cf992
+    }
3cf992
+
3cf992
+    if (dm.version[0] != DM_VERSION_MAJOR) {
3cf992
+        virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
3cf992
+                       _("Unsupported device-mapper version. Expected %d got %d"),
3cf992
+                       DM_VERSION_MAJOR, dm.version[0]);
3cf992
+        return -1;
3cf992
+    }
3cf992
+
3cf992
+    ret = controlFD;
3cf992
+    controlFD = -1;
3cf992
+    return ret;
3cf992
+}
3cf992
+
3cf992
+
3cf992
+static char *
3cf992
+virDMSanitizepath(const char *path)
3cf992
+{
3cf992
+    VIR_AUTOFREE(char *) dmDirPath = NULL;
3cf992
+    struct dirent *ent = NULL;
3cf992
+    struct stat sb[2];
3cf992
+    DIR *dh = NULL;
3cf992
+    const char *p;
3cf992
+    char *ret = NULL;
3cf992
+    int rc;
3cf992
+
3cf992
+    /* If a path is NOT provided then assume it's DM name */
3cf992
+    p = strrchr(path, '/');
3cf992
+
3cf992
+    if (!p) {
3cf992
+        ignore_value(VIR_STRDUP(ret, path));
3cf992
         return ret;
3cf992
+    } else {
3cf992
+        p++;
3cf992
     }
3cf992
 
3cf992
-    if (!virIsDevMapperDevice(path))
3cf992
-        return 0;
3cf992
+    /* It's a path. Check if the last component is DM name */
3cf992
+    if (stat(path, &sb[0]) < 0) {
3cf992
+        virReportError(errno,
3cf992
+                       _("Unable to stat %p"),
3cf992
+                       path);
3cf992
+        return NULL;
3cf992
+    }
3cf992
 
3cf992
-    if (!(dmt = dm_task_create(DM_DEVICE_DEPS))) {
3cf992
-        if (errno == ENOENT || errno == ENODEV) {
3cf992
-            /* It's okay. Kernel is probably built without
3cf992
-             * devmapper support. */
3cf992
-            ret = 0;
3cf992
-        }
3cf992
+    if (virAsprintf(&dmDirPath, DEV_DM_DIR "/%s", p) < 0)
3cf992
+        return NULL;
3cf992
+
3cf992
+    if (stat(dmDirPath, &sb[1]) == 0 &&
3cf992
+        sb[0].st_rdev == sb[1].st_rdev) {
3cf992
+        ignore_value(VIR_STRDUP(ret, p));
3cf992
         return ret;
3cf992
     }
3cf992
 
3cf992
-    if (!dm_task_set_name(dmt, path)) {
3cf992
-        if (errno == ENOENT) {
3cf992
-            /* It's okay, @path is not managed by devmapper =>
3cf992
-             * not a devmapper device. */
3cf992
-            ret = 0;
3cf992
+    /* The last component of @path wasn't DM name. Let's check if
3cf992
+     * there's a device under /dev/mapper/ with the same rdev. */
3cf992
+    if (virDirOpen(&dh, DEV_DM_DIR) < 0)
3cf992
+        return NULL;
3cf992
+
3cf992
+    while ((rc = virDirRead(dh, &ent, DEV_DM_DIR)) > 0) {
3cf992
+        VIR_AUTOFREE(char *) tmp = NULL;
3cf992
+
3cf992
+        if (virAsprintf(&tmp, DEV_DM_DIR "/%s", ent->d_name) < 0)
3cf992
+            return NULL;
3cf992
+
3cf992
+        if (stat(tmp, &sb[1]) == 0 &&
3cf992
+            sb[0].st_rdev == sb[0].st_rdev) {
3cf992
+            VIR_STEAL_PTR(ret, tmp);
3cf992
+            break;
3cf992
         }
3cf992
-        goto cleanup;
3cf992
     }
3cf992
 
3cf992
-    dm_task_no_open_count(dmt);
3cf992
+    virDirClose(&dh);
3cf992
+    return ret;
3cf992
+}
3cf992
 
3cf992
-    if (!dm_task_run(dmt)) {
3cf992
-        if (errno == ENXIO) {
3cf992
-            /* If @path = "/dev/mapper/control" ENXIO is returned. */
3cf992
-            ret = 0;
3cf992
-        }
3cf992
-        goto cleanup;
3cf992
+
3cf992
+static int
3cf992
+virDevMapperGetTargetsImpl(int controlFD,
3cf992
+                           const char *path,
3cf992
+                           char ***devPaths_ret,
3cf992
+                           unsigned int ttl)
3cf992
+{
3cf992
+    VIR_AUTOFREE(char *) sanitizedPath = NULL;
3cf992
+    VIR_AUTOFREE(char *) buf = NULL;
3cf992
+    struct dm_ioctl dm;
3cf992
+    struct dm_target_deps *deps = NULL;
3cf992
+    VIR_AUTOSTRINGLIST devPaths = NULL;
3cf992
+    size_t i;
3cf992
+
3cf992
+    memset(&dm, 0, sizeof(dm));
3cf992
+    *devPaths_ret = NULL;
3cf992
+
3cf992
+    if (ttl == 0) {
3cf992
+        errno = ELOOP;
3cf992
+        return -1;
3cf992
     }
3cf992
 
3cf992
-    if (!dm_task_get_info(dmt, &info))
3cf992
-        goto cleanup;
3cf992
+    if (!virIsDevMapperDevice(path))
3cf992
+        return 0;
3cf992
+
3cf992
+    if (!(sanitizedPath = virDMSanitizepath(path)))
3cf992
+        return 0;
3cf992
 
3cf992
-    if (!info.exists) {
3cf992
-        ret = 0;
3cf992
-        goto cleanup;
3cf992
+    if (virStrcpy(dm.name, sanitizedPath, DM_TABLE_DEPS) == NULL) {
3cf992
+        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
3cf992
+                       _("Resolved device mapper name too long"));
3cf992
+        return -1;
3cf992
     }
3cf992
 
3cf992
-    if (!(deps = dm_task_get_deps(dmt)))
3cf992
-        goto cleanup;
3cf992
+    deps = virDMIoctl(controlFD, DM_TABLE_DEPS, &dm, &buf;;
3cf992
+    if (!deps) {
3cf992
+        if (errno == ENXIO)
3cf992
+            return 0;
3cf992
+
3cf992
+        virReportSystemError(errno,
3cf992
+                             _("Unable to query dependencies for %s"),
3cf992
+                             path);
3cf992
+        return -1;
3cf992
+    }
3cf992
 
3cf992
     if (VIR_ALLOC_N_QUIET(devPaths, deps->count + 1) < 0)
3cf992
-        goto cleanup;
3cf992
+        return -1;
3cf992
 
3cf992
     for (i = 0; i < deps->count; i++) {
3cf992
         if (virAsprintfQuiet(&devPaths[i], "/dev/block/%u:%u",
3cf992
-                             major(deps->device[i]),
3cf992
-                             minor(deps->device[i])) < 0)
3cf992
-            goto cleanup;
3cf992
+                             major(deps->dev[i]),
3cf992
+                             minor(deps->dev[i])) < 0) {
3cf992
+            return -1;
3cf992
+        }
3cf992
     }
3cf992
 
3cf992
     for (i = 0; i < deps->count; i++) {
3cf992
         VIR_AUTOSTRINGLIST tmpPaths = NULL;
3cf992
 
3cf992
-        if (virDevMapperGetTargetsImpl(devPaths[i], &tmpPaths, ttl - 1) < 0)
3cf992
-            goto cleanup;
3cf992
+        if (virDevMapperGetTargetsImpl(controlFD, devPaths[i], &tmpPaths, ttl - 1) < 0)
3cf992
+            return -1;
3cf992
 
3cf992
         if (virStringListMerge(&devPaths, &tmpPaths) < 0)
3cf992
-            goto cleanup;
3cf992
+            return -1;
3cf992
     }
3cf992
 
3cf992
     VIR_STEAL_PTR(*devPaths_ret, devPaths);
3cf992
-    ret = 0;
3cf992
- cleanup:
3cf992
-    dm_task_destroy(dmt);
3cf992
-    return ret;
3cf992
+    return 0;
3cf992
 }
3cf992
 
3cf992
 
3cf992
@@ -171,9 +303,6 @@ virDevMapperGetTargetsImpl(const char *path,
3cf992
  * If @path consists of yet another devmapper targets these are
3cf992
  * consulted recursively.
3cf992
  *
3cf992
- * If we don't have permissions to talk to kernel, -1 is returned
3cf992
- * and errno is set to EBADF.
3cf992
- *
3cf992
  * Returns 0 on success,
3cf992
  *        -1 otherwise (with errno set, no libvirt error is
3cf992
  *        reported)
3cf992
@@ -182,13 +311,20 @@ int
3cf992
 virDevMapperGetTargets(const char *path,
3cf992
                        char ***devPaths)
3cf992
 {
3cf992
+    VIR_AUTOCLOSE controlFD = -1;
3cf992
     const unsigned int ttl = 32;
3cf992
 
3cf992
     /* Arbitrary limit on recursion level. A devmapper target can
3cf992
      * consist of devices or yet another targets. If that's the
3cf992
      * case, we have to stop recursion somewhere. */
3cf992
 
3cf992
-    return virDevMapperGetTargetsImpl(path, devPaths, ttl);
3cf992
+    if (virDevMapperInitialize() < 0)
3cf992
+        return -1;
3cf992
+
3cf992
+    if ((controlFD = virDMOpen()) < 0)
3cf992
+        return -1;
3cf992
+
3cf992
+    return virDevMapperGetTargetsImpl(controlFD, path, devPaths, ttl);
3cf992
 }
3cf992
 
3cf992
 
3cf992
@@ -197,15 +333,18 @@ virIsDevMapperDevice(const char *dev_name)
3cf992
 {
3cf992
     struct stat buf;
3cf992
 
3cf992
+    if (virDevMapperInitialize() < 0)
3cf992
+        return false;
3cf992
+
3cf992
     if (!stat(dev_name, &buf) &&
3cf992
         S_ISBLK(buf.st_mode) &&
3cf992
-        dm_is_dm_major(major(buf.st_rdev)))
3cf992
-            return true;
3cf992
+        major(buf.st_rdev) == virDMMajor)
3cf992
+        return true;
3cf992
 
3cf992
     return false;
3cf992
 }
3cf992
 
3cf992
-#else /* ! WITH_DEVMAPPER */
3cf992
+#else /* !defined(__linux__)  */
3cf992
 
3cf992
 int
3cf992
 virDevMapperGetTargets(const char *path ATTRIBUTE_UNUSED,
3cf992
@@ -221,4 +360,4 @@ virIsDevMapperDevice(const char *dev_name G_GNUC_UNUSED)
3cf992
 {
3cf992
     return false;
3cf992
 }
3cf992
-#endif /* ! WITH_DEVMAPPER */
3cf992
+#endif /* ! defined(__linux__) */
3cf992
-- 
3cf992
2.31.0
3cf992