mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-10-31 18:33:11 +00:00
f711fa9ad0
Introduced by commit <22494556542c676d1b9e7f1c1f2ea13ac17e1e3e> which fixed a CVE. If the @path passed to virDMSanitizepath() is not a DM name or not a path to DM name this function could return incorrect sanitized path as it would always be the first device under /dev/mapper/. Signed-off-by: Pavel Hrdina <phrdina@redhat.com> Reviewed-by: Peter Krempa <pkrempa@redhat.com>
361 lines
8.7 KiB
C
361 lines
8.7 KiB
C
/*
|
|
* virdevmapper.c: Functions for handling device mapper
|
|
*
|
|
* Copyright (C) 2018 Red Hat, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include "virdevmapper.h"
|
|
#include "internal.h"
|
|
|
|
#ifdef __linux__
|
|
# include <sys/sysmacros.h>
|
|
# include <linux/dm-ioctl.h>
|
|
# include <sys/ioctl.h>
|
|
# include <sys/types.h>
|
|
# include <sys/stat.h>
|
|
# include <fcntl.h>
|
|
|
|
# include "virthread.h"
|
|
# include "viralloc.h"
|
|
# include "virstring.h"
|
|
# include "virfile.h"
|
|
# include "virlog.h"
|
|
|
|
# define VIR_FROM_THIS VIR_FROM_STORAGE
|
|
|
|
VIR_LOG_INIT("util.virdevmapper");
|
|
|
|
# define PROC_DEVICES "/proc/devices"
|
|
# define DM_NAME "device-mapper"
|
|
# define DEV_DM_DIR "/dev/" DM_DIR
|
|
# define CONTROL_PATH DEV_DM_DIR "/" DM_CONTROL_NODE
|
|
# define BUF_SIZE (16 * 1024)
|
|
|
|
G_STATIC_ASSERT(BUF_SIZE > sizeof(struct dm_ioctl));
|
|
|
|
|
|
static int
|
|
virDevMapperGetMajor(unsigned int *major)
|
|
{
|
|
g_autofree char *buf = NULL;
|
|
VIR_AUTOSTRINGLIST lines = NULL;
|
|
size_t i;
|
|
|
|
if (!virFileExists(CONTROL_PATH))
|
|
return -2;
|
|
|
|
if (virFileReadAll(PROC_DEVICES, BUF_SIZE, &buf) < 0)
|
|
return -1;
|
|
|
|
lines = virStringSplit(buf, "\n", 0);
|
|
if (!lines)
|
|
return -1;
|
|
|
|
for (i = 0; lines[i]; i++) {
|
|
g_autofree char *dev = NULL;
|
|
unsigned int maj;
|
|
|
|
if (sscanf(lines[i], "%u %ms\n", &maj, &dev) == 2 &&
|
|
STREQ(dev, DM_NAME)) {
|
|
*major = maj;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!lines[i]) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to find major for %s"),
|
|
DM_NAME);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void *
|
|
virDMIoctl(int controlFD, int cmd, struct dm_ioctl *dm, char **buf)
|
|
{
|
|
size_t bufsize = BUF_SIZE;
|
|
|
|
reread:
|
|
*buf = g_new0(char, bufsize);
|
|
|
|
dm->version[0] = DM_VERSION_MAJOR;
|
|
dm->version[1] = 0;
|
|
dm->version[2] = 0;
|
|
dm->data_size = bufsize;
|
|
dm->data_start = sizeof(struct dm_ioctl);
|
|
|
|
memcpy(*buf, dm, sizeof(struct dm_ioctl));
|
|
|
|
if (ioctl(controlFD, cmd, *buf) < 0) {
|
|
VIR_FREE(*buf);
|
|
return NULL;
|
|
}
|
|
|
|
memcpy(dm, *buf, sizeof(struct dm_ioctl));
|
|
|
|
if (dm->flags & DM_BUFFER_FULL_FLAG) {
|
|
bufsize += BUF_SIZE;
|
|
VIR_FREE(*buf);
|
|
goto reread;
|
|
}
|
|
|
|
return *buf + dm->data_start;
|
|
}
|
|
|
|
|
|
static int
|
|
virDMOpen(void)
|
|
{
|
|
VIR_AUTOCLOSE controlFD = -1;
|
|
struct dm_ioctl dm;
|
|
g_autofree char *tmp = NULL;
|
|
int ret;
|
|
|
|
memset(&dm, 0, sizeof(dm));
|
|
|
|
if ((controlFD = open(CONTROL_PATH, O_RDWR)) < 0) {
|
|
/* We can't talk to devmapper. Produce a warning and let
|
|
* the caller decide what to do next. */
|
|
if (errno == ENOENT) {
|
|
VIR_DEBUG("device mapper not available");
|
|
} else {
|
|
VIR_WARN("unable to open %s: %s",
|
|
CONTROL_PATH, g_strerror(errno));
|
|
}
|
|
return -2;
|
|
}
|
|
|
|
if (!virDMIoctl(controlFD, DM_VERSION, &dm, &tmp)) {
|
|
virReportSystemError(errno, "%s",
|
|
_("Unable to get device-mapper version"));
|
|
return -1;
|
|
}
|
|
|
|
if (dm.version[0] != DM_VERSION_MAJOR) {
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
|
|
_("Unsupported device-mapper version. Expected %d got %d"),
|
|
DM_VERSION_MAJOR, dm.version[0]);
|
|
return -1;
|
|
}
|
|
|
|
ret = controlFD;
|
|
controlFD = -1;
|
|
return ret;
|
|
}
|
|
|
|
|
|
static char *
|
|
virDMSanitizepath(const char *path)
|
|
{
|
|
g_autofree char *dmDirPath = NULL;
|
|
struct dirent *ent = NULL;
|
|
struct stat sb[2];
|
|
g_autoptr(DIR) dh = NULL;
|
|
const char *p;
|
|
|
|
/* If a path is NOT provided then assume it's DM name */
|
|
p = strrchr(path, '/');
|
|
|
|
if (!p)
|
|
return g_strdup(path);
|
|
else
|
|
p++;
|
|
|
|
/* It's a path. Check if the last component is DM name */
|
|
if (stat(path, &sb[0]) < 0) {
|
|
virReportError(errno,
|
|
_("Unable to stat %p"),
|
|
path);
|
|
return NULL;
|
|
}
|
|
|
|
dmDirPath = g_strdup_printf(DEV_DM_DIR "/%s", p);
|
|
|
|
if (stat(dmDirPath, &sb[1]) == 0 &&
|
|
sb[0].st_rdev == sb[1].st_rdev) {
|
|
return g_strdup(p);
|
|
}
|
|
|
|
/* The last component of @path wasn't DM name. Let's check if
|
|
* there's a device under /dev/mapper/ with the same rdev. */
|
|
if (virDirOpen(&dh, DEV_DM_DIR) < 0)
|
|
return NULL;
|
|
|
|
while (virDirRead(dh, &ent, DEV_DM_DIR) > 0) {
|
|
g_autofree char *tmp = g_strdup_printf(DEV_DM_DIR "/%s", ent->d_name);
|
|
|
|
if (stat(tmp, &sb[1]) == 0 &&
|
|
sb[0].st_rdev == sb[1].st_rdev) {
|
|
return g_steal_pointer(&tmp);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static int
|
|
virDevMapperGetTargetsImpl(int controlFD,
|
|
const char *path,
|
|
char ***devPaths_ret,
|
|
unsigned int ttl)
|
|
{
|
|
g_autofree char *sanitizedPath = NULL;
|
|
g_autofree char *buf = NULL;
|
|
struct dm_ioctl dm;
|
|
struct dm_target_deps *deps = NULL;
|
|
VIR_AUTOSTRINGLIST devPaths = NULL;
|
|
size_t i;
|
|
|
|
memset(&dm, 0, sizeof(dm));
|
|
*devPaths_ret = NULL;
|
|
|
|
if (ttl == 0) {
|
|
errno = ELOOP;
|
|
return -1;
|
|
}
|
|
|
|
if (!virIsDevMapperDevice(path))
|
|
return 0;
|
|
|
|
if (!(sanitizedPath = virDMSanitizepath(path)))
|
|
return 0;
|
|
|
|
if (virStrncpy(dm.name, sanitizedPath, -1, DM_TABLE_DEPS) < 0) {
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
|
|
_("Resolved device mapper name too long"));
|
|
return -1;
|
|
}
|
|
|
|
deps = virDMIoctl(controlFD, DM_TABLE_DEPS, &dm, &buf);
|
|
if (!deps) {
|
|
if (errno == ENXIO)
|
|
return 0;
|
|
|
|
virReportSystemError(errno,
|
|
_("Unable to query dependencies for %s"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
devPaths = g_new0(char *, deps->count + 1);
|
|
for (i = 0; i < deps->count; i++) {
|
|
devPaths[i] = g_strdup_printf("/dev/block/%u:%u",
|
|
major(deps->dev[i]),
|
|
minor(deps->dev[i]));
|
|
}
|
|
|
|
for (i = 0; i < deps->count; i++) {
|
|
VIR_AUTOSTRINGLIST tmpPaths = NULL;
|
|
|
|
if (virDevMapperGetTargetsImpl(controlFD, devPaths[i], &tmpPaths, ttl - 1) < 0)
|
|
return -1;
|
|
|
|
if (virStringListMerge(&devPaths, &tmpPaths) < 0)
|
|
return -1;
|
|
}
|
|
|
|
*devPaths_ret = g_steal_pointer(&devPaths);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDevMapperGetTargets:
|
|
* @path: devmapper target
|
|
* @devPaths: returned string list of devices
|
|
*
|
|
* For given @path figure out its targets, and store them in
|
|
* @devPaths array. Note, @devPaths is a string list so it's NULL
|
|
* terminated.
|
|
*
|
|
* If @path is not a devmapper device, @devPaths is set to NULL and
|
|
* success is returned.
|
|
*
|
|
* If @path consists of yet another devmapper targets these are
|
|
* consulted recursively.
|
|
*
|
|
* Returns 0 on success,
|
|
* -1 otherwise (with errno set, no libvirt error is
|
|
* reported)
|
|
*/
|
|
int
|
|
virDevMapperGetTargets(const char *path,
|
|
char ***devPaths)
|
|
{
|
|
VIR_AUTOCLOSE controlFD = -1;
|
|
const unsigned int ttl = 32;
|
|
|
|
/* Arbitrary limit on recursion level. A devmapper target can
|
|
* consist of devices or yet another targets. If that's the
|
|
* case, we have to stop recursion somewhere. */
|
|
|
|
if ((controlFD = virDMOpen()) < 0) {
|
|
if (controlFD == -2) {
|
|
/* The CONTROL_PATH doesn't exist or is unusable.
|
|
* Probably the module isn't loaded, yet. Don't error
|
|
* out, just exit. */
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
return virDevMapperGetTargetsImpl(controlFD, path, devPaths, ttl);
|
|
}
|
|
|
|
|
|
bool
|
|
virIsDevMapperDevice(const char *dev_name)
|
|
{
|
|
struct stat buf;
|
|
unsigned int major;
|
|
|
|
if (virDevMapperGetMajor(&major) < 0)
|
|
return false;
|
|
|
|
if (!stat(dev_name, &buf) &&
|
|
S_ISBLK(buf.st_mode) &&
|
|
major(buf.st_rdev) == major)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
#else /* !defined(__linux__) */
|
|
|
|
int
|
|
virDevMapperGetTargets(const char *path G_GNUC_UNUSED,
|
|
char ***devPaths G_GNUC_UNUSED)
|
|
{
|
|
errno = ENOSYS;
|
|
return -1;
|
|
}
|
|
|
|
|
|
bool
|
|
virIsDevMapperDevice(const char *dev_name G_GNUC_UNUSED)
|
|
{
|
|
return false;
|
|
}
|
|
#endif /* ! defined(__linux__) */
|