freebsd-nq/sys/geom/eli/g_eli_ctl.c

1058 lines
26 KiB
C
Raw Normal View History

/*-
* Copyright (c) 2005-2010 Pawel Jakub Dawidek <pjd@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
2006-02-01 12:06:01 +00:00
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/bio.h>
#include <sys/sysctl.h>
#include <sys/malloc.h>
#include <sys/kthread.h>
#include <sys/proc.h>
#include <sys/sched.h>
#include <sys/uio.h>
#include <vm/uma.h>
#include <geom/geom.h>
#include <geom/eli/g_eli.h>
MALLOC_DECLARE(M_ELI);
static void
g_eli_ctl_attach(struct gctl_req *req, struct g_class *mp)
{
struct g_eli_metadata md;
struct g_provider *pp;
const char *name;
u_char *key, mkey[G_ELI_DATAIVKEYLEN];
int *nargs, *detach, *readonly;
int keysize, error;
u_int nkey;
g_topology_assert();
nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs));
if (nargs == NULL) {
gctl_error(req, "No '%s' argument.", "nargs");
return;
}
if (*nargs != 1) {
gctl_error(req, "Invalid number of arguments.");
return;
}
detach = gctl_get_paraml(req, "detach", sizeof(*detach));
if (detach == NULL) {
gctl_error(req, "No '%s' argument.", "detach");
return;
}
readonly = gctl_get_paraml(req, "readonly", sizeof(*readonly));
if (readonly == NULL) {
gctl_error(req, "No '%s' argument.", "readonly");
return;
}
name = gctl_get_asciiparam(req, "arg0");
if (name == NULL) {
gctl_error(req, "No 'arg%u' argument.", 0);
return;
}
if (strncmp(name, "/dev/", strlen("/dev/")) == 0)
name += strlen("/dev/");
pp = g_provider_by_name(name);
if (pp == NULL) {
gctl_error(req, "Provider %s is invalid.", name);
return;
}
error = g_eli_read_metadata(mp, pp, &md);
if (error != 0) {
gctl_error(req, "Cannot read metadata from %s (error=%d).",
name, error);
return;
}
if (md.md_keys == 0x00) {
bzero(&md, sizeof(md));
gctl_error(req, "No valid keys on %s.", pp->name);
return;
}
key = gctl_get_param(req, "key", &keysize);
if (key == NULL || keysize != G_ELI_USERKEYLEN) {
bzero(&md, sizeof(md));
gctl_error(req, "No '%s' argument.", "key");
return;
}
error = g_eli_mkey_decrypt(&md, key, mkey, &nkey);
bzero(key, keysize);
if (error == -1) {
bzero(&md, sizeof(md));
gctl_error(req, "Wrong key for %s.", pp->name);
return;
} else if (error > 0) {
bzero(&md, sizeof(md));
gctl_error(req, "Cannot decrypt Master Key for %s (error=%d).",
pp->name, error);
return;
}
G_ELI_DEBUG(1, "Using Master Key %u for %s.", nkey, pp->name);
if (*detach && *readonly) {
bzero(&md, sizeof(md));
gctl_error(req, "Options -d and -r are mutually exclusive.");
return;
}
if (*detach)
md.md_flags |= G_ELI_FLAG_WO_DETACH;
if (*readonly)
md.md_flags |= G_ELI_FLAG_RO;
g_eli_create(req, mp, pp, &md, mkey, nkey);
bzero(mkey, sizeof(mkey));
bzero(&md, sizeof(md));
}
static struct g_eli_softc *
g_eli_find_device(struct g_class *mp, const char *prov)
{
struct g_eli_softc *sc;
struct g_geom *gp;
struct g_provider *pp;
struct g_consumer *cp;
if (strncmp(prov, "/dev/", strlen("/dev/")) == 0)
prov += strlen("/dev/");
LIST_FOREACH(gp, &mp->geom, geom) {
sc = gp->softc;
if (sc == NULL)
continue;
pp = LIST_FIRST(&gp->provider);
if (pp != NULL && strcmp(pp->name, prov) == 0)
return (sc);
cp = LIST_FIRST(&gp->consumer);
if (cp != NULL && cp->provider != NULL &&
strcmp(cp->provider->name, prov) == 0) {
return (sc);
}
}
return (NULL);
}
static void
g_eli_ctl_detach(struct gctl_req *req, struct g_class *mp)
{
struct g_eli_softc *sc;
int *force, *last, *nargs, error;
const char *prov;
char param[16];
int i;
g_topology_assert();
nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs));
if (nargs == NULL) {
gctl_error(req, "No '%s' argument.", "nargs");
return;
}
if (*nargs <= 0) {
gctl_error(req, "Missing device(s).");
return;
}
force = gctl_get_paraml(req, "force", sizeof(*force));
if (force == NULL) {
gctl_error(req, "No '%s' argument.", "force");
return;
}
last = gctl_get_paraml(req, "last", sizeof(*last));
if (last == NULL) {
gctl_error(req, "No '%s' argument.", "last");
return;
}
for (i = 0; i < *nargs; i++) {
snprintf(param, sizeof(param), "arg%d", i);
prov = gctl_get_asciiparam(req, param);
if (prov == NULL) {
gctl_error(req, "No 'arg%d' argument.", i);
return;
}
sc = g_eli_find_device(mp, prov);
if (sc == NULL) {
gctl_error(req, "No such device: %s.", prov);
return;
}
if (*last) {
sc->sc_flags |= G_ELI_FLAG_RW_DETACH;
sc->sc_geom->access = g_eli_access;
} else {
Bring in geli suspend/resume functionality (finally). Before this change if you wanted to suspend your laptop and be sure that your encryption keys are safe, you had to stop all processes that use file system stored on encrypted device, unmount the file system and detach geli provider. This isn't very handy. If you are a lucky user of a laptop where suspend/resume actually works with FreeBSD (I'm not!) you most likely want to suspend your laptop, because you don't want to start everything over again when you turn your laptop back on. And this is where geli suspend/resume steps in. When you execute: # geli suspend -a geli will wait for all in-flight I/O requests, suspend new I/O requests, remove all geli sensitive data from the kernel memory (like encryption keys) and will wait for either 'geli resume' or 'geli detach'. Now with no keys in memory you can suspend your laptop without stopping any processes or unmounting any file systems. When you resume your laptop you have to resume geli devices using 'geli resume' command. You need to provide your passphrase, etc. again so the keys can be restored and suspended I/O requests released. Of course you need to remember that 'geli suspend' won't clear file system cache and other places where data from your geli-encrypted file system might be present. But to get rid of those stopping processes and unmounting file system won't help either - you have to turn your laptop off. Be warned. Also note, that suspending geli device which contains file system with geli utility (or anything used by 'geli resume') is not very good idea, as you won't be able to resume it - when you execute geli(8), the kernel will try to read it and this read I/O request will be suspended.
2010-10-20 20:50:55 +00:00
error = g_eli_destroy(sc, *force ? TRUE : FALSE);
if (error != 0) {
gctl_error(req,
"Cannot destroy device %s (error=%d).",
sc->sc_name, error);
return;
}
}
}
}
static void
g_eli_ctl_onetime(struct gctl_req *req, struct g_class *mp)
{
struct g_eli_metadata md;
struct g_provider *pp;
const char *name;
intmax_t *keylen, *sectorsize;
u_char mkey[G_ELI_DATAIVKEYLEN];
int *nargs, *detach;
g_topology_assert();
bzero(&md, sizeof(md));
nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs));
if (nargs == NULL) {
gctl_error(req, "No '%s' argument.", "nargs");
return;
}
if (*nargs != 1) {
gctl_error(req, "Invalid number of arguments.");
return;
}
detach = gctl_get_paraml(req, "detach", sizeof(*detach));
if (detach == NULL) {
gctl_error(req, "No '%s' argument.", "detach");
return;
}
strlcpy(md.md_magic, G_ELI_MAGIC, sizeof(md.md_magic));
md.md_version = G_ELI_VERSION;
md.md_flags |= G_ELI_FLAG_ONETIME;
if (*detach)
md.md_flags |= G_ELI_FLAG_WO_DETACH;
md.md_ealgo = CRYPTO_ALGORITHM_MIN - 1;
name = gctl_get_asciiparam(req, "aalgo");
if (name == NULL) {
gctl_error(req, "No '%s' argument.", "aalgo");
return;
}
if (*name != '\0') {
md.md_aalgo = g_eli_str2aalgo(name);
if (md.md_aalgo >= CRYPTO_ALGORITHM_MIN &&
md.md_aalgo <= CRYPTO_ALGORITHM_MAX) {
md.md_flags |= G_ELI_FLAG_AUTH;
} else {
/*
* For backward compatibility, check if the -a option
* was used to provide encryption algorithm.
*/
md.md_ealgo = g_eli_str2ealgo(name);
if (md.md_ealgo < CRYPTO_ALGORITHM_MIN ||
md.md_ealgo > CRYPTO_ALGORITHM_MAX) {
gctl_error(req,
"Invalid authentication algorithm.");
return;
} else {
gctl_error(req, "warning: The -e option, not "
"the -a option is now used to specify "
"encryption algorithm to use.");
}
}
}
if (md.md_ealgo < CRYPTO_ALGORITHM_MIN ||
md.md_ealgo > CRYPTO_ALGORITHM_MAX) {
name = gctl_get_asciiparam(req, "ealgo");
if (name == NULL) {
gctl_error(req, "No '%s' argument.", "ealgo");
return;
}
md.md_ealgo = g_eli_str2ealgo(name);
if (md.md_ealgo < CRYPTO_ALGORITHM_MIN ||
md.md_ealgo > CRYPTO_ALGORITHM_MAX) {
gctl_error(req, "Invalid encryption algorithm.");
return;
}
}
keylen = gctl_get_paraml(req, "keylen", sizeof(*keylen));
if (keylen == NULL) {
gctl_error(req, "No '%s' argument.", "keylen");
return;
}
md.md_keylen = g_eli_keylen(md.md_ealgo, *keylen);
if (md.md_keylen == 0) {
gctl_error(req, "Invalid '%s' argument.", "keylen");
return;
}
/* Not important here. */
md.md_provsize = 0;
/* Not important here. */
bzero(md.md_salt, sizeof(md.md_salt));
md.md_keys = 0x01;
arc4rand(mkey, sizeof(mkey), 0);
/* Not important here. */
bzero(md.md_hash, sizeof(md.md_hash));
name = gctl_get_asciiparam(req, "arg0");
if (name == NULL) {
gctl_error(req, "No 'arg%u' argument.", 0);
return;
}
if (strncmp(name, "/dev/", strlen("/dev/")) == 0)
name += strlen("/dev/");
pp = g_provider_by_name(name);
if (pp == NULL) {
gctl_error(req, "Provider %s is invalid.", name);
return;
}
sectorsize = gctl_get_paraml(req, "sectorsize", sizeof(*sectorsize));
if (sectorsize == NULL) {
gctl_error(req, "No '%s' argument.", "sectorsize");
return;
}
if (*sectorsize == 0)
md.md_sectorsize = pp->sectorsize;
else {
if (*sectorsize < 0 || (*sectorsize % pp->sectorsize) != 0) {
gctl_error(req, "Invalid sector size.");
return;
}
if (*sectorsize > PAGE_SIZE) {
gctl_error(req, "warning: Using sectorsize bigger than "
"the page size!");
}
md.md_sectorsize = *sectorsize;
}
g_eli_create(req, mp, pp, &md, mkey, -1);
bzero(mkey, sizeof(mkey));
bzero(&md, sizeof(md));
}
static void
g_eli_ctl_configure(struct gctl_req *req, struct g_class *mp)
{
struct g_eli_softc *sc;
struct g_eli_metadata md;
struct g_provider *pp;
struct g_consumer *cp;
char param[16];
const char *prov;
u_char *sector;
int *nargs, *boot, *noboot;
int error;
u_int i;
g_topology_assert();
nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs));
if (nargs == NULL) {
gctl_error(req, "No '%s' argument.", "nargs");
return;
}
if (*nargs <= 0) {
gctl_error(req, "Missing device(s).");
return;
}
boot = gctl_get_paraml(req, "boot", sizeof(*boot));
if (boot == NULL) {
gctl_error(req, "No '%s' argument.", "boot");
return;
}
noboot = gctl_get_paraml(req, "noboot", sizeof(*noboot));
if (noboot == NULL) {
gctl_error(req, "No '%s' argument.", "noboot");
return;
}
if (*boot && *noboot) {
gctl_error(req, "Options -b and -B are mutually exclusive.");
return;
}
if (!*boot && !*noboot) {
gctl_error(req, "No option given.");
return;
}
for (i = 0; i < *nargs; i++) {
snprintf(param, sizeof(param), "arg%d", i);
prov = gctl_get_asciiparam(req, param);
if (prov == NULL) {
gctl_error(req, "No 'arg%d' argument.", i);
return;
}
sc = g_eli_find_device(mp, prov);
if (sc == NULL) {
/*
* We ignore not attached providers, userland part will
* take care of them.
*/
G_ELI_DEBUG(1, "Skipping configuration of not attached "
"provider %s.", prov);
continue;
}
if (*boot && (sc->sc_flags & G_ELI_FLAG_BOOT)) {
G_ELI_DEBUG(1, "BOOT flag already configured for %s.",
prov);
continue;
} else if (!*boot && !(sc->sc_flags & G_ELI_FLAG_BOOT)) {
G_ELI_DEBUG(1, "BOOT flag not configured for %s.",
prov);
continue;
}
if (sc->sc_flags & G_ELI_FLAG_RO) {
gctl_error(req, "Cannot change configuration of "
"read-only provider %s.", prov);
continue;
}
cp = LIST_FIRST(&sc->sc_geom->consumer);
pp = cp->provider;
error = g_eli_read_metadata(mp, pp, &md);
if (error != 0) {
gctl_error(req,
"Cannot read metadata from %s (error=%d).",
prov, error);
continue;
}
if (*boot) {
md.md_flags |= G_ELI_FLAG_BOOT;
sc->sc_flags |= G_ELI_FLAG_BOOT;
} else {
md.md_flags &= ~G_ELI_FLAG_BOOT;
sc->sc_flags &= ~G_ELI_FLAG_BOOT;
}
sector = malloc(pp->sectorsize, M_ELI, M_WAITOK | M_ZERO);
eli_metadata_encode(&md, sector);
error = g_write_data(cp, pp->mediasize - pp->sectorsize, sector,
pp->sectorsize);
if (error != 0) {
gctl_error(req,
"Cannot store metadata on %s (error=%d).",
prov, error);
}
bzero(&md, sizeof(md));
bzero(sector, sizeof(sector));
free(sector, M_ELI);
}
}
static void
g_eli_ctl_setkey(struct gctl_req *req, struct g_class *mp)
{
struct g_eli_softc *sc;
struct g_eli_metadata md;
struct g_provider *pp;
struct g_consumer *cp;
const char *name;
u_char *key, *mkeydst, *sector;
intmax_t *valp;
int keysize, nkey, error;
g_topology_assert();
name = gctl_get_asciiparam(req, "arg0");
if (name == NULL) {
gctl_error(req, "No 'arg%u' argument.", 0);
return;
}
sc = g_eli_find_device(mp, name);
if (sc == NULL) {
gctl_error(req, "Provider %s is invalid.", name);
return;
}
if (sc->sc_flags & G_ELI_FLAG_RO) {
gctl_error(req, "Cannot change keys for read-only provider.");
return;
}
cp = LIST_FIRST(&sc->sc_geom->consumer);
pp = cp->provider;
error = g_eli_read_metadata(mp, pp, &md);
if (error != 0) {
gctl_error(req, "Cannot read metadata from %s (error=%d).",
name, error);
return;
}
valp = gctl_get_paraml(req, "keyno", sizeof(*valp));
if (valp == NULL) {
gctl_error(req, "No '%s' argument.", "keyno");
return;
}
if (*valp != -1)
nkey = *valp;
else
nkey = sc->sc_nkey;
if (nkey < 0 || nkey >= G_ELI_MAXMKEYS) {
gctl_error(req, "Invalid '%s' argument.", "keyno");
return;
}
valp = gctl_get_paraml(req, "iterations", sizeof(*valp));
if (valp == NULL) {
gctl_error(req, "No '%s' argument.", "iterations");
return;
}
/* Check if iterations number should and can be changed. */
if (*valp != -1) {
if (bitcount32(md.md_keys) != 1) {
gctl_error(req, "To be able to use '-i' option, only "
"one key can be defined.");
return;
}
if (md.md_keys != (1 << nkey)) {
gctl_error(req, "Only already defined key can be "
"changed when '-i' option is used.");
return;
}
md.md_iterations = *valp;
}
key = gctl_get_param(req, "key", &keysize);
if (key == NULL || keysize != G_ELI_USERKEYLEN) {
bzero(&md, sizeof(md));
gctl_error(req, "No '%s' argument.", "key");
return;
}
mkeydst = md.md_mkeys + nkey * G_ELI_MKEYLEN;
md.md_keys |= (1 << nkey);
bcopy(sc->sc_mkey, mkeydst, sizeof(sc->sc_mkey));
/* Encrypt Master Key with the new key. */
error = g_eli_mkey_encrypt(md.md_ealgo, key, md.md_keylen, mkeydst);
bzero(key, sizeof(key));
if (error != 0) {
bzero(&md, sizeof(md));
gctl_error(req, "Cannot encrypt Master Key (error=%d).", error);
return;
}
sector = malloc(pp->sectorsize, M_ELI, M_WAITOK | M_ZERO);
/* Store metadata with fresh key. */
eli_metadata_encode(&md, sector);
bzero(&md, sizeof(md));
error = g_write_data(cp, pp->mediasize - pp->sectorsize, sector,
pp->sectorsize);
bzero(sector, sizeof(sector));
free(sector, M_ELI);
if (error != 0) {
gctl_error(req, "Cannot store metadata on %s (error=%d).",
pp->name, error);
return;
}
G_ELI_DEBUG(1, "Key %u changed on %s.", nkey, pp->name);
}
static void
g_eli_ctl_delkey(struct gctl_req *req, struct g_class *mp)
{
struct g_eli_softc *sc;
struct g_eli_metadata md;
struct g_provider *pp;
struct g_consumer *cp;
const char *name;
u_char *mkeydst, *sector;
intmax_t *valp;
size_t keysize;
int error, nkey, *all, *force;
u_int i;
g_topology_assert();
nkey = 0; /* fixes causeless gcc warning */
name = gctl_get_asciiparam(req, "arg0");
if (name == NULL) {
gctl_error(req, "No 'arg%u' argument.", 0);
return;
}
sc = g_eli_find_device(mp, name);
if (sc == NULL) {
gctl_error(req, "Provider %s is invalid.", name);
return;
}
if (sc->sc_flags & G_ELI_FLAG_RO) {
gctl_error(req, "Cannot delete keys for read-only provider.");
return;
}
cp = LIST_FIRST(&sc->sc_geom->consumer);
pp = cp->provider;
error = g_eli_read_metadata(mp, pp, &md);
if (error != 0) {
gctl_error(req, "Cannot read metadata from %s (error=%d).",
name, error);
return;
}
all = gctl_get_paraml(req, "all", sizeof(*all));
if (all == NULL) {
gctl_error(req, "No '%s' argument.", "all");
return;
}
if (*all) {
mkeydst = md.md_mkeys;
keysize = sizeof(md.md_mkeys);
} else {
force = gctl_get_paraml(req, "force", sizeof(*force));
if (force == NULL) {
gctl_error(req, "No '%s' argument.", "force");
return;
}
2006-09-30 08:16:49 +00:00
valp = gctl_get_paraml(req, "keyno", sizeof(*valp));
if (valp == NULL) {
gctl_error(req, "No '%s' argument.", "keyno");
return;
}
if (*valp != -1)
nkey = *valp;
else
nkey = sc->sc_nkey;
if (nkey < 0 || nkey >= G_ELI_MAXMKEYS) {
gctl_error(req, "Invalid '%s' argument.", "keyno");
return;
}
if (!(md.md_keys & (1 << nkey)) && !*force) {
gctl_error(req, "Master Key %u is not set.", nkey);
return;
}
md.md_keys &= ~(1 << nkey);
if (md.md_keys == 0 && !*force) {
gctl_error(req, "This is the last Master Key. Use '-f' "
"flag if you really want to remove it.");
return;
}
mkeydst = md.md_mkeys + nkey * G_ELI_MKEYLEN;
keysize = G_ELI_MKEYLEN;
}
sector = malloc(pp->sectorsize, M_ELI, M_WAITOK | M_ZERO);
for (i = 0; i <= g_eli_overwrites; i++) {
if (i == g_eli_overwrites)
bzero(mkeydst, keysize);
else
arc4rand(mkeydst, keysize, 0);
/* Store metadata with destroyed key. */
eli_metadata_encode(&md, sector);
error = g_write_data(cp, pp->mediasize - pp->sectorsize, sector,
pp->sectorsize);
if (error != 0) {
G_ELI_DEBUG(0, "Cannot store metadata on %s "
"(error=%d).", pp->name, error);
}
/*
* Flush write cache so we don't overwrite data N times in cache
* and only once on disk.
*/
(void)g_io_flush(cp);
}
bzero(&md, sizeof(md));
bzero(sector, sizeof(sector));
free(sector, M_ELI);
if (*all)
G_ELI_DEBUG(1, "All keys removed from %s.", pp->name);
else
G_ELI_DEBUG(1, "Key %d removed from %s.", nkey, pp->name);
}
Bring in geli suspend/resume functionality (finally). Before this change if you wanted to suspend your laptop and be sure that your encryption keys are safe, you had to stop all processes that use file system stored on encrypted device, unmount the file system and detach geli provider. This isn't very handy. If you are a lucky user of a laptop where suspend/resume actually works with FreeBSD (I'm not!) you most likely want to suspend your laptop, because you don't want to start everything over again when you turn your laptop back on. And this is where geli suspend/resume steps in. When you execute: # geli suspend -a geli will wait for all in-flight I/O requests, suspend new I/O requests, remove all geli sensitive data from the kernel memory (like encryption keys) and will wait for either 'geli resume' or 'geli detach'. Now with no keys in memory you can suspend your laptop without stopping any processes or unmounting any file systems. When you resume your laptop you have to resume geli devices using 'geli resume' command. You need to provide your passphrase, etc. again so the keys can be restored and suspended I/O requests released. Of course you need to remember that 'geli suspend' won't clear file system cache and other places where data from your geli-encrypted file system might be present. But to get rid of those stopping processes and unmounting file system won't help either - you have to turn your laptop off. Be warned. Also note, that suspending geli device which contains file system with geli utility (or anything used by 'geli resume') is not very good idea, as you won't be able to resume it - when you execute geli(8), the kernel will try to read it and this read I/O request will be suspended.
2010-10-20 20:50:55 +00:00
static int
g_eli_suspend_one(struct g_eli_softc *sc)
{
struct g_eli_worker *wr;
g_topology_assert();
if (sc == NULL)
return (ENOENT);
if (sc->sc_flags & G_ELI_FLAG_ONETIME)
return (EOPNOTSUPP);
mtx_lock(&sc->sc_queue_mtx);
if (sc->sc_flags & G_ELI_FLAG_SUSPEND) {
mtx_unlock(&sc->sc_queue_mtx);
return (EALREADY);
}
sc->sc_flags |= G_ELI_FLAG_SUSPEND;
wakeup(sc);
for (;;) {
LIST_FOREACH(wr, &sc->sc_workers, w_next) {
if (wr->w_active)
break;
}
if (wr == NULL)
break;
/* Not all threads suspended. */
msleep(&sc->sc_workers, &sc->sc_queue_mtx, PRIBIO,
"geli:suspend", 0);
}
/*
* Clear sensitive data on suspend, they will be recovered on resume.
*/
bzero(sc->sc_mkey, sizeof(sc->sc_mkey));
bzero(sc->sc_ekeys,
sc->sc_nekeys * (sizeof(uint8_t *) + G_ELI_DATAKEYLEN));
free(sc->sc_ekeys, M_ELI);
sc->sc_ekeys = NULL;
bzero(sc->sc_akey, sizeof(sc->sc_akey));
bzero(&sc->sc_akeyctx, sizeof(sc->sc_akeyctx));
bzero(sc->sc_ivkey, sizeof(sc->sc_ivkey));
bzero(&sc->sc_ivctx, sizeof(sc->sc_ivctx));
mtx_unlock(&sc->sc_queue_mtx);
G_ELI_DEBUG(0, "%s has been suspended.", sc->sc_name);
return (0);
}
static void
g_eli_ctl_suspend(struct gctl_req *req, struct g_class *mp)
{
struct g_eli_softc *sc;
int *all, *nargs;
int error;
g_topology_assert();
nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs));
if (nargs == NULL) {
gctl_error(req, "No '%s' argument.", "nargs");
return;
}
all = gctl_get_paraml(req, "all", sizeof(*all));
if (all == NULL) {
gctl_error(req, "No '%s' argument.", "all");
return;
}
if (!*all && *nargs == 0) {
gctl_error(req, "Too few arguments.");
return;
}
if (*all) {
struct g_geom *gp, *gp2;
LIST_FOREACH_SAFE(gp, &mp->geom, geom, gp2) {
sc = gp->softc;
if (sc->sc_flags & G_ELI_FLAG_ONETIME)
continue;
error = g_eli_suspend_one(sc);
if (error != 0)
gctl_error(req, "Not fully done.");
}
} else {
const char *prov;
char param[16];
int i;
for (i = 0; i < *nargs; i++) {
snprintf(param, sizeof(param), "arg%d", i);
prov = gctl_get_asciiparam(req, param);
if (prov == NULL) {
G_ELI_DEBUG(0, "No 'arg%d' argument.", i);
continue;
}
sc = g_eli_find_device(mp, prov);
if (sc == NULL) {
G_ELI_DEBUG(0, "No such provider: %s.", prov);
continue;
}
error = g_eli_suspend_one(sc);
if (error != 0)
gctl_error(req, "Not fully done.");
}
}
}
static void
g_eli_ctl_resume(struct gctl_req *req, struct g_class *mp)
{
struct g_eli_metadata md;
struct g_eli_softc *sc;
struct g_provider *pp;
struct g_consumer *cp;
const char *name;
u_char *key, mkey[G_ELI_DATAIVKEYLEN];
int *nargs, keysize, error;
u_int nkey;
g_topology_assert();
nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs));
if (nargs == NULL) {
gctl_error(req, "No '%s' argument.", "nargs");
return;
}
if (*nargs != 1) {
gctl_error(req, "Invalid number of arguments.");
return;
}
name = gctl_get_asciiparam(req, "arg0");
if (name == NULL) {
gctl_error(req, "No 'arg%u' argument.", 0);
return;
}
sc = g_eli_find_device(mp, name);
if (sc == NULL) {
gctl_error(req, "Provider %s is invalid.", name);
return;
}
if (!(sc->sc_flags & G_ELI_FLAG_SUSPEND)) {
gctl_error(req, "Provider %s not suspended.", name);
return;
}
cp = LIST_FIRST(&sc->sc_geom->consumer);
pp = cp->provider;
error = g_eli_read_metadata(mp, pp, &md);
if (error != 0) {
gctl_error(req, "Cannot read metadata from %s (error=%d).",
name, error);
return;
}
if (md.md_keys == 0x00) {
bzero(&md, sizeof(md));
gctl_error(req, "No valid keys on %s.", pp->name);
return;
}
key = gctl_get_param(req, "key", &keysize);
if (key == NULL || keysize != G_ELI_USERKEYLEN) {
bzero(&md, sizeof(md));
gctl_error(req, "No '%s' argument.", "key");
return;
}
error = g_eli_mkey_decrypt(&md, key, mkey, &nkey);
bzero(key, keysize);
if (error == -1) {
bzero(&md, sizeof(md));
gctl_error(req, "Wrong key for %s.", pp->name);
return;
} else if (error > 0) {
bzero(&md, sizeof(md));
gctl_error(req, "Cannot decrypt Master Key for %s (error=%d).",
pp->name, error);
return;
}
G_ELI_DEBUG(1, "Using Master Key %u for %s.", nkey, pp->name);
mtx_lock(&sc->sc_queue_mtx);
/* Restore sc_mkey, sc_ekeys, sc_akey and sc_ivkey. */
g_eli_mkey_propagate(sc, mkey);
bzero(mkey, sizeof(mkey));
bzero(&md, sizeof(md));
/* Restore sc_akeyctx. */
if (sc->sc_flags & G_ELI_FLAG_AUTH) {
SHA256_Init(&sc->sc_akeyctx);
SHA256_Update(&sc->sc_akeyctx, sc->sc_akey,
sizeof(sc->sc_akey));
}
/* Restore sc_ivctx. */
switch (sc->sc_ealgo) {
case CRYPTO_AES_XTS:
break;
default:
SHA256_Init(&sc->sc_ivctx);
SHA256_Update(&sc->sc_ivctx, sc->sc_ivkey,
sizeof(sc->sc_ivkey));
break;
}
sc->sc_flags &= ~G_ELI_FLAG_SUSPEND;
mtx_unlock(&sc->sc_queue_mtx);
G_ELI_DEBUG(1, "Resumed %s.", pp->name);
wakeup(sc);
}
static int
g_eli_kill_one(struct g_eli_softc *sc)
{
struct g_provider *pp;
struct g_consumer *cp;
int error = 0;
g_topology_assert();
if (sc == NULL)
return (ENOENT);
pp = LIST_FIRST(&sc->sc_geom->provider);
g_error_provider(pp, ENXIO);
cp = LIST_FIRST(&sc->sc_geom->consumer);
pp = cp->provider;
if (sc->sc_flags & G_ELI_FLAG_RO) {
G_ELI_DEBUG(0, "WARNING: Metadata won't be erased on read-only "
"provider: %s.", pp->name);
} else {
u_char *sector;
u_int i;
int err;
sector = malloc(pp->sectorsize, M_ELI, M_WAITOK);
for (i = 0; i <= g_eli_overwrites; i++) {
if (i == g_eli_overwrites)
bzero(sector, pp->sectorsize);
else
arc4rand(sector, pp->sectorsize, 0);
err = g_write_data(cp, pp->mediasize - pp->sectorsize,
sector, pp->sectorsize);
if (err != 0) {
G_ELI_DEBUG(0, "Cannot erase metadata on %s "
"(error=%d).", pp->name, err);
if (error == 0)
error = err;
}
/*
* Flush write cache so we don't overwrite data N times
* in cache and only once on disk.
*/
(void)g_io_flush(cp);
}
free(sector, M_ELI);
}
if (error == 0)
G_ELI_DEBUG(0, "%s has been killed.", pp->name);
Bring in geli suspend/resume functionality (finally). Before this change if you wanted to suspend your laptop and be sure that your encryption keys are safe, you had to stop all processes that use file system stored on encrypted device, unmount the file system and detach geli provider. This isn't very handy. If you are a lucky user of a laptop where suspend/resume actually works with FreeBSD (I'm not!) you most likely want to suspend your laptop, because you don't want to start everything over again when you turn your laptop back on. And this is where geli suspend/resume steps in. When you execute: # geli suspend -a geli will wait for all in-flight I/O requests, suspend new I/O requests, remove all geli sensitive data from the kernel memory (like encryption keys) and will wait for either 'geli resume' or 'geli detach'. Now with no keys in memory you can suspend your laptop without stopping any processes or unmounting any file systems. When you resume your laptop you have to resume geli devices using 'geli resume' command. You need to provide your passphrase, etc. again so the keys can be restored and suspended I/O requests released. Of course you need to remember that 'geli suspend' won't clear file system cache and other places where data from your geli-encrypted file system might be present. But to get rid of those stopping processes and unmounting file system won't help either - you have to turn your laptop off. Be warned. Also note, that suspending geli device which contains file system with geli utility (or anything used by 'geli resume') is not very good idea, as you won't be able to resume it - when you execute geli(8), the kernel will try to read it and this read I/O request will be suspended.
2010-10-20 20:50:55 +00:00
g_eli_destroy(sc, TRUE);
return (error);
}
static void
g_eli_ctl_kill(struct gctl_req *req, struct g_class *mp)
{
int *all, *nargs;
int error;
g_topology_assert();
nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs));
if (nargs == NULL) {
gctl_error(req, "No '%s' argument.", "nargs");
return;
}
all = gctl_get_paraml(req, "all", sizeof(*all));
if (all == NULL) {
gctl_error(req, "No '%s' argument.", "all");
return;
}
if (!*all && *nargs == 0) {
gctl_error(req, "Too few arguments.");
return;
}
if (*all) {
struct g_geom *gp, *gp2;
LIST_FOREACH_SAFE(gp, &mp->geom, geom, gp2) {
error = g_eli_kill_one(gp->softc);
if (error != 0)
gctl_error(req, "Not fully done.");
}
} else {
struct g_eli_softc *sc;
const char *prov;
char param[16];
int i;
for (i = 0; i < *nargs; i++) {
snprintf(param, sizeof(param), "arg%d", i);
prov = gctl_get_asciiparam(req, param);
if (prov == NULL) {
G_ELI_DEBUG(0, "No 'arg%d' argument.", i);
continue;
}
sc = g_eli_find_device(mp, prov);
if (sc == NULL) {
G_ELI_DEBUG(0, "No such provider: %s.", prov);
continue;
}
error = g_eli_kill_one(sc);
if (error != 0)
gctl_error(req, "Not fully done.");
}
}
}
void
g_eli_config(struct gctl_req *req, struct g_class *mp, const char *verb)
{
uint32_t *version;
g_topology_assert();
version = gctl_get_paraml(req, "version", sizeof(*version));
if (version == NULL) {
gctl_error(req, "No '%s' argument.", "version");
return;
}
if (*version != G_ELI_VERSION) {
gctl_error(req, "Userland and kernel parts are out of sync.");
return;
}
if (strcmp(verb, "attach") == 0)
g_eli_ctl_attach(req, mp);
else if (strcmp(verb, "detach") == 0 || strcmp(verb, "stop") == 0)
g_eli_ctl_detach(req, mp);
else if (strcmp(verb, "onetime") == 0)
g_eli_ctl_onetime(req, mp);
else if (strcmp(verb, "configure") == 0)
g_eli_ctl_configure(req, mp);
else if (strcmp(verb, "setkey") == 0)
g_eli_ctl_setkey(req, mp);
else if (strcmp(verb, "delkey") == 0)
g_eli_ctl_delkey(req, mp);
Bring in geli suspend/resume functionality (finally). Before this change if you wanted to suspend your laptop and be sure that your encryption keys are safe, you had to stop all processes that use file system stored on encrypted device, unmount the file system and detach geli provider. This isn't very handy. If you are a lucky user of a laptop where suspend/resume actually works with FreeBSD (I'm not!) you most likely want to suspend your laptop, because you don't want to start everything over again when you turn your laptop back on. And this is where geli suspend/resume steps in. When you execute: # geli suspend -a geli will wait for all in-flight I/O requests, suspend new I/O requests, remove all geli sensitive data from the kernel memory (like encryption keys) and will wait for either 'geli resume' or 'geli detach'. Now with no keys in memory you can suspend your laptop without stopping any processes or unmounting any file systems. When you resume your laptop you have to resume geli devices using 'geli resume' command. You need to provide your passphrase, etc. again so the keys can be restored and suspended I/O requests released. Of course you need to remember that 'geli suspend' won't clear file system cache and other places where data from your geli-encrypted file system might be present. But to get rid of those stopping processes and unmounting file system won't help either - you have to turn your laptop off. Be warned. Also note, that suspending geli device which contains file system with geli utility (or anything used by 'geli resume') is not very good idea, as you won't be able to resume it - when you execute geli(8), the kernel will try to read it and this read I/O request will be suspended.
2010-10-20 20:50:55 +00:00
else if (strcmp(verb, "suspend") == 0)
g_eli_ctl_suspend(req, mp);
else if (strcmp(verb, "resume") == 0)
g_eli_ctl_resume(req, mp);
else if (strcmp(verb, "kill") == 0)
g_eli_ctl_kill(req, mp);
else
gctl_error(req, "Unknown verb.");
}