Commit fea30433 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull ext4 updates from Ted Ts'o:
 "Various bug fixes and cleanups for ext4.

  In particular, move the crypto related fucntions from fs/ext4/super.c
  into a new fs/ext4/crypto.c, and fix a number of bugs found by fuzzers
  and error injection tools"

* tag 'ext4_for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4: (25 commits)
  ext4: only allow test_dummy_encryption when supported
  ext4: fix bug_on in __es_tree_search
  ext4: avoid cycles in directory h-tree
  ext4: verify dir block before splitting it
  ext4: filter out EXT4_FC_REPLAY from on-disk superblock field s_state
  ext4: fix bug_on in ext4_writepages
  ext4: refactor and move ext4_ioctl_get_encryption_pwsalt()
  ext4: cleanup function defs from ext4.h into crypto.c
  ext4: move ext4 crypto code to its own file crypto.c
  ext4: fix memory leak in parse_apply_sb_mount_options()
  ext4: reject the 'commit' option on ext2 filesystems
  ext4: remove duplicated #include of dax.h in inode.c
  ext4: fix race condition between ext4_write and ext4_convert_inline_data
  ext4: convert symlink external data block mapping to bdev
  ext4: add nowait mode for ext4_getblk()
  ext4: fix journal_ioprio mount option handling
  ext4: mark group as trimmed only if it was fully scanned
  ext4: fix use-after-free in ext4_rename_dir_prepare
  ext4: add unmount filesystem message
  ext4: remove unnecessary conditionals
  ...
parents 7208c984 5f41fdae
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -17,3 +17,4 @@ ext4-$(CONFIG_EXT4_FS_SECURITY) += xattr_security.o
ext4-inode-test-objs			+= inode-test.o
obj-$(CONFIG_EXT4_KUNIT_TESTS)		+= ext4-inode-test.o
ext4-$(CONFIG_FS_VERITY)		+= verity.o
ext4-$(CONFIG_FS_ENCRYPTION)		+= crypto.o

fs/ext4/crypto.c

0 → 100644
+246 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0

#include <linux/quotaops.h>
#include <linux/uuid.h>

#include "ext4.h"
#include "xattr.h"
#include "ext4_jbd2.h"

static void ext4_fname_from_fscrypt_name(struct ext4_filename *dst,
					 const struct fscrypt_name *src)
{
	memset(dst, 0, sizeof(*dst));

	dst->usr_fname = src->usr_fname;
	dst->disk_name = src->disk_name;
	dst->hinfo.hash = src->hash;
	dst->hinfo.minor_hash = src->minor_hash;
	dst->crypto_buf = src->crypto_buf;
}

int ext4_fname_setup_filename(struct inode *dir, const struct qstr *iname,
			      int lookup, struct ext4_filename *fname)
{
	struct fscrypt_name name;
	int err;

	err = fscrypt_setup_filename(dir, iname, lookup, &name);
	if (err)
		return err;

	ext4_fname_from_fscrypt_name(fname, &name);

#if IS_ENABLED(CONFIG_UNICODE)
	err = ext4_fname_setup_ci_filename(dir, iname, fname);
#endif
	return err;
}

int ext4_fname_prepare_lookup(struct inode *dir, struct dentry *dentry,
			      struct ext4_filename *fname)
{
	struct fscrypt_name name;
	int err;

	err = fscrypt_prepare_lookup(dir, dentry, &name);
	if (err)
		return err;

	ext4_fname_from_fscrypt_name(fname, &name);

#if IS_ENABLED(CONFIG_UNICODE)
	err = ext4_fname_setup_ci_filename(dir, &dentry->d_name, fname);
#endif
	return err;
}

void ext4_fname_free_filename(struct ext4_filename *fname)
{
	struct fscrypt_name name;

	name.crypto_buf = fname->crypto_buf;
	fscrypt_free_filename(&name);

	fname->crypto_buf.name = NULL;
	fname->usr_fname = NULL;
	fname->disk_name.name = NULL;

#if IS_ENABLED(CONFIG_UNICODE)
	kfree(fname->cf_name.name);
	fname->cf_name.name = NULL;
#endif
}

static bool uuid_is_zero(__u8 u[16])
{
	int i;

	for (i = 0; i < 16; i++)
		if (u[i])
			return false;
	return true;
}

