diff mbox series

[v3,04/22] fscrypt: add extent-based encryption

Message ID d7246959ee0b8d2eeb7d6eb8cf40240374c6035c.1666281277.git.sweettea-kernel@dorminy.me (mailing list archive)
State Superseded
Headers show
Series btrfs: add fscrypt integration | expand

Commit Message

Sweet Tea Dorminy Oct. 20, 2022, 4:58 p.m. UTC
Some filesystems need to encrypt data based on extents, rather than on
inodes, due to features incompatible with inode-based encryption. For
instance, btrfs can have multiple inodes referencing a single block of
data, and moves logical data blocks to different physical locations on
disk in the background; these two features mean traditional inode-based
file contents encryption will not work for btrfs.

This change introduces fscrypt_extent_context objects, in analogy to
existing context objects based on inodes. For a filesystem which opts to
use extent-based encryption, a new hook provides a new
fscrypt_extent_context, generated in close analogy to the IVs generated
with existing policies. During file content encryption/decryption, the
existing fscrypt_context object provides key information, while the new
fscrypt_extent_context provides IV information. For filename encryption,
the existing IV generation methods are still used, since filenames are
not stored in extents.

Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
---
 fs/crypto/crypto.c          | 20 ++++++++--
 fs/crypto/fscrypt_private.h | 25 +++++++++++-
 fs/crypto/inline_crypt.c    | 28 ++++++++++---
 fs/crypto/policy.c          | 79 +++++++++++++++++++++++++++++++++++++
 include/linux/fscrypt.h     | 47 ++++++++++++++++++++++
 5 files changed, 189 insertions(+), 10 deletions(-)

Comments

Eric Biggers Oct. 20, 2022, 9:40 p.m. UTC | #1
On Thu, Oct 20, 2022 at 12:58:23PM -0400, Sweet Tea Dorminy wrote:
> +
> +/*
> + * fscrypt_extent_context - the encryption context for an extent
> + *
> + * For filesystems that support extent encryption, this context provides the
> + * necessary randomly-initialized IV in order to encrypt/decrypt the data
> + * stored in the extent. It is stored alongside each extent, and is
> + * insufficient to decrypt the extent: the extent's owning inode(s) provide the
> + * policy information (including key identifier) necessary to decrypt.
> + */
> +struct fscrypt_extent_context_v1 {
> +	u8 version;
> +	union fscrypt_iv iv;
> +} __packed;

On the previous version I had suggested using a 16-byte nonce per extent, so
that it's the same as the inode-based case.  Is there a reason you didn't do
that?

