Enable BIO_DELETE passthru in GELI, so TRIM/UNMAP can work as expected when

GELI is used on a SSD or inside virtual machine, so that guest can tell
host that it is no longer using some of the storage.

Enabling BIO_DELETE passthru comes with a small security consequence - an
attacker can tell how much space is being really used on encrypted device and
has less data no analyse then. This is why the -T option can be given to the
init subcommand to turn off this behaviour and -t/T options for the configure
subcommand can be used to adjust this setting later.

PR:		198863
Submitted by:	Matthew D. Fuller fullermd at over-yonder dot net

This commit also includes a fix from Fabian Keil freebsd-listen at
fabiankeil.de for 'configure' on onetime providers which is not strictly
related, but is entangled in the same code, so would cause conflicts if
separated out.
This commit is contained in:
pjd 2015-08-08 09:51:38 +00:00
parent 349429fe82
commit 2ae822a47d
5 changed files with 178 additions and 54 deletions

View File

@ -24,7 +24,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd June 2, 2015
.Dd July 10, 2015
.Dt GELI 8
.Os
.Sh NAME
@ -51,7 +51,7 @@ utility:
.Pp
.Nm
.Cm init
.Op Fl bPv
.Op Fl bPTv
.Op Fl a Ar aalgo
.Op Fl B Ar backupfile
.Op Fl e Ar ealgo
@ -80,7 +80,7 @@ utility:
.Cm detach
.Nm
.Cm onetime
.Op Fl d
.Op Fl dT
.Op Fl a Ar aalgo
.Op Fl e Ar ealgo
.Op Fl l Ar keylen
@ -88,7 +88,7 @@ utility:
.Ar prov
.Nm
.Cm configure
.Op Fl bB
.Op Fl bBtT
.Ar prov ...
.Nm
.Cm setkey
@ -351,6 +351,17 @@ Change decrypted provider's sector size.
Increasing the sector size allows increased performance,
because encryption/decryption which requires an initialization vector
is done per sector; fewer sectors means less computational work.
.It Fl T
Don't pass through
.Dv BIO_DELETE
calls (i.e., TRIM/UNMAP).
This can prevent an attacker from knowing how much space you're actually
using and which sectors contain live data, but will also prevent the
backing store (SSD, etc) from reclaiming space you're not using, which
may degrade its performance and lifespan.
The underlying provider may or may not actually obliterate the deleted
sectors when TRIM is enabled, so it should not be considered to add any
security.
.It Fl V Ar version
Metadata version to use.
This option is helpful when creating a provider that may be used by older
@ -456,6 +467,11 @@ Change decrypted provider's sector size.
For more information, see the description of the
.Cm init
subcommand.
.It Fl T
Disable TRIM/UNMAP passthru.
For more information, see the description of the
.Cm init
subcommand.
.El
.It Cm configure
Change configuration of the given providers.
@ -469,6 +485,13 @@ For more information, see the description of the
subcommand.
.It Fl B
Remove the BOOT flag from the given providers.
.It Fl t
Enable TRIM/UNMAP passthru.
For more information, see the description of the
.Cm init
subcommand.
.It Fl T
Disable TRIM/UNMAP passthru.
.El
.It Cm setkey
Install a copy of the Master Key into the selected slot, encrypted with

View File

