Re: [PATCH v7 01/43] fscrypt: add per-extent encryption support

Daniel Vacek <[email protected]>
Newsgroups org.kernel.vger.linux-block,org.kernel.vger.linux-btrfs,org.kernel.vger.linux-fscrypt,org.kernel.vger.linux-kernel
Message-ID <CAPjX3Ff5_EaAVJv++_huk3Uw5sjJ5QfoZm0DzJx_UdObNChuiw@mail.gmail.com>
On Wed, 12 Aug 2026 at 12:34, Daniel Vacek <[email protected]> wrote:
> On Tue, 2 Jun 2026 at 00:44, Eric Biggers <[email protected]> wrote:
> > On Wed, May 13, 2026 at 10:52:35AM +0200, Daniel Vacek wrote:
> > > From: Josef Bacik <[email protected]>
> > >
> > > This adds the code necessary for per-extent encryption.  We will store a
> > > nonce for every extent we create, and then use the inode's policy and
> > > the extents nonce to derive a per-extent key.
> > >
> > > This is meant to be flexible, if we choose to expand the on-disk extent
> > > information in the future we have a version number we can use to change
> > > what exists on disk.
> > >
> > > The file system indicates it wants to use per-extent encryption by
> > > setting s_cop->has_per_extent_encryption.  This also requires the use of
> > > inline block encryption.
> > >
> > > The support is relatively straightforward, the only "extra" bit is we're
> > > deriving a per-extent key to use for the encryption, the inode still
> > > controls the policy and access to the master key.
> > >
> > > Since extent based encryption uses a lot of keys, we're requiring the
> > > use of inline block crypto if you use extent-based encryption.  This
> > > enables us to take advantage of the built in pooling and reclamation of
> > > the crypto structures that underpin all of the encryption.
> >
> > The whole reason for extent-based encryption is that extents can be
> > shared between inodes.  So the repeated mentions of "the inode" are
> > really confusing.  This shows up in a lot of different places.
> >
> > What's actually implemented is that each extent stores its own
> > (encryption_mode, master_key_identifier, nonce), but for now the
> > invariant is maintained that all inodes that reference an extent share
> > the same (encryption_mode, master_key_identifier) as the extent.
> >
> > It would be helpful to document this stuff accordingly.
>
> Yeah, that makes sense. I believe this is what we want - any file is
> unlocked by one key. Not that you would need multiple keys for
> unlocking different parts of a file.
>
> > > +/*
> > > + * fscrypt_extent_context - the encryption context of an extent
> > > + *
> > > + * This is the on-disk information stored for an extent.  The nonce is used as a
> > > + * KDF input in conjuction with the inode context to derive a per-extent key for
> > > + * encryption.  This is used only when the filesystem uses per-extent encryption.
> > > + *
> >
> > Basically the same issue here.  The master_key_identifier is actually
> > stored in the extent.  Just the current implementation enforces that
> > when the filesystem accesses the extent through some inode, that inode
> > also has the same master_key_identifier.  How about replacing the second
> > sentence with something like: "The nonce and master_key_identifier are
> > used to derive the key which encrypts the extent."
>
> Right, this sounds better.
>
> > > + * With the current implementation, master_key_identifier and encryption mode
> > > + * must match the inode context.  These are here for future expansion where we
> > > + * may want the option of mixing different keys and encryption modes for the
> > > + * same file.
> > > + */
> >
> > Likewise.  Something like: With the current implementation,
> > master_key_identifier and encryption_mode always match the corresponding
> > values from the fscrypt_context in each inode that shares the extent.
>
> Do we actually want this freedom or should we drop the encryption_mode
> and master_key_identifier and consider them implicit from
> inode/fscrypt_context?
> That would mean storing only the nonce in the extent context. The
> version field still remains, preserving the flexibility to eventually
> add them back later if we decide to.
>
> > > +struct fscrypt_extent_context {
> > > +     u8 version; /* FSCRYPT_EXTENT_CONTEXT_V1 */
> > > +     u8 encryption_mode;
> > > +     u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
> > > +     u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
> > > +};
> >
> > Well, it's an extent nonce, not a file nonce.  It seems it's handled
> > completely separately from the existing file nonce, so it probably
> > should get its own size constant FSCRYPT_EXTENT_NONCE_SIZE.
>
> This actually sounds more confusing to me. They are both the same
> thing. My understanding was that extent is "kind of file" on it's own.
> If anything I'd argue for FSCRYPT_NONCE_SIZE used in both places. But
> a FSCRYPT_FILE_NONCE_SIZE used in inode context as well as in extent
> context sounds fine to me.
>
> > > +/**
> > > + * fscrypt_set_bio_crypt_ctx_from_extent() - prepare a file contents bio for
> > > + *                                        inline crypto with extent
> > > + *                                        encryption
> > > + * @bio: a bio which will eventually be submitted to the file
> > > + * @ei: the extent's crypto info
> >
> > @ei: the extent's crypto info, or NULL if the extent is unencrypted
>
> True, the @ei is optional. I'll fix that.
>
> > > + * If the contents of the file should be encrypted (or decrypted) with inline
> > > + * encryption, then assign the appropriate encryption context to the bio.
> >
> > "If the contents of the file should be encrypted (or decrypted) with
> > inline encryption" => "If the extent should be encrypted (or decrypted)"
> >
> > There's no "file" here.  And inline encryption is the only option for
> > extents.
>
> OK.
>
> > > +/**
> > > + * fscrypt_mergeable_extent_bio() - test whether data can be added to a bio
> > > + * @bio: the bio being built up
> > > + * @ei: the fscrypt_extent_info for this extent
> >
> > @ei: the extent's crypto info, or NULL if the extent is unencrypted
>
> True.
>
> > > + * @pos: the next extent logical offset (in bytes) in the I/O
> > > + *
> > > + * When building a bio which may contain data which should undergo inline
> > > + * encryption (or decryption) via fscrypt,
> >
> > When building a bio which may contain data which should undergo extent
> > encryption (or decryption)
>
> Ack.
>
> > > +static struct fscrypt_extent_info *
> > > +setup_extent_info(struct inode *inode, const u8 nonce[FSCRYPT_FILE_NONCE_SIZE])
> > > +{
> > > +     struct fscrypt_extent_info *ei;
> > > +     struct fscrypt_inode_info *ci;
> > > +     struct fscrypt_master_key *mk;
> > > +     u8 derived_key[FSCRYPT_MAX_RAW_KEY_SIZE];
> > > +     int keysize;
> > > +     int err;
> > > +
> > > +     ci = *fscrypt_inode_info_addr(inode);
> >
> > fscrypt_get_inode_info_raw()
>
> That would add a useless VFS_WARN_ON_ONCE() which will never trigger;
> otherwise, the next line would crash.
> But I guess that's OK.
>
> > > +/**
> > > + * fscrypt_prepare_new_extent() - prepare to create a new extent for a file
> > > + * @inode: the encrypted inode
> > > + *
> > > + * If the inode is encrypted, setup the fscrypt_extent_info for a new extent.
> > > +
> > > + * This will include the nonce and the derived key necessary for the extent to
> > > + * be encrypted.  This is only meant to be used with inline crypto and on inodes
> > > + * that need their contents encrypted.
> >
> > This is ambiguous and contradictory about what type of @inode is
> > required.  It should be something like:
> >
> > * @inode: an encrypted regular file with its key already set up, on a
> > *        filesystem that uses per-extent encryption
> > *
> > * Prepare to encrypt a new extent by generating a new extent nonce,
> > * deriving an extent key, and allocating an fscrypt_extent_info.
>
> OK.
>
> > > * This doesn't persist the new extents encryption context, this is done later
> > > * by calling fscrypt_set_extent_context().
> >
> > There's no function with that name
>
> That should be fscrypt_context_for_new_extent(). I see it was renamed
> in v5 but this one (and the one below) was forgotten.
> Will fix.
>
> > > +     if (WARN_ON_ONCE(!*fscrypt_inode_info_addr(inode)))
> > > +             return ERR_PTR(-EOPNOTSUPP);
> > > +     if (WARN_ON_ONCE(!fscrypt_inode_uses_inline_crypto(inode)))
> > > +             return ERR_PTR(-EOPNOTSUPP);
> >
> > I'm confused what these checks are trying to do.  The first part checks
> > for the inode's encryption key, but setup_extent_info() does that
> > anyway.
>
> setup_extent_info() dereferences ci right away. Without this check it
> would crash if the inode was not encrypted.
>
> > ... The second part is maybe intended to check that the file uses
> > extent encryption, but it doesn't do it correctly.  That would require:
> > fscrypt_needs_contents_encryption(inode) &&
> > inode->i_sb->s_cop->has_per_extent_encryption.
>
> This may be better. The comment says this is only meant to be used
> with inline crypto but that actually looks fishy. We ensure this in
> btrfs but fscrypt itself does not need to.
>
> > It probably would make sense to check that directly in
> > setup_extent_info(), so that it's closer to the call to
> > fscrypt_hkdf_expand() which would has a *very* bad failure mode when
> > !has_per_extent_encryption.
>
> I think it makes sense to check before consuming entropy.
> setup_extent_info() can have a second check if needed, though perhaps
> it was not needed so far?
>
> > > +/**
> > > + * fscrypt_load_extent_info() - create an fscrypt_extent_info from the context
> > > + * @inode: the inode
> > > + * @ctx: the context buffer
> > > + * @ctx_size: the size of the context buffer
> > > + *
> > > + * Create the fscrypt_extent_info and derive the key based on the
> > > + * fscrypt_extent_context buffer that is provided.
> > > + *
> > > + * Return: The newly allocated fscrypt_extent_info on success, -EOPNOTSUPP if
> > > + *      we're not encrypted, or another -errno code
> > > + */
> >
> > What context is this expected to be called in?  I see the caller uses
> > memalloc_nofs_save().  This would require making ->mk_sem nofs-safe; is
> > there a plan to do that?  (Sashiko noticed this too, by the way.)
>
> Honestly, I have to admit I was puzzled by Sashiko's explanation and I
> did not trust it. It sounded rather off.
>
> Trying to dig up the origin of that memalloc_nofs_save() - it is there
> since the very beginning of Josef's [v1] posting. This was around the
> time of Linux v6.6 but the code is still similar.
> So far I don't see if it is really needed or why it was implemented
> this way in the first place. It is called from
> btrfs_do_readpage()->btrfs_get_extent() to set up the key context.
> IIUC, this should not be called from a reclaim path. Hence, I think
> that that memalloc_nofs_save() is redundant and it should be OK to
> simply remove it.