- Eric
Eric Biggers Oct. 20, 2022, 9:45 p.m. UTC | #2
On Thu, Oct 20, 2022 at 12:58:23PM -0400, Sweet Tea Dorminy wrote:
> Some filesystems need to encrypt data based on extents, rather than on
> inodes, due to features incompatible with inode-based encryption. For
> instance, btrfs can have multiple inodes referencing a single block of
> data, and moves logical data blocks to different physical locations on
> disk in the background; these two features mean traditional inode-based
> file contents encryption will not work for btrfs.
> 
> This change introduces fscrypt_extent_context objects, in analogy to
> existing context objects based on inodes. For a filesystem which opts to
> use extent-based encryption, a new hook provides a new
> fscrypt_extent_context, generated in close analogy to the IVs generated
> with existing policies. During file content encryption/decryption, the
> existing fscrypt_context object provides key information, while the new
> fscrypt_extent_context provides IV information. For filename encryption,
> the existing IV generation methods are still used, since filenames are
> not stored in extents.
> 
> Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
> ---
>  fs/crypto/crypto.c          | 20 ++++++++--
>  fs/crypto/fscrypt_private.h | 25 +++++++++++-
>  fs/crypto/inline_crypt.c    | 28 ++++++++++---
>  fs/crypto/policy.c          | 79 +++++++++++++++++++++++++++++++++++++
>  include/linux/fscrypt.h     | 47 ++++++++++++++++++++++
>  5 files changed, 189 insertions(+), 10 deletions(-)
> 
> diff --git a/fs/crypto/crypto.c b/fs/crypto/crypto.c
> index 7fe5979fbea2..08b495dc5c0c 100644
> --- a/fs/crypto/crypto.c
> +++ b/fs/crypto/crypto.c
> @@ -81,8 +81,22 @@ void fscrypt_generate_iv(union fscrypt_iv *iv, u64 lblk_num,
>  			 const struct fscrypt_info *ci)
>  {
>  	u8 flags = fscrypt_policy_flags(&ci->ci_policy);
> +	struct inode *inode = ci->ci_inode;
> +	const struct fscrypt_operations *s_cop = inode->i_sb->s_cop;
>  
> -	memset(iv, 0, ci->ci_mode->ivsize);
> +	memset(iv, 0, sizeof(*iv));
> +	if (s_cop->get_extent_context && lblk_num != U64_MAX) {
> +		size_t extent_offset;
> +		union fscrypt_extent_context ctx;
> +		int ret;
> +
> +		ret = fscrypt_get_extent_context(inode, lblk_num, &ctx,
> +						 &extent_offset, NULL);
> +		WARN_ON_ONCE(ret);
> +		memcpy(iv->raw, ctx.v1.iv.raw, sizeof(*iv));
> +		iv->lblk_num += cpu_to_le64(extent_offset);
> +		return;
> +	}

Please read through my review comment
https://lore.kernel.org/linux-fscrypt/Yx6MnaUqUTdjCmX+@quark/ again, as it
doesn't seem that you've addressed it.

- Eric
Sweet Tea Dorminy Oct. 20, 2022, 10:20 p.m. UTC | #3
On 10/20/22 17:40, Eric Biggers wrote:
> On Thu, Oct 20, 2022 at 12:58:23PM -0400, Sweet Tea Dorminy wrote:
>> +
>> +/*
>> + * fscrypt_extent_context - the encryption context for an extent
>> + *
>> + * For filesystems that support extent encryption, this context provides the
>> + * necessary randomly-initialized IV in order to encrypt/decrypt the data
>> + * stored in the extent. It is stored alongside each extent, and is
>> + * insufficient to decrypt the extent: the extent's owning inode(s) provide the
>> + * policy information (including key identifier) necessary to decrypt.
>> + */
>> +struct fscrypt_extent_context_v1 {
>> +	u8 version;
>> +	union fscrypt_iv iv;
>> +} __packed;
> 
> On the previous version I had suggested using a 16-byte nonce per extent, so
> that it's the same as the inode-based case.  Is there a reason you didn't do
> that?
> 
> - Eric

I probably misunderstood what you meant. For the direct-key case, the 
initial extent context is generated by copying the inode's nonce (and 
setting the lblk_num field to match the starting lblk of the extent, for 
all the policies). In theory, this should result in the same IVs being 
used for unshared extents as would have happened for inode-based encryption?
Sweet Tea Dorminy Oct. 20, 2022, 10:55 p.m. UTC | #4
On 10/20/22 17:45, Eric Biggers wrote:
> On Thu, Oct 20, 2022 at 12:58:23PM -0400, Sweet Tea Dorminy wrote:
>> Some filesystems need to encrypt data based on extents, rather than on
>> inodes, due to features incompatible with inode-based encryption. For
>> instance, btrfs can have multiple inodes referencing a single block of
>> data, and moves logical data blocks to different physical locations on
>> disk in the background; these two features mean traditional inode-based
>> file contents encryption will not work for btrfs.
>>
>> This change introduces fscrypt_extent_context objects, in analogy to
>> existing context objects based on inodes. For a filesystem which opts to
>> use extent-based encryption, a new hook provides a new
>> fscrypt_extent_context, generated in close analogy to the IVs generated
>> with existing policies. During file content encryption/decryption, the
>> existing fscrypt_context object provides key information, while the new
>> fscrypt_extent_context provides IV information. For filename encryption,
>> the existing IV generation methods are still used, since filenames are
>> not stored in extents.
>>
>> Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
>> ---
>>   fs/crypto/crypto.c          | 20 ++++++++--
>>   fs/crypto/fscrypt_private.h | 25 +++++++++++-
>>   fs/crypto/inline_crypt.c    | 28 ++++++++++---
>>   fs/crypto/policy.c          | 79 +++++++++++++++++++++++++++++++++++++
>>   include/linux/fscrypt.h     | 47 ++++++++++++++++++++++
>>   5 files changed, 189 insertions(+), 10 deletions(-)
>>
>> diff --git a/fs/crypto/crypto.c b/fs/crypto/crypto.c
>> index 7fe5979fbea2..08b495dc5c0c 100644
>> --- a/fs/crypto/crypto.c
>> +++ b/fs/crypto/crypto.c
>> @@ -81,8 +81,22 @@ void fscrypt_generate_iv(union fscrypt_iv *iv, u64 lblk_num,
>>   			 const struct fscrypt_info *ci)
>>   {
>>   	u8 flags = fscrypt_policy_flags(&ci->ci_policy);
>> +	struct inode *inode = ci->ci_inode;
>> +	const struct fscrypt_operations *s_cop = inode->i_sb->s_cop;
>>   
>> -	memset(iv, 0, ci->ci_mode->ivsize);
>> +	memset(iv, 0, sizeof(*iv));
>> +	if (s_cop->get_extent_context && lblk_num != U64_MAX) {
>> +		size_t extent_offset;
>> +		union fscrypt_extent_context ctx;
>> +		int ret;
>> +
>> +		ret = fscrypt_get_extent_context(inode, lblk_num, &ctx,
>> +						 &extent_offset, NULL);
>> +		WARN_ON_ONCE(ret);
>> +		memcpy(iv->raw, ctx.v1.iv.raw, sizeof(*iv));
>> +		iv->lblk_num += cpu_to_le64(extent_offset);
>> +		return;
>> +	}
> 
> Please read through my review comment
> https://lore.kernel.org/linux-fscrypt/Yx6MnaUqUTdjCmX+@quark/ again, as it
> doesn't seem that you've addressed it.
> 
> - Eric

I probably didn't understand it correctly. I think there were three 
points in it:

1) reconsider per-extent keys
2) make IV generation work for non-directkey policies as similarly as 
possible to how they work in inode-based filesystems
3) never use 'file-based' except in contrast to dm-crypt and other 
block-layer encryption.

