Date: Sat, 7 Mar 2015 18:01:31 -0600 From: "Matthew D. Fuller" <fullermd@over-yonder.net> To: freebsd-geom@freebsd.org Subject: RFC: Pass TRIM through GELI Message-ID: <20150308000131.GP1742@over-yonder.net>
next in thread | raw e-mail | index | archive | help
[-- Attachment #1 --] [ bcc'd to -fs because it's been discussed there in the past; followup to -geom because that seems where it belongs ] I've done a quick implementation of TRIM passthru support for GELI. Patch attached is against late Feb -CURRENT; however, a glance at svn suggests this code hasn't changed much, so it's probably pretty portable forward and back. It applies cleanly to a stable/10 tree though I haven't tried compiling or using it there. This has been lightly tested and seems to work fine. It adds a '-t' flag to init and onetime to enable passthru on the new provider, and '-t/-T' options to configure to en/disable on existing (but see caveat below about metadata versions; as written, you can't use this on existing partitions). Since all we're doing is passing it through instead of denying it, I'd expect "seems to work" to be pretty much the same as "does work"; the requests go through, space clears up, and messing with a little ZFS on top of it doesn't show up any data corruption. The patch to gnop in <https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198405> is a useful adjunct in checking when/if the requests actually go through, by making the .eli on top of the .nop and seeing when the counters tick. In this implementation, I added a new G_ELI_VERSION and required it for setting the flag. I actually think this is probably not necessary; all we do is set a value in the flags field, and if it's loaded onto a system that doesn't know what to do with that value, it'll just do nothing, which IMO is fine. And since there is no `geli upgrade`, this means that you can't enable it on any existing partitions, but have to kill/init from scratch, which isn't very user-friendly. So I propose that it actually be done sans those version changes, but they are in the patch as attached for now. One not-quite-related change in here is that I added a denial for 'geli configure' attempts on onetime providers. Trying this causes a panic, as the metadata version field on onetime providers is nonsensical. As far as I can tell in quick testing, this isn't related to my changes; it happens with stock GELI code as well. Presumably it's never been noticed before since the only prior use for 'geli configure' was to un/set the BOOT flag, which makes no sense on a onetime anyway, so nobody ever bothered trying. But with -[tT], somebody might try. Evidence: I did, and found the panic :) -- Matthew Fuller (MF4839) | fullermd@over-yonder.net Systems/Network Administrator | http://www.over-yonder.net/~fullermd/ On the Internet, nobody can hear you scream. [-- Attachment #2 --] Index: sbin/geom/class/eli/geli.8 =================================================================== --- sbin/geom/class/eli/geli.8 (revision 279210) +++ sbin/geom/class/eli/geli.8 (working copy) @@ -51,7 +51,7 @@ .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 @@ .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 @@ .Ar prov .Nm .Cm configure -.Op Fl bB +.Op Fl bBtT .Ar prov ... .Nm .Cm setkey @@ -351,6 +351,12 @@ 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 +Pass through BIO_DELETE calls (i.e., TRIM/UNMAP). +This can be useful when encrypting on a SSD, by allowing it to clean up +sectors you're no longer using. +It will, however, provide some information (how much space you're actually +using, and which sectors contain real data) to an attacker. .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 +462,11 @@ For more information, see the description of the .Cm init subcommand. +.It Fl t +Enable 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 +480,13 @@ 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 Index: sbin/geom/class/eli/geom_eli.c =================================================================== --- sbin/geom/class/eli/geom_eli.c (revision 279210) +++ sbin/geom/class/eli/geom_eli.c (working copy) @@ -114,10 +114,11 @@ { 'l', "keylen", "0", G_TYPE_NUMBER }, { 'P', "nonewpassphrase", NULL, G_TYPE_BOOL }, { 's', "sectorsize", "0", G_TYPE_NUMBER }, + { 't', "trim", 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 @@ { 'e', "ealgo", GELI_ENC_ALGO, G_TYPE_STRING }, { 'l', "keylen", "0", G_TYPE_NUMBER }, { 's', "sectorsize", "0", G_TYPE_NUMBER }, + { 't', "trim", 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,14 @@ md.md_flags = 0; if (gctl_get_int(req, "boot")) md.md_flags |= G_ELI_FLAG_BOOT; + if (gctl_get_int(req, "trim")) { + if (version < G_ELI_VERSION_08) { + gctl_error(req, "TRIM is supported starting from version %u.", + G_ELI_VERSION_08); + return; + } + md.md_flags |= G_ELI_FLAG_DELETE; + } md.md_ealgo = CRYPTO_ALGORITHM_MIN - 1; str = gctl_get_ascii(req, "aalgo"); if (*str != '\0') { @@ -899,26 +911,50 @@ } 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; + changed = 1; + } + + if (trim == 1 && (md.md_flags & G_ELI_FLAG_DELETE)) { + if (verbose) + printf("TRIM flag already configured for %s.\n", prov); + } else if (trim == 0 && !(md.md_flags & G_ELI_FLAG_DELETE)) { + if (verbose) + printf("TRIM flag not configured for %s.\n", prov); + } else if (trim >= 0) { + if(md.md_version < G_ELI_VERSION_08) { + printf("TRIM requires version %u, %s is %u\n", G_ELI_VERSION_08, + prov, md.md_version); + } else { + if (trim) + md.md_flags |= G_ELI_FLAG_DELETE; + else + md.md_flags &= ~G_ELI_FLAG_DELETE; + changed = 1; + } + } + + if(changed) eli_metadata_store(req, prov, &md); - } bzero(&md, sizeof(md)); } @@ -926,7 +962,8 @@ 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 +974,30 @@ 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 +1008,7 @@ 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); } } Index: sys/geom/eli/g_eli.c =================================================================== --- sys/geom/eli/g_eli.c (revision 279210) +++ sys/geom/eli/g_eli.c (working copy) @@ -314,8 +314,11 @@ /* * We could eventually support BIO_DELETE request. * It could be done by overwritting requested sector with - * random data g_eli_overwrites number of times. + * random data g_eli_overwrites number of times. Or if the user + * has set the DELETE flag, we just pass it down the stack. */ + if (sc->sc_flags & G_ELI_FLAG_DELETE) + break; default: g_io_deliver(bp, EOPNOTSUPP); return; @@ -342,6 +345,7 @@ 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; @@ -1245,6 +1249,7 @@ 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_DELETE, "DELETE"); #undef ADD_FLAG } sbuf_printf(sb, "</Flags>\n"); Index: sys/geom/eli/g_eli.h =================================================================== --- sys/geom/eli/g_eli.h (revision 279210) +++ sys/geom/eli/g_eli.h (working copy) @@ -70,6 +70,7 @@ * G_ELI_FLAG_FIRST_KEY flag will be set for older versions). * 7 - Encryption keys are now generated from the Data Key and not from the * IV Key (the G_ELI_FLAG_ENC_IVKEY flag will be set for older versions). + * 8 - Added G_ELI_FLAG_DELETE. */ #define G_ELI_VERSION_00 0 #define G_ELI_VERSION_01 1 @@ -79,7 +80,8 @@ #define G_ELI_VERSION_05 5 #define G_ELI_VERSION_06 6 #define G_ELI_VERSION_07 7 -#define G_ELI_VERSION G_ELI_VERSION_07 +#define G_ELI_VERSION_08 8 +#define G_ELI_VERSION G_ELI_VERSION_08 /* ON DISK FLAGS. */ /* Use random, onetime keys. */ @@ -94,6 +96,8 @@ #define G_ELI_FLAG_AUTH 0x00000010 /* Provider is read-only, we should deny all write attempts. */ #define G_ELI_FLAG_RO 0x00000020 +/* Pass through BIO_DELETE requests */ +#define G_ELI_FLAG_DELETE 0x00000040 /* RUNTIME FLAGS. */ /* Provider was open for writing. */ #define G_ELI_FLAG_WOPEN 0x00010000 @@ -242,7 +246,7 @@ *datap = p; } static __inline void -eli_metadata_encode_v1v2v3v4v5v6v7(struct g_eli_metadata *md, u_char **datap) +eli_metadata_encode_v1_to_8(struct g_eli_metadata *md, u_char **datap) { u_char *p; @@ -281,7 +285,8 @@ case G_ELI_VERSION_05: case G_ELI_VERSION_06: case G_ELI_VERSION_07: - eli_metadata_encode_v1v2v3v4v5v6v7(md, &p); + case G_ELI_VERSION_08: + eli_metadata_encode_v1_to_8(md, &p); break; default: #ifdef _KERNEL @@ -321,7 +326,7 @@ } static __inline int -eli_metadata_decode_v1v2v3v4v5v6v7(const u_char *data, struct g_eli_metadata *md) +eli_metadata_decode_v1_to_8(const u_char *data, struct g_eli_metadata *md) { MD5_CTX ctx; const u_char *p; @@ -364,7 +369,8 @@ case G_ELI_VERSION_05: case G_ELI_VERSION_06: case G_ELI_VERSION_07: - error = eli_metadata_decode_v1v2v3v4v5v6v7(data, md); + case G_ELI_VERSION_08: + error = eli_metadata_decode_v1_to_8(data, md); break; default: error = EOPNOTSUPP; Index: sys/geom/eli/g_eli_ctl.c =================================================================== --- sys/geom/eli/g_eli_ctl.c (revision 279210) +++ sys/geom/eli/g_eli_ctl.c (working copy) @@ -236,7 +236,7 @@ const char *name; intmax_t *keylen, *sectorsize; u_char mkey[G_ELI_DATAIVKEYLEN]; - int *nargs, *detach; + int *nargs, *detach, *trim; g_topology_assert(); bzero(&md, sizeof(md)); @@ -256,6 +256,11 @@ gctl_error(req, "No '%s' argument.", "detach"); return; } + trim = gctl_get_paraml(req, "trim", sizeof(*trim)); + if (detach == NULL) { + gctl_error(req, "No '%s' argument.", "trim"); + return; + } strlcpy(md.md_magic, G_ELI_MAGIC, sizeof(md.md_magic)); md.md_version = G_ELI_VERSION; @@ -262,6 +267,8 @@ md.md_flags |= G_ELI_FLAG_ONETIME; if (*detach) md.md_flags |= G_ELI_FLAG_WO_DETACH; + if (*trim) + md.md_flags |= G_ELI_FLAG_DELETE; md.md_ealgo = CRYPTO_ALGORITHM_MIN - 1; name = gctl_get_asciiparam(req, "aalgo"); @@ -377,11 +384,13 @@ char param[16]; const char *prov; u_char *sector; - int *nargs, *boot, *noboot; + int *nargs, *boot, *noboot, *trim, *notrim; int error; + int changed; u_int i; g_topology_assert(); + changed = 0; nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); if (nargs == NULL) { @@ -407,7 +416,27 @@ 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) { + gctl_error(req, "No '%s' argument.", "trim"); + return; + } + notrim = gctl_get_paraml(req, "notrim", sizeof(*notrim)); + if (notrim == NULL) { + gctl_error(req, "No '%s' argument.", "notrim"); + return; + } + 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 +458,41 @@ "provider %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 (sc->sc_flags & G_ELI_FLAG_ONETIME) { + gctl_error(req, "Cannot change configuration of " + "onetime 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)) { + } else if (*noboot && !(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); + + if (*trim && (sc->sc_flags & G_ELI_FLAG_DELETE)) { + G_ELI_DEBUG(1, "TRIM flag already configured for %s.", + prov); continue; + } else if (*notrim && !(sc->sc_flags & G_ELI_FLAG_DELETE)) { + G_ELI_DEBUG(1, "TRIM flag not configured for %s.", + prov); + continue; + } else if ((*trim || *notrim) && sc->sc_version < G_ELI_VERSION_08) { + gctl_error(req, "TRIM requires version %u, %s is %u.", + G_ELI_VERSION_08, prov, sc->sc_version); + continue; } + cp = LIST_FIRST(&sc->sc_geom->consumer); pp = cp->provider; error = g_eli_read_metadata(mp, pp, &md); @@ -456,11 +506,19 @@ 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 (*trim) { + md.md_flags |= G_ELI_FLAG_DELETE; + sc->sc_flags |= G_ELI_FLAG_DELETE; + } else if (*notrim) { + md.md_flags &= ~G_ELI_FLAG_DELETE; + sc->sc_flags &= ~G_ELI_FLAG_DELETE; + } + 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,
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20150308000131.GP1742>
