Attempt to add multiple device attachment to "geli attach"

Karl Denninger karl at denninger.net
Sun Sep 28 14:24:11 UTC 2014


Yes, I wrote a script to handle it in the meantime as well.  The way the
geli command is architected it is rather difficult to do properly in the
code itself....

On 9/28/2014 8:15 AM, Pawel Jakub Dawidek wrote:
> Hi Karl,
>
> I like the idea. I myself currently use a script which obtains the
> passphrase:
>
> 	echo -n "Enter passphrase: "
> 	stty -echo
> 	read pass
> 	stty echo
> 	echo
>
> And then iterates over all providers I want to attach and uses -j option
> to provide the key for the attach subcommand.
>
> To make it integral part of geli and to be complete we would need to add
> support for multiple providers to the following subcommands: init,
> attach, onetime, setkey, delkey and resume.
>
> On Wed, Sep 03, 2014 at 02:33:18PM -0500, Karl Denninger wrote:
>> Never mind... I know what I missed -- the key generation that is passed 
>> in is dependent on the metadata read from the userspace.
>>
>> More work to do here.... will have to pass a separate key structure for 
>> each disk and it will also require some more work in the userspace 
>> command area so it doesn't prompt a second time for a password.
>>
>> I'll post the completed patch set once I have it if people here think it 
>> would be interesting.
>>
>> On 9/3/2014 14:13, Karl Denninger wrote:
>>> I'm aware of the potential issues here in terms of keying risks, but 
>>> there are plenty of reasons to support this capability with the 
>>> largest one being ZFS volumes that you wish to run encrypted.
>>>
>>> Take the following:
>>>
>>> label/pool0
>>> label/pool1
>>> label/pool2
>>> label/pool3
>>>
>>> (all relative to /dev, of course)
>>>
>>> These are all gpt partitions on different devices (typically full 
>>> disks less labels.)  You "geli init" them and then attach them and 
>>> build a raidz2 pool on that.
>>>
>>> OK, now the system is rebooted.  If you use the rc.conf file's option 
>>> to request geli passwords during the boot you had better not screw up 
>>> three times for only ONE of these volumes or the pool WILL come up 
>>> degraded!  Needless to say that's not nice.  It's even worse if it's a 
>>> raidz pool, you blow it, you reattach that disk and allow it to 
>>> resilver *and take a disk error on the remaining drives during the 
>>> resilver* -- now you're completely hosed.
>>>
>>> So, here's the idea -- if you use the same password and/or keyfile for 
>>> ALL of the volumes then either they ALL mount (if you get it right) or 
>>> NONE of them mount (if you get it wrong.)  Now the pool won't import 
>>> if you get it wrong and you're safe from the risk of taking a forced 
>>> resilver and potential data loss.
>>>
>>> The geom subclass command has a simple "nargs" test (must be "1") in 
>>> the attach command; I replaced that with "nargs < 1" for the error 
>>> case.  Now I can pass multiple devices to the kernel's geom handler 
>>> and they get passed to the kernel ctl handler.
>>>
>>> The following patch should, I believe, work -- but it doesn't. The 
>>> first disk attaches but the second one that was init'd with the same 
>>> passphrase fails.
>>>
>>> As near as I can tell the key components are not picked up off the 
>>> metadata until the geom driver gets ahold of it -- and thus the second 
>>> decryption attempt should work since on the second iteration through 
>>> the code grabs the key parameters off the request a second time.
>>>
>>> But I'm obviously missing something because the second volume returns 
>>> "Wrong key for ...."
>>>
>>> Ideas?
>>>
>>> Patch is relative to /usr/src/sys/geom/eli:
>>>
>>> *** g_eli_ctl.c.orig    Wed Sep  3 13:11:52 2014
>>> --- g_eli_ctl.c    Wed Sep  3 13:19:15 2014
>>> ***************
>>> *** 60,65 ****
>>> --- 60,68 ----
>>>       int *nargs, *detach, *readonly;
>>>       int keysize, error;
>>>       u_int nkey;
>>> +     char param[16];
>>> +
>>> +     u_int count;
>>>
>>>       g_topology_assert();
>>>
>>> ***************
>>> *** 68,74 ****
>>>           gctl_error(req, "No '%s' argument.", "nargs");
>>>           return;
>>>       }
>>> !     if (*nargs != 1) {
>>>           gctl_error(req, "Invalid number of arguments.");
>>>           return;
>>>       }
>>> --- 71,77 ----
>>>           gctl_error(req, "No '%s' argument.", "nargs");
>>>           return;
>>>       }
>>> !     if (*nargs < 1) {
>>>           gctl_error(req, "Invalid number of arguments.");
>>>           return;
>>>       }
>>> ***************
>>> *** 84,147 ****
>>>           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 *
>>> --- 87,152 ----
>>>           gctl_error(req, "No '%s' argument.", "readonly");
>>>           return;
>>>       }
>>> +     for (count = 0; count < *nargs; count++) {
>>> +         snprintf(param, sizeof(param), "arg%d", count);
>>> +         name = gctl_get_asciiparam(req, param);
>>> +         if (name == NULL) {
>>> +             gctl_error(req, "No 'arg%u' argument.", count);
>>> +             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 *
>>>
>>>
>>> ------------------------
>>>
>> -- 
>> -- Karl
>> karl at denninger.net
>>
>>
>
>

-- 
Karl Denninger
karl at denninger.net <mailto:karl at denninger.net>
/The Market Ticker/
-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/pkcs7-signature
Size: 2711 bytes
Desc: S/MIME Cryptographic Signature
URL: <http://lists.freebsd.org/pipermail/freebsd-geom/attachments/20140928/e4ee391f/attachment.bin>


More information about the freebsd-geom mailing list