For point 2, I changed the initial extent context generation to match up 
with fscrypt_generate_iv() (and probably didn't call that out enough in 
the description). (Looking at it again, I could literally call 
fscrypt_generate_iv() to generate the initial extent context; I didn't 
realize that before).

Then adding lblk_num to the existing lblk_num in the iv from the start 
of the extent should be the same as the iv->lblk_num setting in the 
inode-based case: for lblk 12, for instance, the same IV should result 
from inode-based with lblk 12, as with extent-based with an initial 
lblk_num of 9 and an extent_offset of 3. For shared extents, they'll be 
different, but for singly-referenced extents, the IVs should be exactly 
the same in theory.

I'm not sure whether I misunderstood the points or didn't address them 
fully, I apologize. Would you be up for elaborating where I missed, 
either by email or by videochat whenever works for you?

Thanks.
Eric Biggers Oct. 20, 2022, 11:56 p.m. UTC | #5
On Thu, Oct 20, 2022 at 06:55:04PM -0400, Sweet Tea Dorminy wrote:
> 
> 
> On 10/20/22 17:45, Eric Biggers wrote:
> > On Thu, Oct 20, 2022 at 12:58:23PM -0400, Sweet Tea Dorminy wrote:
> > > Some filesystems need to encrypt data based on extents, rather than on
> > > inodes, due to features incompatible with inode-based encryption. For
> > > instance, btrfs can have multiple inodes referencing a single block of
> > > data, and moves logical data blocks to different physical locations on
> > > disk in the background; these two features mean traditional inode-based
> > > file contents encryption will not work for btrfs.
> > > 
> > > This change introduces fscrypt_extent_context objects, in analogy to
> > > existing context objects based on inodes. For a filesystem which opts to
> > > use extent-based encryption, a new hook provides a new
> > > fscrypt_extent_context, generated in close analogy to the IVs generated
> > > with existing policies. During file content encryption/decryption, the
> > > existing fscrypt_context object provides key information, while the new
> > > fscrypt_extent_context provides IV information. For filename encryption,
> > > the existing IV generation methods are still used, since filenames are
> > > not stored in extents.
> > > 
> > > Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
> > > ---
> > >   fs/crypto/crypto.c          | 20 ++++++++--
> > >   fs/crypto/fscrypt_private.h | 25 +++++++++++-
> > >   fs/crypto/inline_crypt.c    | 28 ++++++++++---
> > >   fs/crypto/policy.c          | 79 +++++++++++++++++++++++++++++++++++++
> > >   include/linux/fscrypt.h     | 47 ++++++++++++++++++++++
> > >   5 files changed, 189 insertions(+), 10 deletions(-)
> > > 
> > > diff --git a/fs/crypto/crypto.c b/fs/crypto/crypto.c
> > > index 7fe5979fbea2..08b495dc5c0c 100644
> > > --- a/fs/crypto/crypto.c
> > > +++ b/fs/crypto/crypto.c
> > > @@ -81,8 +81,22 @@ void fscrypt_generate_iv(union fscrypt_iv *iv, u64 lblk_num,
> > >   			 const struct fscrypt_info *ci)
> > >   {
> > >   	u8 flags = fscrypt_policy_flags(&ci->ci_policy);
> > > +	struct inode *inode = ci->ci_inode;
> > > +	const struct fscrypt_operations *s_cop = inode->i_sb->s_cop;
> > > -	memset(iv, 0, ci->ci_mode->ivsize);
> > > +	memset(iv, 0, sizeof(*iv));
> > > +	if (s_cop->get_extent_context && lblk_num != U64_MAX) {
> > > +		size_t extent_offset;
> > > +		union fscrypt_extent_context ctx;
> > > +		int ret;
> > > +
> > > +		ret = fscrypt_get_extent_context(inode, lblk_num, &ctx,
> > > +						 &extent_offset, NULL);
> > > +		WARN_ON_ONCE(ret);
> > > +		memcpy(iv->raw, ctx.v1.iv.raw, sizeof(*iv));
> > > +		iv->lblk_num += cpu_to_le64(extent_offset);
> > > +		return;
> > > +	}
> > 
> > Please read through my review comment
> > https://lore.kernel.org/linux-fscrypt/Yx6MnaUqUTdjCmX+@quark/ again, as it
> > doesn't seem that you've addressed it.
> > 
> > - Eric
> 
> I probably didn't understand it correctly. I think there were three points
> in it:
> 
> 1) reconsider per-extent keys
> 2) make IV generation work for non-directkey policies as similarly as
> possible to how they work in inode-based filesystems
> 3) never use 'file-based' except in contrast to dm-crypt and other
> block-layer encryption.
> 
> For point 2, I changed the initial extent context generation to match up
> with fscrypt_generate_iv() (and probably didn't call that out enough in the
> description). (Looking at it again, I could literally call
> fscrypt_generate_iv() to generate the initial extent context; I didn't
> realize that before).
> 
> Then adding lblk_num to the existing lblk_num in the iv from the start of
> the extent should be the same as the iv->lblk_num setting in the inode-based
> case: for lblk 12, for instance, the same IV should result from inode-based
> with lblk 12, as with extent-based with an initial lblk_num of 9 and an
> extent_offset of 3. For shared extents, they'll be different, but for
> singly-referenced extents, the IVs should be exactly the same in theory.
> 
> I'm not sure whether I misunderstood the points or didn't address them
> fully, I apologize. Would you be up for elaborating where I missed, either
> by email or by videochat whenever works for you?

