Message ID | 5e762e300535cbb9f04b25a97e1d13fd082f5b0e.1662420176.git.sweettea-kernel@dorminy.me (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | btrfs: add fscrypt integration | expand |
On Mon, Sep 05, 2022 at 08:35:17PM -0400, Sweet Tea Dorminy wrote: > From: Omar Sandoval <osandov@osandov.com> > > Creating several new subvolumes out of snapshots of another subvolume, > each for a different VM's storage, is a important usecase for btrfs. We > would like to give each VM a unique encryption key to use for new writes > to its subvolume, so that secure deletion of the VM's data is as simple > as securely deleting the key; to avoid needing multiple keys in each VM, > we envision the initial subvolume being unencrypted. However, this means > that the snapshot's directories would have a mix of encrypted and > unencrypted files. During lookup with a key, both unencrypted and > encrypted forms of the desired name must be queried. > > To allow this, add another FS_CFLG to allow filesystems to opt into > partially encrypted directories. > > Signed-off-by: Omar Sandoval <osandov@osandov.com> > Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me> Reviewed-by: Josef Bacik <josef@toxicpanda.com> Thanks, Josef
On Mon, Sep 05, 2022 at 08:35:17PM -0400, Sweet Tea Dorminy wrote: > From: Omar Sandoval <osandov@osandov.com> > > Creating several new subvolumes out of snapshots of another subvolume, > each for a different VM's storage, is a important usecase for btrfs. We > would like to give each VM a unique encryption key to use for new writes > to its subvolume, so that secure deletion of the VM's data is as simple > as securely deleting the key; to avoid needing multiple keys in each VM, > we envision the initial subvolume being unencrypted. However, this means > that the snapshot's directories would have a mix of encrypted and > unencrypted files. During lookup with a key, both unencrypted and > encrypted forms of the desired name must be queried. > > To allow this, add another FS_CFLG to allow filesystems to opt into > partially encrypted directories. > > Signed-off-by: Omar Sandoval <osandov@osandov.com> > Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me> > --- > fs/crypto/fname.c | 17 ++++++++++++++++- > include/linux/fscrypt.h | 2 ++ > 2 files changed, 18 insertions(+), 1 deletion(-) I'm still trying to wrap my head around what this part involves exactly. This is a pretty big change in semantics. Could this be moved to the end of the patchset, or is this a fundamental part of the btrfs fscrypt support that the rest of your patchset depends on? I'd think it would be a lot easier to review if this change was an add-on at the end. One thing to keep in mind is that FS_IOC_SET_ENCRYPTION_POLICY failing on nonempty directories can actually be very useful, since it makes it possible to detect bugs where people create files in encrypted directories (expecting that they are encrypted) before an encryption policy actually gets assigned. Since FS_IOC_SET_ENCRYPTION_POLICY fails in that case, such bugs can be detected and fixed. If it succeeds, then everything would just silently work and some files wouldn't be encrypted as intended. That's kind of scary. It might be warranted to use an encryption policy flag to explicitly indicate that mixing encrypted and unencrypted files is being allowed. - Eric
On 06/09/2022 08:35, Sweet Tea Dorminy wrote: > From: Omar Sandoval <osandov@osandov.com> > > Creating several new subvolumes out of snapshots of another subvolume, > each for a different VM's storage, is a important usecase for btrfs. > We > would like to give each VM a unique encryption key to use for new writes > to its subvolume, so that secure deletion of the VM's data is as simple > as securely deleting the key; to avoid needing multiple keys in each VM, > we envision the initial subvolume being unencrypted. In this usecase, you didn't mention if the original subvolume is encrypted, assuming it is not, so this usecase is the same as mentioned in the design document. Now, in this usecase, what happens if the original subvolume is encrypted? Can we still avoid the multiple keys? How is that going to work? Or we don't yet support subvolumes out of snapshots of another encrypted subvolume? Thanks, -Anand > However, this means > that the snapshot's directories would have a mix of encrypted and > unencrypted files. During lookup with a key, both unencrypted and > encrypted forms of the desired name must be queried. > > To allow this, add another FS_CFLG to allow filesystems to opt into > partially encrypted directories. > > Signed-off-by: Omar Sandoval <osandov@osandov.com> > Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me> > --- > fs/crypto/fname.c | 17 ++++++++++++++++- > include/linux/fscrypt.h | 2 ++ > 2 files changed, 18 insertions(+), 1 deletion(-) > > diff --git a/fs/crypto/fname.c b/fs/crypto/fname.c > index 6c092a1533f7..3bdece33e14d 100644 > --- a/fs/crypto/fname.c > +++ b/fs/crypto/fname.c > @@ -414,6 +414,7 @@ int fscrypt_setup_filename(struct inode *dir, const struct qstr *iname, > fname->usr_fname = iname; > > if (!IS_ENCRYPTED(dir) || fscrypt_is_dot_dotdot(iname)) { > +unencrypted: > fname->disk_name.name = (unsigned char *)iname->name; > fname->disk_name.len = iname->len; > return 0; > @@ -448,8 +449,16 @@ int fscrypt_setup_filename(struct inode *dir, const struct qstr *iname, > * user-supplied name > */ > > - if (iname->len > FSCRYPT_NOKEY_NAME_MAX_ENCODED) > + if (iname->len > FSCRYPT_NOKEY_NAME_MAX_ENCODED) { > + /* > + * This isn't a valid nokey name, but it could be an unencrypted > + * name if the filesystem allows partially encrypted > + * directories. > + */ > + if (dir->i_sb->s_cop->flags & FS_CFLG_ALLOW_PARTIAL) > + goto unencrypted; > return -ENOENT; > + } > > fname->crypto_buf.name = kmalloc(FSCRYPT_NOKEY_NAME_MAX, GFP_KERNEL); > if (fname->crypto_buf.name == NULL) > @@ -460,6 +469,12 @@ int fscrypt_setup_filename(struct inode *dir, const struct qstr *iname, > if (ret < (int)offsetof(struct fscrypt_nokey_name, bytes[1]) || > (ret > offsetof(struct fscrypt_nokey_name, sha256) && > ret != FSCRYPT_NOKEY_NAME_MAX)) { > + /* Again, this could be an unencrypted name. */ > + if (dir->i_sb->s_cop->flags & FS_CFLG_ALLOW_PARTIAL) { > + kfree(fname->crypto_buf.name); > + fname->crypto_buf.name = NULL; > + goto unencrypted; > + } > ret = -ENOENT; > goto errout; > } > diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h > index a236d8c6d0da..a4e00314c91b 100644 > --- a/include/linux/fscrypt.h > +++ b/include/linux/fscrypt.h > @@ -102,6 +102,8 @@ struct fscrypt_nokey_name { > * pages for writes and therefore won't need the fscrypt bounce page pool. > */ > #define FS_CFLG_OWN_PAGES (1U << 1) > +/* The filesystem allows partially encrypted directories/files. */ > +#define FS_CFLG_ALLOW_PARTIAL (1U << 2) > > /* Crypto operations for filesystems */ > struct fscrypt_operations {
On Tue, Sep 13, 2022 at 6:21 AM Anand Jain <anand.jain@oracle.com> wrote: > > On 06/09/2022 08:35, Sweet Tea Dorminy wrote: > > From: Omar Sandoval <osandov@osandov.com> > > > > Creating several new subvolumes out of snapshots of another subvolume, > > each for a different VM's storage, is a important usecase for btrfs. > > > We > > would like to give each VM a unique encryption key to use for new writes > > to its subvolume, so that secure deletion of the VM's data is as simple > > as securely deleting the key; to avoid needing multiple keys in each VM, > > we envision the initial subvolume being unencrypted. > > In this usecase, you didn't mention if the original subvolume is > encrypted, assuming it is not, so this usecase is the same as mentioned > in the design document. Now, in this usecase, what happens if the > original subvolume is encrypted? Can we still avoid the multiple keys? > How is that going to work? Or we don't yet support subvolumes out of > snapshots of another encrypted subvolume? > Count this as the first official "email triggered by Plumbers" I suppose, but I've been looking forward to Btrfs encryption for Fedora for a variety of use-cases: * Converting a disk from unencrypted to encrypted without reinstalling[1] * Encrypting system and user data with different credentials[2][3] * Mass managed systems with dual-credential encryption (one rooted in org, other by user)[1] The way I envision Fedora's usage of this feature going is as follows: The system is shipped (OEM install; e.g. Lenovo, Slimbook, etc.) or installed without encryption (from Anaconda). The firstboot process helps the user get setup, and asks if they want disk encryption (provided there is no policy forcing it on). If the answer is yes, then the root subvolume and the home subvolume are configured to be encrypted using the on-board secure enclave (TPM or whatever). When the user creation step occurs, if disk encryption was set on earlier, then the user subvolume inside the home subvolume gets configured to be encrypted using the login credentials. If it wasn't set on earlier, we could also still offer to encrypt the user data here. If this is a mass-managed system, an additional decryption key would be available for the system manager to use in the event of credential loss, damaged TPM, etc. For the cloudy scenario, we'd probably provide tools to orchestrate this from cloud-init for confidential computing stuff too. For all this to work, I expect the following: * Encryption to support *somehow* any number of decryption keys/methods * Nesting of subvolumes with differing encryption methods * The ability to blindly backup and restore subvolumes without needing to decrypt On a personal system, I expect at most two credentials in use: one keyed to the system and one keyed to the user. For a corporate/mass managed system, I expect a third one: one keyed to the management platform. This is necessary because in a lot of jurisdictions, you don't necessarily own the data on your corporate laptop, and the owner has the right to be able to access that data. But a more prosaic or banal reason is that people forget their credentials and being able to reset them without losing all their data is usually a good thing. :) Hopefully I've transcribed what I said properly into this email. :) [1]: https://pagure.io/fedora-btrfs/project/issue/10 [2]: https://pagure.io/fedora-workstation/issue/82 [3]: https://pagure.io/fedora-workstation/issue/136 -- Neal Gompa (FAS: ngompa)
> I'm still trying to wrap my head around what this part involves > exactly. This > is a pretty big change in semantics. > > Could this be moved to the end of the patchset, or is this a > fundamental part of > the btrfs fscrypt support that the rest of your patchset depends on? > I'd think > it would be a lot easier to review if this change was an add-on at the > end. Definitely. > > One thing to keep in mind is that FS_IOC_SET_ENCRYPTION_POLICY failing > on > nonempty directories can actually be very useful, since it makes it > possible to > detect bugs where people create files in encrypted directories > (expecting that > they are encrypted) before an encryption policy actually gets assigned. > Since > FS_IOC_SET_ENCRYPTION_POLICY fails in that case, such bugs can be > detected and > fixed. I agree that this has risks of inadvertent misuse in that fashion. The usecase I'm oriented towards is: someone builds an unencrypted subvolume with a container base filesystem, takes several snapshots of the subvolume, starts a container on each subvolume, and has each container encrypt its designated subvolume going forward with a different key. This usecase needs some way to mark a subvolume/directory already containing files as encrypted going forward; I've had a hard time coming up with a way to both protect users against such accidental misuse, but also allow this container usecase. > > It might be warranted to use an encryption policy flag to explicitly > indicate > that mixing encrypted and unencrypted files is being allowed. Could it be sufficient to check either empty or read-only, something like (is_empty_dir(inode) || (FS_CFLG_PARTIAL && !inode_permission(..., inode, MAY_WRITE)))? Then the user is unable to accidentally write unencrypted data, since they've taken an action to make the directory read-only, until they've set a policy and key and turned the directory read-write again. Thanks!
diff --git a/fs/crypto/fname.c b/fs/crypto/fname.c index 6c092a1533f7..3bdece33e14d 100644 --- a/fs/crypto/fname.c +++ b/fs/crypto/fname.c @@ -414,6 +414,7 @@ int fscrypt_setup_filename(struct inode *dir, const struct qstr *iname, fname->usr_fname = iname; if (!IS_ENCRYPTED(dir) || fscrypt_is_dot_dotdot(iname)) { +unencrypted: fname->disk_name.name = (unsigned char *)iname->name; fname->disk_name.len = iname->len; return 0; @@ -448,8 +449,16 @@ int fscrypt_setup_filename(struct inode *dir, const struct qstr *iname, * user-supplied name */ - if (iname->len > FSCRYPT_NOKEY_NAME_MAX_ENCODED) + if (iname->len > FSCRYPT_NOKEY_NAME_MAX_ENCODED) { + /* + * This isn't a valid nokey name, but it could be an unencrypted + * name if the filesystem allows partially encrypted + * directories. + */ + if (dir->i_sb->s_cop->flags & FS_CFLG_ALLOW_PARTIAL) + goto unencrypted; return -ENOENT; + } fname->crypto_buf.name = kmalloc(FSCRYPT_NOKEY_NAME_MAX, GFP_KERNEL); if (fname->crypto_buf.name == NULL) @@ -460,6 +469,12 @@ int fscrypt_setup_filename(struct inode *dir, const struct qstr *iname, if (ret < (int)offsetof(struct fscrypt_nokey_name, bytes[1]) || (ret > offsetof(struct fscrypt_nokey_name, sha256) && ret != FSCRYPT_NOKEY_NAME_MAX)) { + /* Again, this could be an unencrypted name. */ + if (dir->i_sb->s_cop->flags & FS_CFLG_ALLOW_PARTIAL) { + kfree(fname->crypto_buf.name); + fname->crypto_buf.name = NULL; + goto unencrypted; + } ret = -ENOENT; goto errout; } diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h index a236d8c6d0da..a4e00314c91b 100644 --- a/include/linux/fscrypt.h +++ b/include/linux/fscrypt.h @@ -102,6 +102,8 @@ struct fscrypt_nokey_name { * pages for writes and therefore won't need the fscrypt bounce page pool. */ #define FS_CFLG_OWN_PAGES (1U << 1) +/* The filesystem allows partially encrypted directories/files. */ +#define FS_CFLG_ALLOW_PARTIAL (1U << 2) /* Crypto operations for filesystems */ struct fscrypt_operations {