And removing it and testing with stress-ng induced memory pressure
seems to confirm the memalloc_nofs_save() is not needed in this code
path.

@Josef: By any chance, do you recall why this was originally put in?

--nX

> [v1] https://lore.kernel.org/linux-btrfs/de7dec18d4dd440ebf3c538af5c765d747e1d3ef.1695750478.git.josef@toxicpanda.com/
>
> > > +     const struct fscrypt_inode_info *ci = *fscrypt_inode_info_addr(inode);
> >
> > fscrypt_get_inode_info_raw(inode)
>
> OK.
>
> > > +/**
> > > + * fscrypt_set_extent_context() - Set the fscrypt extent context of a new extent
> >
> > It seems the function name and semantics changed at some point, but the
> > kerneldoc wasn't updated.
>
> As mentioned above, I see V4 -> v5 rename. No semantics changes.
>
> > > + * @inode: the inode this extent belongs to
> >
> > The inode that the extent will initially belong to, I guess?
>
> Yes, at this point it's a new extent for this inode.
>
> > > +ssize_t fscrypt_context_for_new_extent(struct inode *inode,
> > > +                                    struct fscrypt_extent_info *ei, u8 *buf)
> > > +{
> > > +     struct fscrypt_extent_context *ctx = (struct fscrypt_extent_context *)buf;
> > > +     const struct fscrypt_inode_info *ci = *fscrypt_inode_info_addr(inode);
> >
> > fscrypt_get_inode_info_raw(inode)
>
> Again, adds an useless WARN_ON_ONCE, but won't hurt.
>
> Thanks!
>
> --nX
>
> > - Eric
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.