It seems you misunderstood point (2).  See what I said below:

	So if you do want to implement the DIRECT_KEY method, the natural thing
	to do would be to store a 16-byte nonce along with each extent, and use
	the DIRECT_KEY IV generation method as-is.  It seems that you've done it
	a bit differently; you store a 32-byte nonce and generate the IV as
	'nonce + lblk_num', instead of 'nonce || lblk_num'.  I think that's a
	mistake -- it should be exactly the same.

	If the issue is that the 'nonce || lblk_num' method doesn't allow for
	AES-XTS support, we could extend DIRECT_KEY to do 'nonce + lblk_num'
	*if* the algorithm has a 16-byte IV size and thus has to tolerate some
	chance of IV reuse.  Note that this change would be unrelated to
	extent-based encryption, and could be applied regardless of it.

So:

1.) Provided that you've decided against per-extent keys, and are not trying to
    support UFS and eMMC inline encryption hardware, then you should *only*
    support DIRECT_KEY -- not other settings that don't make sense.

2.) There should be a preparatory patch that makes DIRECT_KEY be allowed when
    the IV length is 16 bytes, using the method 'nonce + lblk_num' -- assuming
    that you need AES-XTS support and aren't planning on supporting Adiantum or
    AES-HCTR2 only.  (The small chance for IV reuse that it results in is not
    ideal, but it's probably tolerable.  Maybe the nonce should also be hashed
    with a secret key, like what IV_INO_LBLK_32 does with the inode number; I'll
    have to think about it.)  If you plan to just support AES-HCTR2 instead of
    AES-XTS, then you'd need a patch to allow AES-HCTR2 for contents encryption,
    as currently it is only allowed for filenames.

3.) Each extent context should contain a 16-byte random nonce, that is newly
    generated just for that extent -- not copied from anywhere.

4.) IVs should be generated using the DIRECT_KEY method.  That is,
    'nonce || lblk_num' if the IV length allows it, otherwise 'nonce + lblk_num'
    as mentioned in (2).  For inode-based encryption, nonce means the inode's
    nonce, and lblk_num means the index of the block in the inode.  For
    extent-based encryption, nonce will mean the extent's nonce, and lblk_num
    will mean the index of the block in the extent.

