Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cfc3461a authored by Eric Biggers's avatar Eric Biggers
Browse files

ANDROID: fscrypt: handle direct I/O with IV_INO_LBLK_32



With the existing fscrypt IV generation methods, each file's data blocks
have contiguous DUNs.  Therefore the direct I/O code "just worked"
because it only submits logically contiguous bios.  But with
IV_INO_LBLK_32, the direct I/O code breaks because the DUN can wrap from
0xffffffff to 0.  We can't submit bios across such boundaries.

This is especially difficult to handle when block_size != PAGE_SIZE,
since in that case the DUN can wrap in the middle of a page.  Punt on
this case for now and just handle block_size == PAGE_SIZE.

Add and use a new function fscrypt_dio_supported() to check whether a
direct I/O request is unsupported due to encryption constraints.

Then, update fs/direct-io.c (used by f2fs, and by ext4 in kernel v5.4
and earlier) and fs/iomap/direct-io.c (used by ext4 in kernel v5.5 and
later) to avoid submitting I/O across a DUN discontinuity.

(This is needed in ACK now because ACK already supports direct I/O with
inline crypto.  I'll be sending this upstream along with the encrypted
direct I/O support itself once its prerequisites are closer to landing.)

(cherry picked from android-mainline commit
 8d6c90c9d68b985fa809626d12f8c9aff3c9dcb1)

Conflicts:
	fs/ext4/file.c
	fs/iomap/direct-io.c

(Dropped the iomap changes because in kernel v5.4 and earlier,
 ext4 doesn't use iomap for direct I/O)

Test: For now, just manually tested direct I/O on ext4 and f2fs in the
      DUN discontinuity case.
Bug: 144046242
Change-Id: I0c0b0b20a73ade35c3660cc6f9c09d49d3853ba5
Signed-off-by: default avatarEric Biggers <ebiggers@google.com>
parent 4286f44b
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -67,6 +67,14 @@ void fscrypt_free_bounce_page(struct page *bounce_page)
}
EXPORT_SYMBOL(fscrypt_free_bounce_page);

/*
 * Generate the IV for the given logical block number within the given file.
 * For filenames encryption, lblk_num == 0.
 *
 * Keep this in sync with fscrypt_limit_dio_pages().  fscrypt_limit_dio_pages()
 * needs to know about any IV generation methods where the low bits of IV don't
 * simply contain the lblk_num (e.g., IV_INO_LBLK_32).
 */