int ext4_ioctl_get_encryption_pwsalt(struct file *filp, void __user *arg)
{
	struct super_block *sb = file_inode(filp)->i_sb;
	struct ext4_sb_info *sbi = EXT4_SB(sb);
	int err, err2;
	handle_t *handle;

	if (!ext4_has_feature_encrypt(sb))
		return -EOPNOTSUPP;

	if (uuid_is_zero(sbi->s_es->s_encrypt_pw_salt)) {
		err = mnt_want_write_file(filp);
		if (err)
			return err;
		handle = ext4_journal_start_sb(sb, EXT4_HT_MISC, 1);
		if (IS_ERR(handle)) {
			err = PTR_ERR(handle);
			goto pwsalt_err_exit;
		}
		err = ext4_journal_get_write_access(handle, sb, sbi->s_sbh,
						    EXT4_JTR_NONE);
		if (err)
			goto pwsalt_err_journal;
		lock_buffer(sbi->s_sbh);
		generate_random_uuid(sbi->s_es->s_encrypt_pw_salt);
		ext4_superblock_csum_set(sb);
		unlock_buffer(sbi->s_sbh);
		err = ext4_handle_dirty_metadata(handle, NULL, sbi->s_sbh);
pwsalt_err_journal:
		err2 = ext4_journal_stop(handle);
		if (err2 && !err)
			err = err2;
pwsalt_err_exit:
		mnt_drop_write_file(filp);
		if (err)
			return err;
	}

	if (copy_to_user(arg, sbi->s_es->s_encrypt_pw_salt, 16))
		return -EFAULT;
	return 0;
}

static int ext4_get_context(struct inode *inode, void *ctx, size_t len)
{
	return ext4_xattr_get(inode, EXT4_XATTR_INDEX_ENCRYPTION,
				 EXT4_XATTR_NAME_ENCRYPTION_CONTEXT, ctx, len);
}

static int ext4_set_context(struct inode *inode, const void *ctx, size_t len,
							void *fs_data)
{
	handle_t *handle = fs_data;
	int res, res2, credits, retries = 0;

	/*
	 * Encrypting the root directory is not allowed because e2fsck expects
	 * lost+found to exist and be unencrypted, and encrypting the root
	 * directory would imply encrypting the lost+found directory as well as
	 * the filename "lost+found" itself.
	 */
	if (inode->i_ino == EXT4_ROOT_INO)
		return -EPERM;

	if (WARN_ON_ONCE(IS_DAX(inode) && i_size_read(inode)))
		return -EINVAL;

	if (ext4_test_inode_flag(inode, EXT4_INODE_DAX))
		return -EOPNOTSUPP;

	res = ext4_convert_inline_data(inode);
	if (res)
		return res;

	/*
	 * If a journal handle was specified, then the encryption context is
	 * being set on a new inode via inheritance and is part of a larger
	 * transaction to create the inode.  Otherwise the encryption context is
	 * being set on an existing inode in its own transaction.  Only in the
	 * latter case should the "retry on ENOSPC" logic be used.
	 */

	if (handle) {
		res = ext4_xattr_set_handle(handle, inode,
					    EXT4_XATTR_INDEX_ENCRYPTION,
					    EXT4_XATTR_NAME_ENCRYPTION_CONTEXT,
					    ctx, len, 0);
		if (!res) {
			ext4_set_inode_flag(inode, EXT4_INODE_ENCRYPT);
			ext4_clear_inode_state(inode,
					EXT4_STATE_MAY_INLINE_DATA);
			/*
			 * Update inode->i_flags - S_ENCRYPTED will be enabled,
			 * S_DAX may be disabled
			 */
			ext4_set_inode_flags(inode, false);
		}
		return res;
	}

	res = dquot_initialize(inode);
	if (res)
		return res;
retry:
	res = ext4_xattr_set_credits(inode, len, false /* is_create */,
				     &credits);
	if (res)
		return res;

	handle = ext4_journal_start(inode, EXT4_HT_MISC, credits);
	if (IS_ERR(handle))
		return PTR_ERR(handle);

	res = ext4_xattr_set_handle(handle, inode, EXT4_XATTR_INDEX_ENCRYPTION,
				    EXT4_XATTR_NAME_ENCRYPTION_CONTEXT,
				    ctx, len, 0);
	if (!res) {
		ext4_set_inode_flag(inode, EXT4_INODE_ENCRYPT);
		/*
		 * Update inode->i_flags - S_ENCRYPTED will be enabled,
		 * S_DAX may be disabled
		 */
		ext4_set_inode_flags(inode, false);
		res = ext4_mark_inode_dirty(handle, inode);
		if (res)
			EXT4_ERROR_INODE(inode, "Failed to mark inode dirty");
	}
	res2 = ext4_journal_stop(handle);

	if (res == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries))
		goto retry;
	if (!res)
		res = res2;
	return res;
}