- Eric
Sweet Tea Dorminy Oct. 21, 2022, 12:37 a.m. UTC | #6
On 10/20/22 19:56, Eric Biggers wrote:
> On Thu, Oct 20, 2022 at 06:55:04PM -0400, Sweet Tea Dorminy wrote:
>>
>>
>> On 10/20/22 17:45, Eric Biggers wrote:
>>> On Thu, Oct 20, 2022 at 12:58:23PM -0400, Sweet Tea Dorminy wrote:
>>>> Some filesystems need to encrypt data based on extents, rather than on
>>>> inodes, due to features incompatible with inode-based encryption. For
>>>> instance, btrfs can have multiple inodes referencing a single block of
>>>> data, and moves logical data blocks to different physical locations on
>>>> disk in the background; these two features mean traditional inode-based
>>>> file contents encryption will not work for btrfs.
>>>>
>>>> This change introduces fscrypt_extent_context objects, in analogy to
>>>> existing context objects based on inodes. For a filesystem which opts to
>>>> use extent-based encryption, a new hook provides a new
>>>> fscrypt_extent_context, generated in close analogy to the IVs generated
>>>> with existing policies. During file content encryption/decryption, the
>>>> existing fscrypt_context object provides key information, while the new
>>>> fscrypt_extent_context provides IV information. For filename encryption,
>>>> the existing IV generation methods are still used, since filenames are
>>>> not stored in extents.
>>>>
>>>> Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
>>>> ---
>>>>    fs/crypto/crypto.c          | 20 ++++++++--
>>>>    fs/crypto/fscrypt_private.h | 25 +++++++++++-
>>>>    fs/crypto/inline_crypt.c    | 28 ++++++++++---
>>>>    fs/crypto/policy.c          | 79 +++++++++++++++++++++++++++++++++++++
>>>>    include/linux/fscrypt.h     | 47 ++++++++++++++++++++++
>>>>    5 files changed, 189 insertions(+), 10 deletions(-)
>>>>
>>>> diff --git a/fs/crypto/crypto.c b/fs/crypto/crypto.c
>>>> index 7fe5979fbea2..08b495dc5c0c 100644
>>>> --- a/fs/crypto/crypto.c
>>>> +++ b/fs/crypto/crypto.c
>>>> @@ -81,8 +81,22 @@ void fscrypt_generate_iv(union fscrypt_iv *iv, u64 lblk_num,
>>>>    			 const struct fscrypt_info *ci)
>>>>    {
>>>>    	u8 flags = fscrypt_policy_flags(&ci->ci_policy);
>>>> +	struct inode *inode = ci->ci_inode;
>>>> +	const struct fscrypt_operations *s_cop = inode->i_sb->s_cop;
>>>> -	memset(iv, 0, ci->ci_mode->ivsize);
>>>> +	memset(iv, 0, sizeof(*iv));
>>>> +	if (s_cop->get_extent_context && lblk_num != U64_MAX) {
>>>> +		size_t extent_offset;
>>>> +		union fscrypt_extent_context ctx;
>>>> +		int ret;
>>>> +
>>>> +		ret = fscrypt_get_extent_context(inode, lblk_num, &ctx,
>>>> +						 &extent_offset, NULL);
>>>> +		WARN_ON_ONCE(ret);
>>>> +		memcpy(iv->raw, ctx.v1.iv.raw, sizeof(*iv));
>>>> +		iv->lblk_num += cpu_to_le64(extent_offset);
>>>> +		return;
>>>> +	}
>>>
>>> Please read through my review comment
>>> https://lore.kernel.org/linux-fscrypt/Yx6MnaUqUTdjCmX+@quark/ again, as it
>>> doesn't seem that you've addressed it.
>>>
>>> - Eric
>>
>> I probably didn't understand it correctly. I think there were three points
>> in it:
>>
>> 1) reconsider per-extent keys
>> 2) make IV generation work for non-directkey policies as similarly as
>> possible to how they work in inode-based filesystems
>> 3) never use 'file-based' except in contrast to dm-crypt and other
>> block-layer encryption.
>>
>> For point 2, I changed the initial extent context generation to match up
>> with fscrypt_generate_iv() (and probably didn't call that out enough in the
>> description). (Looking at it again, I could literally call
>> fscrypt_generate_iv() to generate the initial extent context; I didn't
>> realize that before).
>>
>> Then adding lblk_num to the existing lblk_num in the iv from the start of
>> the extent should be the same as the iv->lblk_num setting in the inode-based
>> case: for lblk 12, for instance, the same IV should result from inode-based
>> with lblk 12, as with extent-based with an initial lblk_num of 9 and an
>> extent_offset of 3. For shared extents, they'll be different, but for
>> singly-referenced extents, the IVs should be exactly the same in theory.
>>
>> I'm not sure whether I misunderstood the points or didn't address them
>> fully, I apologize. Would you be up for elaborating where I missed, either
>> by email or by videochat whenever works for you?
> 
> It seems you misunderstood point (2).  See what I said below:
> 
> 	So if you do want to implement the DIRECT_KEY method, the natural thing
> 	to do would be to store a 16-byte nonce along with each extent, and use
> 	the DIRECT_KEY IV generation method as-is.  It seems that you've done it
> 	a bit differently; you store a 32-byte nonce and generate the IV as
> 	'nonce + lblk_num', instead of 'nonce || lblk_num'.  I think that's a
> 	mistake -- it should be exactly the same.
> 
> 	If the issue is that the 'nonce || lblk_num' method doesn't allow for
> 	AES-XTS support, we could extend DIRECT_KEY to do 'nonce + lblk_num'
> 	*if* the algorithm has a 16-byte IV size and thus has to tolerate some
> 	chance of IV reuse.  Note that this change would be unrelated to
> 	extent-based encryption, and could be applied regardless of it.
> 
> So:
> 
> 1.) Provided that you've decided against per-extent keys, and are not trying to
>      support UFS and eMMC inline encryption hardware, then you should *only*
>      support DIRECT_KEY -- not other settings that don't make sense.
> 
> 2.) There should be a preparatory patch that makes DIRECT_KEY be allowed when
>      the IV length is 16 bytes, using the method 'nonce + lblk_num' -- assuming
>      that you need AES-XTS support and aren't planning on supporting Adiantum or
>      AES-HCTR2 only.  (The small chance for IV reuse that it results in is not
>      ideal, but it's probably tolerable.  Maybe the nonce should also be hashed
>      with a secret key, like what IV_INO_LBLK_32 does with the inode number; I'll
>      have to think about it.)  If you plan to just support AES-HCTR2 instead of
>      AES-XTS, then you'd need a patch to allow AES-HCTR2 for contents encryption,
>      as currently it is only allowed for filenames.
> 
> 3.) Each extent context should contain a 16-byte random nonce, that is newly
>      generated just for that extent -- not copied from anywhere.
> 
> 4.) IVs should be generated using the DIRECT_KEY method.  That is,
>      'nonce || lblk_num' if the IV length allows it, otherwise 'nonce + lblk_num'
>      as mentioned in (2).  For inode-based encryption, nonce means the inode's
>      nonce, and lblk_num means the index of the block in the inode.  For
>      extent-based encryption, nonce will mean the extent's nonce, and lblk_num
>      will mean the index of the block in the extent.
> 
> - Eric

