/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 1999,2000,2001,2002,2003,2006,2007,2008,2009,2010,2011,2012,2013 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see .
*/
#include
#include
#include
#include
#ifdef HAVE_DEVICE_MAPPER
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_LIMITS_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
static int
grub_util_open_dm (const char *os_dev, struct dm_tree **tree,
struct dm_tree_node **node)
{
uint32_t maj, min;
struct stat st;
*node = NULL;
*tree = NULL;
if (stat (os_dev, &st) < 0)
return 0;
maj = major (st.st_rdev);
min = minor (st.st_rdev);
if (!dm_is_dm_major (maj))
return 0;
*tree = dm_tree_create ();
if (! *tree)
{
grub_puts_ (N_("Failed to create `device-mapper' tree"));
grub_dprintf ("hostdisk", "dm_tree_create failed\n");
return 0;
}
if (! dm_tree_add_dev (*tree, maj, min))
{
grub_dprintf ("hostdisk", "dm_tree_add_dev failed\n");
dm_tree_free (*tree);
*tree = NULL;
return 0;
}
*node = dm_tree_find_node (*tree, maj, min);
if (! *node)
{
grub_dprintf ("hostdisk", "dm_tree_find_node failed\n");
dm_tree_free (*tree);
*tree = NULL;
return 0;
}
return 1;
}
static char *
get_dm_uuid (const char *os_dev)
{
struct dm_tree *tree;
struct dm_tree_node *node;
const char *node_uuid;
char *ret;
if (!grub_util_open_dm (os_dev, &tree, &node))
return NULL;
node_uuid = dm_tree_node_get_uuid (node);
if (! node_uuid)
{
grub_dprintf ("hostdisk", "%s has no DM uuid\n", os_dev);
dm_tree_free (tree);
return NULL;
}
ret = grub_strdup (node_uuid);
dm_tree_free (tree);
return ret;
}
enum grub_dev_abstraction_types
grub_util_get_dm_abstraction (const char *os_dev)
{
char *uuid;
uuid = get_dm_uuid (os_dev);
if (uuid == NULL)
return GRUB_DEV_ABSTRACTION_NONE;
if (strncmp (uuid, "LVM-", 4) == 0)
{
grub_free (uuid);
return GRUB_DEV_ABSTRACTION_LVM;
}
if (strncmp (uuid, "CRYPT-LUKS1-", sizeof ("CRYPT-LUKS1-") - 1) == 0
|| strncmp (uuid, "CRYPT-LUKS2-", sizeof ("CRYPT-LUKS2-") - 1) == 0)
{
grub_free (uuid);
return GRUB_DEV_ABSTRACTION_LUKS;
}
grub_free (uuid);
return GRUB_DEV_ABSTRACTION_NONE;
}
void
grub_util_pull_devmapper (const char *os_dev)
{
struct dm_tree *tree;
struct dm_tree_node *node;
struct dm_tree_node *child;
void *handle = NULL;
char *lastsubdev = NULL;
char *uuid;
uuid = get_dm_uuid (os_dev);
if (!grub_util_open_dm (os_dev, &tree, &node))
{
grub_free (uuid);
return;
}
while ((child = dm_tree_next_child (&handle, node, 0)))
{
const struct dm_info *dm = dm_tree_node_get_info (child);
char *subdev;
if (!dm)
continue;
subdev = grub_find_device ("/dev", makedev (dm->major, dm->minor));
if (subdev)
{
lastsubdev = subdev;
grub_util_pull_device (subdev);
}
}
if (uuid
&& (strncmp (uuid, "CRYPT-LUKS1-", sizeof ("CRYPT-LUKS1-") - 1) == 0
|| strncmp (uuid, "CRYPT-LUKS2-", sizeof ("CRYPT-LUKS2-") - 1) == 0)
&& lastsubdev)
{
char *grdev = grub_util_get_grub_dev (lastsubdev);
if (grdev)
{
grub_err_t err;
err = grub_cryptodisk_cheat_mount (grdev, os_dev);
if (err)
grub_util_error (_("can't mount encrypted volume `%s': %s"),
lastsubdev, grub_errmsg);
if (strncmp (uuid, "CRYPT-LUKS2-", sizeof ("CRYPT-LUKS2-") - 1) == 0)
{
/*
* Set LUKS2 cipher from dm parameters, since it is not
* possible to determine the correct one without
* unlocking, as there might be multiple segments.
*/
grub_disk_t source;
grub_cryptodisk_t cryptodisk;
grub_uint64_t start, length;
char *target_type;
char *params;
const char *name;
char *cipher, *cipher_mode;
struct dm_task *dmt;
char *seek_head, *c;
unsigned int remaining;
source = grub_disk_open (grdev);
if (! source)
grub_util_error (_("cannot open grub disk `%s'"), grdev);
cryptodisk = grub_cryptodisk_get_by_source_disk (source);
if (! cryptodisk)
grub_util_error (_("cannot get cryptodisk from source disk `%s'"), grdev);
grub_disk_close (source);
/*
* The following function always returns a non-NULL pointer,
* but the string may be empty if the relevant info is not present.
*/
name = dm_tree_node_get_name (node);
if (*name == '\0')
grub_util_error (_("cannot get dm node name for grub dev `%s'"), grdev);
grub_util_info ("populating parameters of cryptomount `%s' from DM device `%s'",
uuid, name);
dmt = dm_task_create (DM_DEVICE_TABLE);
if (dmt == NULL)
grub_util_error (_("can't create dm task DM_DEVICE_TABLE"));
if (dm_task_set_name (dmt, name) == 0)
grub_util_error (_("can't set dm task name to `%s'"), name);
if (dm_task_run (dmt) == 0)
grub_util_error (_("can't run dm task for `%s'"), name);
/*
* dm_get_next_target() doesn't have any error modes, everything has
* been handled by dm_task_run().
*/
dm_get_next_target (dmt, NULL, &start, &length,
&target_type, ¶ms);
if (strncmp (target_type, "crypt", sizeof ("crypt")) != 0)
grub_util_error (_("dm target of type `%s' is not `crypt'"), target_type);
/*
* The dm target parameters for dm-crypt are
* [<#opt_params> ...]
*/
c = params;
remaining = grub_strlen (c);
/* First, get the cipher name from the cipher. */
seek_head = grub_memchr (c, '-', remaining);
if (seek_head == NULL)
grub_util_error (_("can't get cipher from dm-crypt parameters `%s'"),
params);
cipher = grub_strndup (c, seek_head - c);
if (cipher == NULL)
grub_util_error (_("could not strndup cipher of length `%" PRIuGRUB_SIZE "'"), (grub_size_t) (seek_head - c));
remaining -= seek_head - c + 1;
c = seek_head + 1;
/* Now, the cipher mode. */
seek_head = grub_memchr (c, ' ', remaining);
if (seek_head == NULL)
grub_util_error (_("can't get cipher mode from dm-crypt parameters `%s'"),
params);
cipher_mode = grub_strndup (c, seek_head - c);
if (cipher_mode == NULL)
grub_util_error (_("could not strndup cipher_mode of length `%" PRIuGRUB_SIZE "'"), (grub_size_t) (seek_head - c));
remaining -= seek_head - c + 1;
c = seek_head + 1;
err = grub_cryptodisk_setcipher (cryptodisk, cipher, cipher_mode);
if (err)
grub_util_error (_("can't set cipher of cryptodisk `%s' to `%s' with mode `%s'"),
uuid, cipher, cipher_mode);
grub_free (cipher);
grub_free (cipher_mode);
/*
* This is the only hash usable by PBKDF2, and we don't
* have Argon2 support yet, so set it by default,
* otherwise grub-probe would miss the required
* abstraction.
*/
cryptodisk->hash = grub_crypto_lookup_md_by_name ("sha256");
if (cryptodisk->hash == NULL)
grub_util_error (_("can't lookup hash sha256 by name"));
dm_task_destroy (dmt);
}
}
dm_tree_free (tree);
grub_free (grdev);
}
else
dm_tree_free (tree);
grub_free (uuid);
}
char *
grub_util_devmapper_part_to_disk (struct stat *st,
int *is_part, const char *path)
{
int major, minor;
if (grub_util_get_dm_node_linear_info (st->st_rdev,
&major, &minor, 0))
{
*is_part = 1;
return grub_find_device ("/dev", makedev (major, minor));
}
*is_part = 0;
return xstrdup (path);
}
char *
grub_util_get_devmapper_grub_dev (const char *os_dev)
{
char *uuid, *optr;
char *grub_dev;
uuid = get_dm_uuid (os_dev);
if (!uuid)
return NULL;
switch (grub_util_get_dev_abstraction (os_dev))
{
case GRUB_DEV_ABSTRACTION_LVM:
{
unsigned i;
int dashes[] = { 0, 6, 10, 14, 18, 22, 26, 32, 38, 42, 46, 50, 54, 58};
grub_dev = xmalloc (grub_strlen (uuid) + 40);
optr = grub_stpcpy (grub_dev, "lvmid/");
for (i = 0; i < ARRAY_SIZE (dashes) - 1; i++)
{
memcpy (optr, uuid + sizeof ("LVM-") - 1 + dashes[i],
dashes[i+1] - dashes[i]);
optr += dashes[i+1] - dashes[i];
*optr++ = '-';
}
optr = stpcpy (optr, uuid + sizeof ("LVM-") - 1 + dashes[i]);
*optr = '\0';
grub_dev[sizeof("lvmid/xxxxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxxxx") - 1]
= '/';
free (uuid);
return grub_dev;
}
case GRUB_DEV_ABSTRACTION_LUKS:
{
char *dash;
dash = grub_strchr (uuid + sizeof ("CRYPT-LUKS*-") - 1, '-');
if (dash)
*dash = 0;
grub_dev = grub_xasprintf ("cryptouuid/%s",
uuid + sizeof ("CRYPT-LUKS*-") - 1);
grub_free (uuid);
return grub_dev;
}
default:
grub_free (uuid);
return NULL;
}
}
char *
grub_util_get_vg_uuid (const char *os_dev)
{
char *uuid, *vgid;
int dashes[] = { 0, 6, 10, 14, 18, 22, 26, 32};
unsigned i;
char *optr;
uuid = get_dm_uuid (os_dev);
if (!uuid)
return NULL;
vgid = xmalloc (grub_strlen (uuid));
optr = vgid;
for (i = 0; i < ARRAY_SIZE (dashes) - 1; i++)
{
memcpy (optr, uuid + sizeof ("LVM-") - 1 + dashes[i],
dashes[i+1] - dashes[i]);
optr += dashes[i+1] - dashes[i];
*optr++ = '-';
}
optr--;
*optr = '\0';
grub_free (uuid);
return vgid;
}
void
grub_util_devmapper_cleanup (void)
{
dm_lib_release ();
}
#else
void
grub_util_pull_devmapper (const char *os_dev __attribute__ ((unused)))
{
return;
}
void
grub_util_devmapper_cleanup (void)
{
}
enum grub_dev_abstraction_types
grub_util_get_dm_abstraction (const char *os_dev __attribute__ ((unused)))
{
return GRUB_DEV_ABSTRACTION_NONE;
}
char *
grub_util_get_vg_uuid (const char *os_dev __attribute__ ((unused)))
{
return NULL;
}
char *
grub_util_devmapper_part_to_disk (struct stat *st __attribute__ ((unused)),
int *is_part __attribute__ ((unused)),
const char *os_dev __attribute__ ((unused)))
{
return NULL;
}
char *
grub_util_get_devmapper_grub_dev (const char *os_dev __attribute__ ((unused)))
{
return NULL;
}
#endif