static const union fscrypt_policy *ext4_get_dummy_policy(struct super_block *sb)
{
	return EXT4_SB(sb)->s_dummy_enc_policy.policy;
}

static bool ext4_has_stable_inodes(struct super_block *sb)
{
	return ext4_has_feature_stable_inodes(sb);
}

static void ext4_get_ino_and_lblk_bits(struct super_block *sb,
				       int *ino_bits_ret, int *lblk_bits_ret)
{
	*ino_bits_ret = 8 * sizeof(EXT4_SB(sb)->s_es->s_inodes_count);
	*lblk_bits_ret = 8 * sizeof(ext4_lblk_t);
}

const struct fscrypt_operations ext4_cryptops = {
	.key_prefix		= "ext4:",
	.get_context		= ext4_get_context,
	.set_context		= ext4_set_context,
	.get_dummy_policy	= ext4_get_dummy_policy,
	.empty_dir		= ext4_empty_dir,
	.has_stable_inodes	= ext4_has_stable_inodes,
	.get_ino_and_lblk_bits	= ext4_get_ino_and_lblk_bits,
};
+3 −3
Original line number Diff line number Diff line
@@ -412,7 +412,7 @@ struct fname {
};

/*
 * This functoin implements a non-recursive way of freeing all of the
 * This function implements a non-recursive way of freeing all of the
 * nodes in the red-black tree.
 */
