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

fscrypt: handle test_dummy_encryption in more logical way



The behavior of the test_dummy_encryption mount option is that when a
new file (or directory or symlink) is created in an unencrypted
directory, it's automatically encrypted using a dummy encryption policy.
That's it; in particular, the encryption (or lack thereof) of existing
files (or directories or symlinks) doesn't change.

Unfortunately the implementation of test_dummy_encryption is a bit weird
and confusing.  When test_dummy_encryption is enabled and a file is
being created in an unencrypted directory, we set up an encryption key
(->i_crypt_info) for the directory.  This isn't actually used to do any
encryption, however, since the directory is still unencrypted!  Instead,
->i_crypt_info is only used for inheriting the encryption policy.

One consequence of this is that the filesystem ends up providing a
"dummy context" (policy + nonce) instead of a "dummy policy".  In
commit ed318a6c ("fscrypt: support test_dummy_encryption=v2"), I
mistakenly thought this was required.  However, actually the nonce only
ends up being used to derive a key that is never used.

Another consequence of this implementation is that it allows for
'inode->i_crypt_info != NULL && !IS_ENCRYPTED(inode)', which is an edge
case that can be forgotten about.  For example, currently
FS_IOC_GET_ENCRYPTION_POLICY on an unencrypted directory may return the
dummy encryption policy when the filesystem is mounted with
test_dummy_encryption.  That seems like the wrong thing to do, since
again, the directory itself is not actually encrypted.

Therefore, switch to a more logical and maintainable implementation
where the dummy encryption policy inheritance is done without setting up
keys for unencrypted directories.  This involves:

- Adding a function fscrypt_policy_to_inherit() which returns the
  encryption policy to inherit from a directory.  This can be a real
  policy, a dummy policy, or no policy.

- Replacing struct fscrypt_dummy_context, ->get_dummy_context(), etc.
  with struct fscrypt_dummy_policy, ->get_dummy_policy(), etc.

- Making fscrypt_fname_encrypted_size() take an fscrypt_policy instead
  of an inode.

Acked-by: default avatarJaegeuk Kim <jaegeuk@kernel.org>
Acked-by: default avatarJeff Layton <jlayton@kernel.org>
Link: https://lore.kernel.org/r/20200917041136.178600-13-ebiggers@kernel.org


Signed-off-by: default avatarEric Biggers <ebiggers@google.com>
parent 31114726
Loading
Loading
Loading
Loading
+6 −5
Original line number Diff line number Diff line
@@ -242,11 +242,11 @@ static int base64_decode(const char *src, int len, u8 *dst)
	return cp - dst;
}

bool fscrypt_fname_encrypted_size(const struct inode *inode, u32 orig_len,
				  u32 max_len, u32 *encrypted_len_ret)