Awesome, thank you for the elaboration. I'll give it a shot tonight and 
will send out v4 as soon as it's ready.

-Sweet Tea
diff mbox series

Patch

diff --git a/fs/crypto/crypto.c b/fs/crypto/crypto.c
index 7fe5979fbea2..08b495dc5c0c 100644
--- a/fs/crypto/crypto.c
+++ b/fs/crypto/crypto.c
@@ -81,8 +81,22 @@  void fscrypt_generate_iv(union fscrypt_iv *iv, u64 lblk_num,
 			 const struct fscrypt_info *ci)
 {
 	u8 flags = fscrypt_policy_flags(&ci->ci_policy);
+	struct inode *inode = ci->ci_inode;
+	const struct fscrypt_operations *s_cop = inode->i_sb->s_cop;
 
-	memset(iv, 0, ci->ci_mode->ivsize);
+	memset(iv, 0, sizeof(*iv));
+	if (s_cop->get_extent_context && lblk_num != U64_MAX) {
+		size_t extent_offset;
+		union fscrypt_extent_context ctx;
+		int ret;
+
+		ret = fscrypt_get_extent_context(inode, lblk_num, &ctx,
+						 &extent_offset, NULL);
+		WARN_ON_ONCE(ret);
+		memcpy(iv->raw, ctx.v1.iv.raw, sizeof(*iv));
+		iv->lblk_num += cpu_to_le64(extent_offset);
+		return;
+	}
 
 	/*
 	 * Filename encryption. For inode-based policies, filenames are
@@ -93,8 +107,8 @@  void fscrypt_generate_iv(union fscrypt_iv *iv, u64 lblk_num,
 
 	if (flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64) {
 		WARN_ON_ONCE(lblk_num > U32_MAX);
-		WARN_ON_ONCE(ci->ci_inode->i_ino > U32_MAX);
-		lblk_num |= (u64)ci->ci_inode->i_ino << 32;
+		WARN_ON_ONCE(inode->i_ino > U32_MAX);
+		lblk_num |= (u64)inode->i_ino << 32;
 	} else if (flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32) {
 		WARN_ON_ONCE(lblk_num > U32_MAX);
 		lblk_num = (u32)(ci->ci_hashed_ino + lblk_num);
diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h
index d5f68a0c5d15..9c4cae2580de 100644
--- a/fs/crypto/fscrypt_private.h
+++ b/fs/crypto/fscrypt_private.h
@@ -280,7 +280,6 @@  fscrypt_msg(const struct inode *inode, const char *level, const char *fmt, ...);
 	fscrypt_msg((inode), KERN_ERR, fmt, ##__VA_ARGS__)
 
 #define FSCRYPT_MAX_IV_SIZE	32
-
 union fscrypt_iv {
 	struct {
 		/* logical block number within the file */
@@ -293,6 +292,27 @@  union fscrypt_iv {
 	__le64 dun[FSCRYPT_MAX_IV_SIZE / sizeof(__le64)];
 };
 
+
+/*
+ * fscrypt_extent_context - the encryption context for an extent
+ *
+ * For filesystems that support extent encryption, this context provides the
+ * necessary randomly-initialized IV in order to encrypt/decrypt the data
+ * stored in the extent. It is stored alongside each extent, and is
+ * insufficient to decrypt the extent: the extent's owning inode(s) provide the
+ * policy information (including key identifier) necessary to decrypt.
+ */
+struct fscrypt_extent_context_v1 {
+	u8 version;
+	union fscrypt_iv iv;
+} __packed;
+
+union fscrypt_extent_context {
+	u8 version;
+	struct fscrypt_extent_context_v1 v1;
+};
+
+
 void fscrypt_generate_iv(union fscrypt_iv *iv, u64 lblk_num,
 			 const struct fscrypt_info *ci);
 
@@ -662,5 +682,8 @@  int fscrypt_policy_from_context(union fscrypt_policy *policy_u,
 				const union fscrypt_context *ctx_u,
 				int ctx_size);
 const union fscrypt_policy *fscrypt_policy_to_inherit(struct inode *dir);
+int fscrypt_get_extent_context(const struct inode *inode, u64 lblk_num,
+			       union fscrypt_extent_context *ctx,
+			       size_t *extent_offset, size_t *extent_length);
 
 #endif /* _FSCRYPT_PRIVATE_H */
diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c
index cea8b14007e6..6adb72c52ce2 100644
--- a/fs/crypto/inline_crypt.c
+++ b/fs/crypto/inline_crypt.c
@@ -460,6 +460,7 @@  EXPORT_SYMBOL_GPL(fscrypt_dio_supported);
  */
 u64 fscrypt_limit_io_blocks(const struct inode *inode, u64 lblk, u64 nr_blocks)
 {
+	const struct fscrypt_operations *s_cop = inode->i_sb->s_cop;
 	const struct fscrypt_info *ci;
 	u32 dun;
 
@@ -470,14 +471,29 @@  u64 fscrypt_limit_io_blocks(const struct inode *inode, u64 lblk, u64 nr_blocks)
 		return nr_blocks;
 
 	ci = inode->i_crypt_info;
-	if (!(fscrypt_policy_flags(&ci->ci_policy) &
-	      FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32))
-		return nr_blocks;
 
-	/* With IV_INO_LBLK_32, the DUN can wrap around from U32_MAX to 0. */
+	if (s_cop->get_extent_context) {
+		size_t extent_offset, extent_length;
+		int ret = fscrypt_get_extent_context(inode, lblk, NULL,
+						     &extent_offset,
+						     &extent_length);
+		if (ret < 0) {
+			WARN_ON_ONCE(ret < 0);
+			return 1;
+		}
+		return extent_length - extent_offset;
+	}
 
-	dun = ci->ci_hashed_ino + lblk;
+	if ((fscrypt_policy_flags(&ci->ci_policy) &
+	      FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32)) {
+		/*
+		 * With IV_INO_LBLK_32, the DUN can wrap around from U32_MAX to
+		 * 0.
+		 */
+		dun = ci->ci_hashed_ino + lblk;
+		return min_t(u64, nr_blocks, (u64)U32_MAX + 1 - dun);
+	}
 
-	return min_t(u64, nr_blocks, (u64)U32_MAX + 1 - dun);
+	return nr_blocks;
 }
 EXPORT_SYMBOL_GPL(fscrypt_limit_io_blocks);
diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c
index b7c4820a8001..0a9bd20db023 100644
--- a/fs/crypto/policy.c
+++ b/fs/crypto/policy.c
@@ -777,6 +777,85 @@  int fscrypt_set_context(struct inode *inode, void *fs_data)
 }
 EXPORT_SYMBOL_GPL(fscrypt_set_context);
 
