diff mbox series

[RFC,2/4] fscrypt: add flag allowing partially-encrypted directories

Message ID 0508dac7fd6ec817007c5e21a565d1bb9d4f4921.1658623235.git.sweettea-kernel@dorminy.me (mailing list archive)
State New, archived
Headers show
Series fscrypt changes for btrfs encryption | expand

Commit Message

Sweet Tea Dorminy July 24, 2022, 12:52 a.m. UTC
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.

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(-)

Comments

Eric Biggers July 25, 2022, 7:49 p.m. UTC | #1
On Sat, Jul 23, 2022 at 08:52:26PM -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.
> 
> 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 5d5c26d827fd..c5dd19c1d19e 100644
> --- a/fs/crypto/fname.c
> +++ b/fs/crypto/fname.c
> @@ -389,6 +389,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;
> @@ -424,8 +425,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)
> @@ -436,6 +445,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 6020b738c3b2..fb48961c46f6 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)

I'm very confused about what the semantics of this are.  So a directory will be
able to contain both encrypted and unencrypted filenames?  If so, how will it be
possible to distinguish between them?  Or is it just both encrypted and
unencrypted files (which is actually already possible, in the case where
encrypted files are moved into an unencrypted directory)?  What sort of metadata
is stored with the parent directory?

Please note that any new semantics and APIs will need to be documented in
Documentation/filesystems/fscrypt.rst.

- Eric
Sweet Tea Dorminy July 26, 2022, 2:13 a.m. UTC | #2
>> diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h
>> index 6020b738c3b2..fb48961c46f6 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)
> 
> I'm very confused about what the semantics of this are.  So a directory will be
> able to contain both encrypted and unencrypted filenames?  If so, how will it be
> possible to distinguish between them? Or is it just both encrypted and
> unencrypted files (which is actually already possible, in the case where
> encrypted files are moved into an unencrypted directory)?  What sort of metadata
> is stored with the parent directory?
Yes, a directory for a filesystem with this flag could have both 
encrypted and unencrypted filenames.

When a directory switches to encrypted, the filesystem can get and store 
a fscrypt_context for it, as though it were a new directory. All new 
filenames for that directory will be encrypted, as will any filename 
lookup requests, by fscrypt_prepare_filename() since the directory has a 
context.

When a request for a lookup of a name in that directory comes in, it'll 
be an encrypted or nokey name; the directory can be searched for both 
the encrypted and unencrypted versions of that name. I don't think any 
filename collisions can result, as any encrypted filename which happens 
to match a plaintext filename will be detected as a collision when it's 
first added.

> 
> Please note that any new semantics and APIs will need to be documented in
> Documentation/filesystems/fscrypt.rst.
Good point.

Thanks!

Sweet Tea
diff mbox series

Patch

diff --git a/fs/crypto/fname.c b/fs/crypto/fname.c
index 5d5c26d827fd..c5dd19c1d19e 100644
--- a/fs/crypto/fname.c
+++ b/fs/crypto/fname.c
@@ -389,6 +389,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;
@@ -424,8 +425,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)
@@ -436,6 +445,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 6020b738c3b2..fb48961c46f6 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 {