@ -114,10 +114,11 @@ struct g_command class_commands[] = {
{ 'l', "keylen", "0", G_TYPE_NUMBER },
{ 'P', "nonewpassphrase", NULL, G_TYPE_BOOL },
{ 's', "sectorsize", "0", G_TYPE_NUMBER },
{ 'T', "notrim", NULL, G_TYPE_BOOL },
{ 'V', "mdversion", "-1", G_TYPE_NUMBER },
G_OPT_SENTINEL
},
"[-bPv] [-a aalgo] [-B backupfile] [-e ealgo] [-i iterations] [-l keylen] [-J newpassfile] [-K newkeyfile] [-s sectorsize] [-V version] prov"
"[-bPTv] [-a aalgo] [-B backupfile] [-e ealgo] [-i iterations] [-l keylen] [-J newpassfile] [-K newkeyfile] [-s sectorsize] [-V version] prov"
},
{ "label", G_FLAG_VERBOSE, eli_main,
{
@ -170,17 +171,20 @@ struct g_command class_commands[] = {
{ 'e', "ealgo", GELI_ENC_ALGO, G_TYPE_STRING },
{ 'l', "keylen", "0", G_TYPE_NUMBER },
{ 's', "sectorsize", "0", G_TYPE_NUMBER },
{ 'T', "notrim", NULL, G_TYPE_BOOL },
G_OPT_SENTINEL
},
"[-d] [-a aalgo] [-e ealgo] [-l keylen] [-s sectorsize] prov"
"[-dT] [-a aalgo] [-e ealgo] [-l keylen] [-s sectorsize] prov"
},
{ "configure", G_FLAG_VERBOSE, eli_main,
{
{ 'b', "boot", NULL, G_TYPE_BOOL },
{ 'B', "noboot", NULL, G_TYPE_BOOL },
{ 't', "trim", NULL, G_TYPE_BOOL },
{ 'T', "notrim", NULL, G_TYPE_BOOL },
G_OPT_SENTINEL
},
"[-bB] prov ..."
"[-bBtT] prov ..."
},
{ "setkey", G_FLAG_VERBOSE, eli_main,
{
@ -698,6 +702,8 @@ eli_init(struct gctl_req *req)
md.md_flags = 0;
if (gctl_get_int(req, "boot"))
md.md_flags |= G_ELI_FLAG_BOOT;
if (gctl_get_int(req, "notrim"))
md.md_flags |= G_ELI_FLAG_NODELETE;
md.md_ealgo = CRYPTO_ALGORITHM_MIN - 1;
str = gctl_get_ascii(req, "aalgo");
if (*str != '\0') {
@ -899,26 +905,45 @@ eli_attach(struct gctl_req *req)
}
static void
eli_configure_detached(struct gctl_req *req, const char *prov, bool boot)
eli_configure_detached(struct gctl_req *req, const char *prov, int boot,
int trim)
{
struct g_eli_metadata md;
bool changed = 0;
if (eli_metadata_read(req, prov, &md) == -1)
return;
if (boot && (md.md_flags & G_ELI_FLAG_BOOT)) {
if (boot == 1 && (md.md_flags & G_ELI_FLAG_BOOT)) {
if (verbose)
printf("BOOT flag already configured for %s.\n", prov);
} else if (!boot && !(md.md_flags & G_ELI_FLAG_BOOT)) {
} else if (boot == 0 && !(md.md_flags & G_ELI_FLAG_BOOT)) {
if (verbose)
printf("BOOT flag not configured for %s.\n", prov);
} else {
} else if (boot >= 0) {
if (boot)
md.md_flags |= G_ELI_FLAG_BOOT;
else
md.md_flags &= ~G_ELI_FLAG_BOOT;
eli_metadata_store(req, prov, &md);
changed = 1;
}
if (trim == 0 && (md.md_flags & G_ELI_FLAG_NODELETE)) {
if (verbose)
printf("TRIM disable flag already configured for %s.\n", prov);
} else if (trim == 1 && !(md.md_flags & G_ELI_FLAG_NODELETE)) {
if (verbose)
printf("TRIM disable flag not configured for %s.\n", prov);
} else if (trim >= 0) {
if (trim)
md.md_flags &= ~G_ELI_FLAG_NODELETE;
else
md.md_flags |= G_ELI_FLAG_NODELETE;
changed = 1;
}
if (changed)
eli_metadata_store(req, prov, &md);
bzero(&md, sizeof(md));
}
@ -926,7 +951,8 @@ static void
eli_configure(struct gctl_req *req)
{
const char *prov;
bool boot, noboot;
bool boot, noboot, trim, notrim;
int doboot, dotrim;
int i, nargs;
nargs = gctl_get_int(req, "nargs");
@ -937,12 +963,30 @@ eli_configure(struct gctl_req *req)
boot = gctl_get_int(req, "boot");
noboot = gctl_get_int(req, "noboot");
trim = gctl_get_int(req, "trim");
notrim = gctl_get_int(req, "notrim");
doboot = -1;
if (boot && noboot) {
gctl_error(req, "Options -b and -B are mutually exclusive.");
return;
}
if (!boot && !noboot) {
if (boot)
doboot = 1;
else if (noboot)
doboot = 0;
dotrim = -1;
if (trim && notrim) {
gctl_error(req, "Options -t and -T are mutually exclusive.");
return;
}
if (trim)
dotrim = 1;
else if (notrim)
dotrim = 0;
if (doboot == -1 && dotrim == -1) {
gctl_error(req, "No option given.");
return;
}
@ -953,7 +997,7 @@ eli_configure(struct gctl_req *req)
for (i = 0; i < nargs; i++) {
prov = gctl_get_ascii(req, "arg%d", i);
if (!eli_is_attached(prov))
eli_configure_detached(req, prov, boot);
eli_configure_detached(req, prov, doboot, dotrim);
}
}

View File

@ -312,10 +312,15 @@ g_eli_start(struct bio *bp)
break;
case BIO_DELETE:
/*
* We could eventually support BIO_DELETE request.
* It could be done by overwritting requested sector with
* random data g_eli_overwrites number of times.
* If the user hasn't set the NODELETE flag, we just pass
* it down the stack and let the layers beneath us do (or
* not) whatever they do with it. If they have, we
* reject it. A possible extension would be an
* additional flag to take it as a hint to shred the data
* with [multiple?] overwrites.
*/
if (!(sc->sc_flags & G_ELI_FLAG_NODELETE))
break;
default:
g_io_deliver(bp, EOPNOTSUPP);
return;
@ -342,6 +347,7 @@ g_eli_start(struct bio *bp)
break;
case BIO_GETATTR:
case BIO_FLUSH:
case BIO_DELETE:
cbp->bio_done = g_std_done;
cp = LIST_FIRST(&sc->sc_geom->consumer);
cbp->bio_to = cp->provider;
@ -1255,6 +1261,7 @@ g_eli_dumpconf(struct sbuf *sb, const char *indent, struct g_geom *gp,
ADD_FLAG(G_ELI_FLAG_WOPEN, "W-OPEN");
ADD_FLAG(G_ELI_FLAG_DESTROY, "DESTROY");
ADD_FLAG(G_ELI_FLAG_RO, "READ-ONLY");
ADD_FLAG(G_ELI_FLAG_NODELETE, "NODELETE");
#undef ADD_FLAG
}
sbuf_printf(sb, "</Flags>\n");

View File

@ -94,6 +94,8 @@
#define G_ELI_FLAG_AUTH 0x00000010
/* Provider is read-only, we should deny all write attempts. */
#define G_ELI_FLAG_RO 0x00000020
/* Don't pass through BIO_DELETE requests. */
#define G_ELI_FLAG_NODELETE 0x00000040
/* RUNTIME FLAGS. */
/* Provider was open for writing. */
#define G_ELI_FLAG_WOPEN 0x00010000

View File

@ -236,7 +236,7 @@ g_eli_ctl_onetime(struct gctl_req *req, struct g_class *mp)
const char *name;
intmax_t *keylen, *sectorsize;
u_char mkey[G_ELI_DATAIVKEYLEN];
int *nargs, *detach;
int *nargs, *detach, *notrim;
g_topology_assert();
bzero(&md, sizeof(md));
@ -251,17 +251,16 @@ g_eli_ctl_onetime(struct gctl_req *req, struct g_class *mp)
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)
detach = gctl_get_paraml(req, "detach", sizeof(*detach));
if (detach != NULL && *detach)
md.md_flags |= G_ELI_FLAG_WO_DETACH;
notrim = gctl_get_paraml(req, "notrim", sizeof(*notrim));
if (notrim != NULL && *notrim)
md.md_flags |= G_ELI_FLAG_NODELETE;
md.md_ealgo = CRYPTO_ALGORITHM_MIN - 1;
name = gctl_get_asciiparam(req, "aalgo");
@ -377,12 +376,15 @@ g_eli_ctl_configure(struct gctl_req *req, struct g_class *mp)
char param[16];
const char *prov;
u_char *sector;
int *nargs, *boot, *noboot;
int error;
int *nargs, *boot, *noboot, *trim, *notrim;
int zero, error, changed;
u_int i;
g_topology_assert();
changed = 0;
zero = 0;
nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs));
if (nargs == NULL) {
gctl_error(req, "No '%s' argument.", "nargs");
@ -394,20 +396,32 @@ g_eli_ctl_configure(struct gctl_req *req, struct g_class *mp)
}
boot = gctl_get_paraml(req, "boot", sizeof(*boot));
if (boot == NULL) {
gctl_error(req, "No '%s' argument.", "boot");
return;
}
if (boot == NULL)
boot = &zero;
noboot = gctl_get_paraml(req, "noboot", sizeof(*noboot));
if (noboot == NULL) {
gctl_error(req, "No '%s' argument.", "noboot");
return;
}
if (noboot == NULL)
noboot = &zero;
if (*boot && *noboot) {
gctl_error(req, "Options -b and -B are mutually exclusive.");
return;
}
if (!*boot && !*noboot) {
if (*boot || *noboot)
changed = 1;
trim = gctl_get_paraml(req, "trim", sizeof(*trim));
if (trim == NULL)
trim = &zero;
notrim = gctl_get_paraml(req, "notrim", sizeof(*notrim));
if (notrim == NULL)
notrim = &zero;
if (*trim && *notrim) {
gctl_error(req, "Options -t and -T are mutually exclusive.");
return;
}
if (*trim || *notrim)
changed = 1;
if (!changed) {
gctl_error(req, "No option given.");
return;
}
@ -429,20 +443,40 @@ g_eli_ctl_configure(struct gctl_req *req, struct g_class *mp)
"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;
}
if (*boot && (sc->sc_flags & G_ELI_FLAG_BOOT)) {
G_ELI_DEBUG(1, "BOOT flag already configured for %s.",
prov);
continue;
} else if (*noboot && !(sc->sc_flags & G_ELI_FLAG_BOOT)) {
G_ELI_DEBUG(1, "BOOT flag not configured for %s.",
prov);
continue;
}
if (*notrim && (sc->sc_flags & G_ELI_FLAG_NODELETE)) {
G_ELI_DEBUG(1, "TRIM disable flag already configured for %s.",
prov);
continue;
} else if (*trim && !(sc->sc_flags & G_ELI_FLAG_NODELETE)) {
G_ELI_DEBUG(1, "TRIM disable flag not configured for %s.",
prov);
continue;
}
if (!(sc->sc_flags & G_ELI_FLAG_ONETIME)) {
/*
* ONETIME providers don't write metadata to
* disk, so don't try reading it. This means
* we're bit-flipping uninitialized memory in md
* below, but that's OK; we don't do anything
* with it later.
*/
cp = LIST_FIRST(&sc->sc_geom->consumer);
pp = cp->provider;
error = g_eli_read_metadata(mp, pp, &md);
@ -452,15 +486,29 @@ g_eli_ctl_configure(struct gctl_req *req, struct g_class *mp)
prov, error);
continue;
}
}
if (*boot) {
md.md_flags |= G_ELI_FLAG_BOOT;
sc->sc_flags |= G_ELI_FLAG_BOOT;
} else {
} else if (*noboot) {
md.md_flags &= ~G_ELI_FLAG_BOOT;
sc->sc_flags &= ~G_ELI_FLAG_BOOT;
}
if (*notrim) {
md.md_flags |= G_ELI_FLAG_NODELETE;
sc->sc_flags |= G_ELI_FLAG_NODELETE;
} else if (*trim) {
md.md_flags &= ~G_ELI_FLAG_NODELETE;
sc->sc_flags &= ~G_ELI_FLAG_NODELETE;
}
if (sc->sc_flags & G_ELI_FLAG_ONETIME) {
/* There's no metadata on disk so we are done here. */
continue;
}
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,