bool fscrypt_fname_encrypted_size(const union fscrypt_policy *policy,
				  u32 orig_len, u32 max_len,
				  u32 *encrypted_len_ret)
{
	const struct fscrypt_info *ci = inode->i_crypt_info;
	int padding = 4 << (fscrypt_policy_flags(&ci->ci_policy) &
	int padding = 4 << (fscrypt_policy_flags(policy) &
			    FSCRYPT_POLICY_FLAGS_PAD_MASK);
	u32 encrypted_len;

@@ -418,7 +418,8 @@ int fscrypt_setup_filename(struct inode *dir, const struct qstr *iname,
		return ret;

	if (fscrypt_has_encryption_key(dir)) {
		if (!fscrypt_fname_encrypted_size(dir, iname->len,
		if (!fscrypt_fname_encrypted_size(&dir->i_crypt_info->ci_policy,
						  iname->len,
						  dir->i_sb->s_cop->max_namelen,
						  &fname->crypto_buf.len))
			return -ENAMETOOLONG;
+4 −2
Original line number Diff line number Diff line
@@ -291,8 +291,9 @@ void fscrypt_generate_iv(union fscrypt_iv *iv, u64 lblk_num,
/* fname.c */
int fscrypt_fname_encrypt(const struct inode *inode, const struct qstr *iname,
			  u8 *out, unsigned int olen);
bool fscrypt_fname_encrypted_size(const struct inode *inode, u32 orig_len,
				  u32 max_len, u32 *encrypted_len_ret);
bool fscrypt_fname_encrypted_size(const union fscrypt_policy *policy,
				  u32 orig_len, u32 max_len,
				  u32 *encrypted_len_ret);
extern const struct dentry_operations fscrypt_d_ops;

/* hkdf.c */
@@ -592,5 +593,6 @@ bool fscrypt_supported_policy(const union fscrypt_policy *policy_u,
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);

#endif /* _FSCRYPT_PRIVATE_H */
+12 −18
Original line number Diff line number Diff line
@@ -193,30 +193,24 @@ int fscrypt_prepare_symlink(struct inode *dir, const char *target,
			    unsigned int len, unsigned int max_len,
			    struct fscrypt_str *disk_link)
{
	int err;
	const union fscrypt_policy *policy;

	if (!IS_ENCRYPTED(dir) && !fscrypt_get_dummy_context(dir->i_sb)) {
	/*
	 * To calculate the size of the encrypted symlink target we need to know
	 * the amount of NUL padding, which is determined by the flags set in
	 * the encryption policy which will be inherited from the directory.
	 */
	policy = fscrypt_policy_to_inherit(dir);
	if (policy == NULL) {
		/* Not encrypted */
		disk_link->name = (unsigned char *)target;
		disk_link->len = len + 1;
		if (disk_link->len > max_len)
			return -ENAMETOOLONG;
		return 0;
	}

	/*
	 * To calculate the size of the encrypted symlink target we need to know
	 * the amount of NUL padding, which is determined by the flags set in
	 * the encryption policy which will be inherited from the directory.
	 * The easiest way to get access to this is to just load the directory's
	 * fscrypt_info, since we'll need it to create the dir_entry anyway.
	 *
	 * Note: in test_dummy_encryption mode, @dir may be unencrypted.
	 */
	err = fscrypt_get_encryption_info(dir);
	if (err)
		return err;
	if (!fscrypt_has_encryption_key(dir))
		return -ENOKEY;
	if (IS_ERR(policy))
		return PTR_ERR(policy);

	/*
	 * Calculate the size of the encrypted symlink and verify it won't
@@ -229,7 +223,7 @@ int fscrypt_prepare_symlink(struct inode *dir, const char *target,
	 * counting it (even though it is meaningless for ciphertext) is simpler
	 * for now since filesystems will assume it is there and subtract it.
	 */
	if (!fscrypt_fname_encrypted_size(dir, len,
	if (!fscrypt_fname_encrypted_size(policy, len,
					  max_len - sizeof(struct fscrypt_symlink_data),
					  &disk_link->len))
		return -ENAMETOOLONG;
+9 −24
Original line number Diff line number Diff line
@@ -547,7 +547,7 @@ out:

/**
 * fscrypt_get_encryption_info() - set up an inode's encryption key
 * @inode: the inode to set up the key for
 * @inode: the inode to set up the key for.  Must be encrypted.
 *
 * Set up ->i_crypt_info, if it hasn't already been done.
 *
@@ -569,19 +569,9 @@ int fscrypt_get_encryption_info(struct inode *inode)

	res = inode->i_sb->s_cop->get_context(inode, &ctx, sizeof(ctx));
	if (res < 0) {
		const union fscrypt_context *dummy_ctx =
			fscrypt_get_dummy_context(inode->i_sb);

		if (IS_ENCRYPTED(inode) || !dummy_ctx) {
			fscrypt_warn(inode,
				     "Error %d getting encryption context",
				     res);
		fscrypt_warn(inode, "Error %d getting encryption context", res);
		return res;
	}
		/* Fake up a context for an unencrypted directory */
		res = fscrypt_context_size(dummy_ctx);
		memcpy(&ctx, dummy_ctx, res);
	}

	res = fscrypt_policy_from_context(&policy, &ctx, res);
	if (res) {
@@ -627,17 +617,14 @@ EXPORT_SYMBOL(fscrypt_get_encryption_info);
int fscrypt_prepare_new_inode(struct inode *dir, struct inode *inode,
			      bool *encrypt_ret)
{
	int err;
	const union fscrypt_policy *policy;
	u8 nonce[FSCRYPT_FILE_NONCE_SIZE];

	if (!IS_ENCRYPTED(dir) && fscrypt_get_dummy_context(dir->i_sb) == NULL)
	policy = fscrypt_policy_to_inherit(dir);
	if (policy == NULL)
		return 0;

	err = fscrypt_get_encryption_info(dir);
	if (err)
		return err;
	if (!fscrypt_has_encryption_key(dir))
		return -ENOKEY;
	if (IS_ERR(policy))
		return PTR_ERR(policy);

	if (WARN_ON_ONCE(inode->i_mode == 0))
		return -EINVAL;
@@ -654,9 +641,7 @@ int fscrypt_prepare_new_inode(struct inode *dir, struct inode *inode,
	*encrypt_ret = true;

	get_random_bytes(nonce, FSCRYPT_FILE_NONCE_SIZE);
	return fscrypt_setup_encryption_info(inode,
					     &dir->i_crypt_info->ci_policy,
					     nonce,
	return fscrypt_setup_encryption_info(inode, policy, nonce,
					     IS_CASEFOLDED(dir) &&
					     S_ISDIR(inode->i_mode));
}
+69 −44
Original line number Diff line number Diff line
@@ -32,6 +32,14 @@ bool fscrypt_policies_equal(const union fscrypt_policy *policy1,
	return !memcmp(policy1, policy2, fscrypt_policy_size(policy1));
}

static const union fscrypt_policy *
fscrypt_get_dummy_policy(struct super_block *sb)
{
	if (!sb->s_cop->get_dummy_policy)
		return NULL;
	return sb->s_cop->get_dummy_policy(sb);
}

static bool fscrypt_valid_enc_modes(u32 contents_mode, u32 filenames_mode)
{
	if (contents_mode == FSCRYPT_MODE_AES_256_XTS &&
@@ -628,6 +636,25 @@ int fscrypt_has_permitted_context(struct inode *parent, struct inode *child)
}
EXPORT_SYMBOL(fscrypt_has_permitted_context);

/*
 * Return the encryption policy that new files in the directory will inherit, or
 * NULL if none, or an ERR_PTR() on error.  If the directory is encrypted, also
 * ensure that its key is set up, so that the new filename can be encrypted.
 */
const union fscrypt_policy *fscrypt_policy_to_inherit(struct inode *dir)
{
	int err;

	if (IS_ENCRYPTED(dir)) {
		err = fscrypt_require_key(dir);
		if (err)
			return ERR_PTR(err);
		return &dir->i_crypt_info->ci_policy;
	}

	return fscrypt_get_dummy_policy(dir->i_sb);
}

/**
 * fscrypt_set_context() - Set the fscrypt context of a new inode
 * @inode: a new inode
@@ -672,31 +699,28 @@ EXPORT_SYMBOL_GPL(fscrypt_set_context);
 * @sb: the filesystem on which test_dummy_encryption is being specified
 * @arg: the argument to the test_dummy_encryption option.
 *	 If no argument was specified, then @arg->from == NULL.
 * @dummy_ctx: the filesystem's current dummy context (input/output, see below)
 * @dummy_policy: the filesystem's current dummy policy (input/output, see
 *		  below)
 *
 * Handle the test_dummy_encryption mount option by creating a dummy encryption
 * context, saving it in @dummy_ctx, and adding the corresponding dummy
 * encryption key to the filesystem.  If the @dummy_ctx is already set, then
 * policy, saving it in @dummy_policy, and adding the corresponding dummy
 * encryption key to the filesystem.  If the @dummy_policy is already set, then
 * instead validate that it matches @arg.  Don't support changing it via
 * remount, as that is difficult to do safely.
 *
 * The reason we use an fscrypt_context rather than an fscrypt_policy is because
 * we mustn't generate a new nonce each time we access a dummy-encrypted
 * directory, as that would change the way filenames are encrypted.
 *
 * Return: 0 on success (dummy context set, or the same context is already set);
 *         -EEXIST if a different dummy context is already set;
 * Return: 0 on success (dummy policy set, or the same policy is already set);
 *         -EEXIST if a different dummy policy is already set;
 *         or another -errno value.
 */
int fscrypt_set_test_dummy_encryption(struct super_block *sb,
				      const substring_t *arg,
				      struct fscrypt_dummy_context *dummy_ctx)
				      struct fscrypt_dummy_policy *dummy_policy)
{
	const char *argstr = "v2";
	const char *argstr_to_free = NULL;
	struct fscrypt_key_specifier key_spec = { 0 };
	int version;
	union fscrypt_context *ctx = NULL;
	union fscrypt_policy *policy = NULL;
	int err;

	if (arg->from) {
@@ -706,12 +730,12 @@ int fscrypt_set_test_dummy_encryption(struct super_block *sb,
	}

	if (!strcmp(argstr, "v1")) {
		version = FSCRYPT_CONTEXT_V1;
		version = FSCRYPT_POLICY_V1;
		key_spec.type = FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR;
		memset(key_spec.u.descriptor, 0x42,
		       FSCRYPT_KEY_DESCRIPTOR_SIZE);
	} else if (!strcmp(argstr, "v2")) {
		version = FSCRYPT_CONTEXT_V2;
		version = FSCRYPT_POLICY_V2;
		key_spec.type = FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER;
		/* key_spec.u.identifier gets filled in when adding the key */
	} else {
@@ -719,21 +743,8 @@ int fscrypt_set_test_dummy_encryption(struct super_block *sb,
		goto out;
	}

	if (dummy_ctx->ctx) {
		/*
		 * Note: if we ever make test_dummy_encryption support
		 * specifying other encryption settings, such as the encryption
		 * modes, we'll need to compare those settings here.
		 */
		if (dummy_ctx->ctx->version == version)
			err = 0;
		else
			err = -EEXIST;
		goto out;
	}

	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
	if (!ctx) {
	policy = kzalloc(sizeof(*policy), GFP_KERNEL);
	if (!policy) {
		err = -ENOMEM;
		goto out;
	}
@@ -742,18 +753,18 @@ int fscrypt_set_test_dummy_encryption(struct super_block *sb,
	if (err)
		goto out;

	ctx->version = version;
	switch (ctx->version) {
	case FSCRYPT_CONTEXT_V1:
		ctx->v1.contents_encryption_mode = FSCRYPT_MODE_AES_256_XTS;
		ctx->v1.filenames_encryption_mode = FSCRYPT_MODE_AES_256_CTS;
		memcpy(ctx->v1.master_key_descriptor, key_spec.u.descriptor,
	policy->version = version;
	switch (policy->version) {
	case FSCRYPT_POLICY_V1:
		policy->v1.contents_encryption_mode = FSCRYPT_MODE_AES_256_XTS;
		policy->v1.filenames_encryption_mode = FSCRYPT_MODE_AES_256_CTS;
		memcpy(policy->v1.master_key_descriptor, key_spec.u.descriptor,
		       FSCRYPT_KEY_DESCRIPTOR_SIZE);
		break;
	case FSCRYPT_CONTEXT_V2:
		ctx->v2.contents_encryption_mode = FSCRYPT_MODE_AES_256_XTS;
		ctx->v2.filenames_encryption_mode = FSCRYPT_MODE_AES_256_CTS;
		memcpy(ctx->v2.master_key_identifier, key_spec.u.identifier,
	case FSCRYPT_POLICY_V2:
		policy->v2.contents_encryption_mode = FSCRYPT_MODE_AES_256_XTS;
		policy->v2.filenames_encryption_mode = FSCRYPT_MODE_AES_256_CTS;
		memcpy(policy->v2.master_key_identifier, key_spec.u.identifier,
		       FSCRYPT_KEY_IDENTIFIER_SIZE);
		break;
	default:
@@ -761,11 +772,19 @@ int fscrypt_set_test_dummy_encryption(struct super_block *sb,
		err = -EINVAL;
		goto out;
	}
	dummy_ctx->ctx = ctx;
	ctx = NULL;

	if (dummy_policy->policy) {
		if (fscrypt_policies_equal(policy, dummy_policy->policy))
			err = 0;
		else
			err = -EEXIST;
		goto out;
	}
	dummy_policy->policy = policy;
	policy = NULL;
	err = 0;
out:
	kfree(ctx);
	kfree(policy);
	kfree(argstr_to_free);
	return err;
}
@@ -783,10 +802,16 @@ EXPORT_SYMBOL_GPL(fscrypt_set_test_dummy_encryption);
void fscrypt_show_test_dummy_encryption(struct seq_file *seq, char sep,
					struct super_block *sb)
{
	const union fscrypt_context *ctx = fscrypt_get_dummy_context(sb);
	const union fscrypt_policy *policy = fscrypt_get_dummy_policy(sb);
	int vers;

	if (!ctx)
	if (!policy)
		return;
	seq_printf(seq, "%ctest_dummy_encryption=v%d", sep, ctx->version);

	vers = policy->version;
	if (vers == FSCRYPT_POLICY_V1) /* Handle numbering quirk */
		vers = 1;

	seq_printf(seq, "%ctest_dummy_encryption=v%d", sep, vers);
}
EXPORT_SYMBOL_GPL(fscrypt_show_test_dummy_encryption);
Loading