+/**
+ * fscrypt_get_extent_context() - Get the fscrypt extent context for a location
+ *
+ * @inode: an inode associated with the extent
+ * @lblk_num: a logical block number within the inode owned by the extent
+ * @ctx: a pointer to return the context found (may be NULL)
+ * @extent_offset: a pointer to return the offset of @lblk_num within the
+ *                 extent (may be NULL)
+ * @extent_length: a pointer to return the length of the extent found (may be
+ *                 NULL)
+ *
+ * Return: 0 on success, -errno on failure
+ */
+int fscrypt_get_extent_context(const struct inode *inode, u64 lblk_num,
+			       union fscrypt_extent_context *ctx,
+			       size_t *extent_offset, size_t *extent_length)
+{
+	int ret;
+	int ctxsize = (ctx == NULL ? 0 : sizeof(*ctx));
+
+	if (!IS_ENCRYPTED(inode))
+		return -ENODATA;
+
+	ret = inode->i_sb->s_cop->get_extent_context(inode, lblk_num, ctx,
+						     ctxsize, extent_offset,
+						     extent_length);
+	if (ret == ctxsize && (!ctx || ctx->version == 1))
+		return 0;
+	if (ret >= 0)
+		return -EINVAL;
+	return ret;
+}
+EXPORT_SYMBOL_GPL(fscrypt_get_extent_context);
+
+/**
+ * fscrypt_set_extent_context() - Set an extent's fscrypt context
+ *
+ * @inode: an inode to which the extent belongs
+ * @lblk_num: the offset into the inode at which the extent starts
+ * @extent: private data referring to the extent, given by the FS and passed
+ *          to ->set_extent_context()
+ *
+ * This should be called after fscrypt_prepare_new_inode(), generally during a
+ * filesystem transaction.  Everything here must be %GFP_NOFS-safe.
+ *
+ * Return: 0 on success, -errno on failure
+ */
+int fscrypt_set_extent_context(struct inode *inode, u64 lblk_num, void *extent)
+{
+	struct fscrypt_info *ci = fscrypt_get_info(inode);
+	u8 flags = fscrypt_policy_flags(&ci->ci_policy);
+	union fscrypt_extent_context ctx;
+
+	if (!IS_ENCRYPTED(inode))
+		return -ENODATA;
+
+	/*
+	 * Since the inode using this extent context is variable, most of the
+	 * IV generation policies that are in fscrypt_generate_iv() must be
+	 * implemented here for extent-based policies.
+	 */
+	ctx.v1.version = 1;
+	if (flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64) {
+		WARN_ON_ONCE(lblk_num > U32_MAX);
+		WARN_ON_ONCE(inode->i_ino > U32_MAX);
+		lblk_num |= (u64)inode->i_ino << 32;
+	} else if (flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32) {
+		WARN_ON_ONCE(lblk_num > U32_MAX);
+		lblk_num = (u32)(ci->ci_hashed_ino + lblk_num);
+	} else if (flags & FSCRYPT_POLICY_FLAG_DIRECT_KEY) {
+		memcpy(ctx.v1.iv.nonce, ci->ci_nonce, FSCRYPT_FILE_NONCE_SIZE);
+	}
+	ctx.v1.iv.lblk_num = cpu_to_le64(lblk_num);
+
+	return inode->i_sb->s_cop->set_extent_context(extent,
+						      &ctx, sizeof(ctx));
+}
+EXPORT_SYMBOL_GPL(fscrypt_set_extent_context);
+
 /**
  * fscrypt_parse_test_dummy_encryption() - parse the test_dummy_encryption mount option
  * @param: the mount option
diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h
index 9cc5a61c1200..4143c722ea1b 100644
--- a/include/linux/fscrypt.h
+++ b/include/linux/fscrypt.h
@@ -94,6 +94,13 @@  struct fscrypt_nokey_name {
 /* Maximum value for the third parameter of fscrypt_operations.set_context(). */
 #define FSCRYPT_SET_CONTEXT_MAX_SIZE	40
 