void fscrypt_generate_iv(union fscrypt_iv *iv, u64 lblk_num,
			 const struct fscrypt_info *ci)
{
+82 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
#include <linux/blkdev.h>
#include <linux/buffer_head.h>
#include <linux/keyslot-manager.h>
#include <linux/uio.h>

#include "fscrypt_private.h"

@@ -428,3 +429,84 @@ bool fscrypt_mergeable_bio_bh(struct bio *bio,
	return fscrypt_mergeable_bio(bio, inode, next_lblk);
}
EXPORT_SYMBOL_GPL(fscrypt_mergeable_bio_bh);

/**
 * fscrypt_dio_supported() - check whether a direct I/O request is unsupported
 *			     due to encryption constraints
 * @iocb: the file and position the I/O is targeting
 * @iter: the I/O data segment(s)
 *
 * Return: true if direct I/O is supported
 */
bool fscrypt_dio_supported(struct kiocb *iocb, struct iov_iter *iter)
{
	const struct inode *inode = file_inode(iocb->ki_filp);
	const struct fscrypt_info *ci = inode->i_crypt_info;
	const unsigned int blocksize = i_blocksize(inode);

	/* If the file is unencrypted, no veto from us. */
	if (!fscrypt_needs_contents_encryption(inode))
		return true;

	/* We only support direct I/O with inline crypto, not fs-layer crypto */
	if (!fscrypt_inode_uses_inline_crypto(inode))
		return false;

	/*
	 * Since the granularity of encryption is filesystem blocks, the I/O
	 * must be block aligned -- not just disk sector aligned.
	 */
	if (!IS_ALIGNED(iocb->ki_pos | iov_iter_alignment(iter), blocksize))
		return false;

	/*
	 * With IV_INO_LBLK_32 and sub-page blocks, the DUN can wrap around in
	 * the middle of a page.  This isn't handled by the direct I/O code yet.
	 */
	if (blocksize != PAGE_SIZE &&
	    (fscrypt_policy_flags(&ci->ci_policy) &
	     FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32))
		return false;

	return true;
}
EXPORT_SYMBOL_GPL(fscrypt_dio_supported);

/**
 * fscrypt_limit_dio_pages() - limit I/O pages to avoid discontiguous DUNs
 * @inode: the file on which I/O is being done
 * @pos: the file position (in bytes) at which the I/O is being done
 * @nr_pages: the number of pages we want to submit starting at @pos
 *
 * For direct I/O: limit the number of pages that will be submitted in the bio
 * targeting @pos, in order to avoid crossing a data unit number (DUN)
 * discontinuity.  This is only needed for certain IV generation methods.
 *
 * This assumes block_size == PAGE_SIZE; see fscrypt_dio_supported().
 *
 * Return: the actual number of pages that can be submitted
 */
int fscrypt_limit_dio_pages(const struct inode *inode, loff_t pos, int nr_pages)
{
	const struct fscrypt_info *ci = inode->i_crypt_info;
	u32 dun;

	if (!fscrypt_inode_uses_inline_crypto(inode))
		return nr_pages;

	if (nr_pages <= 1)
		return nr_pages;

	if (!(fscrypt_policy_flags(&ci->ci_policy) &
	      FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32))
		return nr_pages;

	if (WARN_ON_ONCE(i_blocksize(inode) != PAGE_SIZE))
		return 1;

	/* With IV_INO_LBLK_32, the DUN can wrap around from U32_MAX to 0. */

	dun = ci->ci_hashed_ino + (pos >> inode->i_blkbits);

	return min_t(u64, nr_pages, (u64)U32_MAX + 1 - dun);
}
+9 −1
Original line number Diff line number Diff line
@@ -814,9 +814,17 @@ static inline int dio_send_cur_page(struct dio *dio, struct dio_submit *sdio,
		 * current logical offset in the file does not equal what would
		 * be the next logical offset in the bio, submit the bio we
		 * have.
		 *
		 * When fscrypt inline encryption is used, data unit number
		 * (DUN) contiguity is also required.  Normally that's implied
		 * by logical contiguity.  However, certain IV generation
		 * methods (e.g. IV_INO_LBLK_32) don't guarantee it.  So, we
		 * must explicitly check fscrypt_mergeable_bio() too.
		 */
		if (sdio->final_block_in_bio != sdio->cur_page_block ||
		    cur_offset != bio_next_offset)
		    cur_offset != bio_next_offset ||
		    !fscrypt_mergeable_bio(sdio->bio, dio->inode,
					   cur_offset >> dio->inode->i_blkbits))
			dio_bio_submit(dio, sdio);
	}

+3 −6
Original line number Diff line number Diff line
@@ -3918,12 +3918,9 @@ static ssize_t ext4_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
	ssize_t ret;
	int rw = iov_iter_rw(iter);

	if (IS_ENABLED(CONFIG_FS_ENCRYPTION) && IS_ENCRYPTED(inode)) {
		if (!fscrypt_inode_uses_inline_crypto(inode) ||
		    !IS_ALIGNED(iocb->ki_pos | iov_iter_alignment(iter),
				i_blocksize(inode)))
	if (!fscrypt_dio_supported(iocb, iter))
		return 0;
	}

	if (fsverity_active(inode))
		return 0;

+2 −6
Original line number Diff line number Diff line
@@ -4032,12 +4032,8 @@ static inline bool f2fs_force_buffered_io(struct inode *inode,
	struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
	int rw = iov_iter_rw(iter);

	if (IS_ENABLED(CONFIG_FS_ENCRYPTION) && f2fs_encrypted_file(inode)) {
		if (!fscrypt_inode_uses_inline_crypto(inode) ||
		    !IS_ALIGNED(iocb->ki_pos | iov_iter_alignment(iter),
				F2FS_BLKSIZE))
	if (!fscrypt_dio_supported(iocb, iter))
		return true;
	}
	if (fsverity_active(inode))
		return true;
	if (f2fs_is_multi_device(sbi))
Loading