static void free_rb_tree_fname(struct rb_root *root)
@@ -515,7 +515,7 @@ int ext4_htree_store_dirent(struct file *dir_file, __u32 hash,

/*
 * This is a helper function for ext4_dx_readdir.  It calls filldir
 * for all entres on the fname linked list.  (Normally there is only
 * for all entries on the fname linked list.  (Normally there is only
 * one entry on the linked list, unless there are 62 bit hash collisions.)
 */
static int call_filldir(struct file *file, struct dir_context *ctx,
@@ -648,7 +648,7 @@ int ext4_check_all_de(struct inode *dir, struct buffer_head *bh, void *buf,
	unsigned int offset = 0;
	char *top;

	de = (struct ext4_dir_entry_2 *)buf;
	de = buf;
	top = buf + buf_size;
	while ((char *) de < top) {
		if (ext4_check_dir_entry(dir, NULL, de, bh,
+16 −68
Original line number Diff line number Diff line
@@ -673,6 +673,8 @@ enum {
	/* Caller will submit data before dropping transaction handle. This
	 * allows jbd2 to avoid submitting data before commit. */
#define EXT4_GET_BLOCKS_IO_SUBMIT		0x0400
	/* Caller is in the atomic contex, find extent if it has been cached */
#define EXT4_GET_BLOCKS_CACHED_NOWAIT		0x0800

/*
 * The bit position of these flags must not overlap with any of the
@@ -1440,12 +1442,6 @@ struct ext4_super_block {

#ifdef __KERNEL__

#ifdef CONFIG_FS_ENCRYPTION
#define DUMMY_ENCRYPTION_ENABLED(sbi) ((sbi)->s_dummy_enc_policy.policy != NULL)
#else
#define DUMMY_ENCRYPTION_ENABLED(sbi) (0)
#endif

/* Number of quota types we support */
#define EXT4_MAXQUOTAS 3

@@ -2731,74 +2727,20 @@ extern int ext4_fname_setup_ci_filename(struct inode *dir,
					 struct ext4_filename *fname);
#endif

/* ext4 encryption related stuff goes here crypto.c */
#ifdef CONFIG_FS_ENCRYPTION
static inline void ext4_fname_from_fscrypt_name(struct ext4_filename *dst,
						const struct fscrypt_name *src)
{
	memset(dst, 0, sizeof(*dst));

	dst->usr_fname = src->usr_fname;
	dst->disk_name = src->disk_name;
	dst->hinfo.hash = src->hash;
	dst->hinfo.minor_hash = src->minor_hash;
	dst->crypto_buf = src->crypto_buf;
}
extern const struct fscrypt_operations ext4_cryptops;

static inline int ext4_fname_setup_filename(struct inode *dir,
					    const struct qstr *iname,
					    int lookup,
					    struct ext4_filename *fname)
{
	struct fscrypt_name name;
	int err;
int ext4_fname_setup_filename(struct inode *dir, const struct qstr *iname,
			      int lookup, struct ext4_filename *fname);

	err = fscrypt_setup_filename(dir, iname, lookup, &name);
	if (err)
		return err;

	ext4_fname_from_fscrypt_name(fname, &name);

#if IS_ENABLED(CONFIG_UNICODE)
	err = ext4_fname_setup_ci_filename(dir, iname, fname);
#endif
	return err;
}

static inline int ext4_fname_prepare_lookup(struct inode *dir,
					    struct dentry *dentry,
					    struct ext4_filename *fname)
{
	struct fscrypt_name name;
	int err;

	err = fscrypt_prepare_lookup(dir, dentry, &name);
	if (err)
		return err;

	ext4_fname_from_fscrypt_name(fname, &name);

#if IS_ENABLED(CONFIG_UNICODE)
	err = ext4_fname_setup_ci_filename(dir, &dentry->d_name, fname);
#endif
	return err;
}

static inline void ext4_fname_free_filename(struct ext4_filename *fname)
{
	struct fscrypt_name name;
int ext4_fname_prepare_lookup(struct inode *dir, struct dentry *dentry,
			      struct ext4_filename *fname);

	name.crypto_buf = fname->crypto_buf;
	fscrypt_free_filename(&name);
void ext4_fname_free_filename(struct ext4_filename *fname);

	fname->crypto_buf.name = NULL;
	fname->usr_fname = NULL;
	fname->disk_name.name = NULL;
int ext4_ioctl_get_encryption_pwsalt(struct file *filp, void __user *arg);

#if IS_ENABLED(CONFIG_UNICODE)
	kfree(fname->cf_name.name);
	fname->cf_name.name = NULL;
#endif
}
#else /* !CONFIG_FS_ENCRYPTION */
static inline int ext4_fname_setup_filename(struct inode *dir,
					    const struct qstr *iname,
@@ -2831,6 +2773,12 @@ static inline void ext4_fname_free_filename(struct ext4_filename *fname)
	fname->cf_name.name = NULL;
#endif
}

static inline int ext4_ioctl_get_encryption_pwsalt(struct file *filp,
						   void __user *arg)
{
	return -EOPNOTSUPP;
}
#endif /* !CONFIG_FS_ENCRYPTION */

/* dir.c */
+11 −9
Original line number Diff line number Diff line
@@ -372,7 +372,7 @@ static int ext4_valid_extent_entries(struct inode *inode,
{
	unsigned short entries;
	ext4_lblk_t lblock = 0;
	ext4_lblk_t prev = 0;
	ext4_lblk_t cur = 0;

	if (eh->eh_entries == 0)
		return 1;
@@ -396,11 +396,11 @@ static int ext4_valid_extent_entries(struct inode *inode,

			/* Check for overlapping extents */
			lblock = le32_to_cpu(ext->ee_block);
			if ((lblock <= prev) && prev) {
			if (lblock < cur) {
				*pblk = ext4_ext_pblock(ext);
				return 0;
			}
			prev = lblock + ext4_ext_get_actual_len(ext) - 1;
			cur = lblock + ext4_ext_get_actual_len(ext);
			ext++;
			entries--;
		}
@@ -420,13 +420,13 @@ static int ext4_valid_extent_entries(struct inode *inode,

			/* Check for overlapping index extents */
			lblock = le32_to_cpu(ext_idx->ei_block);
			if ((lblock <= prev) && prev) {
			if (lblock < cur) {
				*pblk = ext4_idx_pblock(ext_idx);
				return 0;
			}
			ext_idx++;
			entries--;
			prev = lblock;
			cur = lblock + 1;
		}
	}
	return 1;
@@ -4693,15 +4693,17 @@ long ext4_fallocate(struct file *file, int mode, loff_t offset, loff_t len)
		     FALLOC_FL_INSERT_RANGE))
		return -EOPNOTSUPP;

	inode_lock(inode);
	ret = ext4_convert_inline_data(inode);
	inode_unlock(inode);
	if (ret)
		goto exit;

	if (mode & FALLOC_FL_PUNCH_HOLE) {
		ret = ext4_punch_hole(file, offset, len);
		goto exit;
	}

	ret = ext4_convert_inline_data(inode);
	if (ret)
		goto exit;

	if (mode & FALLOC_FL_COLLAPSE_RANGE) {
		ret = ext4_collapse_range(file, offset, len);
		goto exit;
Loading