+/*
+ * Maximum value for the third parameter of
+ * fscrypt_operations.set_extent_context(). Update if fscrypt_private.h:
+ * FSCRYPT_MAX_IVSIZE changes
+ */
+#define FSCRYPT_EXTENT_CONTEXT_MAX_SIZE	33
+
 #ifdef CONFIG_FS_ENCRYPTION
 
 /*
@@ -150,6 +157,39 @@  struct fscrypt_operations {
 	int (*set_context)(struct inode *inode, const void *ctx, size_t len,
 			   void *fs_data);
 
+	/*
+	 * Get the fscrypt extent context for a given inode and lblk number.
+	 *
+	 * @inode: the inode to which the extent belongs
+	 * @lblk_num: the block number within the file whose extent is being
+	 *            queried
+	 * @ctx: the buffer into which to get the context (may be NULL)
+	 * @len: the length of the @ctx buffer in bytes
+	 * @extent_offset: a pointer to return the offset of @lblk_num within
+	 *                 the extent whose context is returned (may be NULL)
+	 * @extent_length: a pointer to return the total length of the extent
+	 *                 whose context was found (may be NULL)
+	 *
+	 * Return: On success, returns the length of the context in bytes,
+	 * which may be less than @len. On failure, returns -ENODATA if the
+	 * extent doesn't have a context, -ERANGE if the context is longer
+	 * than @len, or another -errno code.
+	 */
+	int (*get_extent_context)(const struct inode *inode, u64 lblk_num,
+				  void *ctx, size_t len,
+				  size_t *extent_offset, size_t *extent_length);
+
+	/*
+	 * Set the fscrypt extent context for an extent.
+	 *
+	 * @extent: an opaque pointer to the filesystem's extent object
+	 * @ctx: the buffer containing the extent context to set
+	 * @len: the length of the @ctx buffer in bytes
+	 *
+	 * Return: 0 on success, -errno on failure.
+	 */
+	int (*set_extent_context)(void *extent, void *ctx, size_t len);
+
 	/*
 	 * Get the dummy fscrypt policy in use on the filesystem (if any).
 	 *
@@ -321,6 +361,7 @@  int fscrypt_ioctl_get_nonce(struct file *filp, void __user *arg);
 int fscrypt_has_permitted_context(struct inode *parent, struct inode *child);
 int fscrypt_context_for_new_inode(void *ctx, struct inode *inode);
 int fscrypt_set_context(struct inode *inode, void *fs_data);
+int fscrypt_set_extent_context(struct inode *inode, u64 offset, void *extent);
 
 struct fscrypt_dummy_policy {
 	const union fscrypt_policy *policy;
@@ -530,6 +571,12 @@  static inline int fscrypt_set_context(struct inode *inode, void *fs_data)
 	return -EOPNOTSUPP;
 }
 
+static inline int fscrypt_set_extent_context(struct inode *inode, u64 offset,
+					     void *extent)
+{
+	return -EOPNOTSUPP;
+}
+
 struct fscrypt_dummy_policy {
 };