Add new ioctls which allow for the metadata of encrypted files (both the filename and the crypto policy) to be backed up and restored. Signed-off-by: Theodore Ts'o <tytso@xxxxxxx> --- fs/ext4/crypto_key.c | 124 ++++++++++++++++++++++++++++++++++++- fs/ext4/ext4.h | 12 ++++ fs/ext4/ext4_crypto.h | 16 +++++ fs/ext4/ioctl.c | 87 ++++++++++++++++++++++++++ fs/ext4/namei.c | 165 ++++++++++++++++++++++++++++++++++++++++++-------- 5 files changed, 379 insertions(+), 25 deletions(-) diff --git a/fs/ext4/crypto_key.c b/fs/ext4/crypto_key.c index 9a16d1e..4f4b2c8 100644 --- a/fs/ext4/crypto_key.c +++ b/fs/ext4/crypto_key.c @@ -10,11 +10,12 @@ #include <keys/encrypted-type.h> #include <keys/user-type.h> +#include <linux/crc16.h> #include <linux/random.h> #include <linux/scatterlist.h> #include <uapi/linux/keyctl.h> -#include "ext4.h" +#include "ext4_jbd2.h" #include "xattr.h" static void derive_crypt_complete(struct crypto_async_request *req, int rc) @@ -274,3 +275,124 @@ int ext4_has_encryption_key(struct inode *inode) return (ei->i_crypt_info != NULL); } + +int ext4_get_encryption_metadata(struct inode *inode, + struct ext4_encrypted_metadata *mdata) +{ + unsigned char *cp = &mdata->metadata[0]; + size_t size = mdata->len; + loff_t isize; + int res; + + if (size < sizeof(struct ext4_encryption_context) + 12) + return -EINVAL; + + if (!inode_owner_or_capable(inode) && !capable(CAP_SYS_ADMIN)) + return -EACCES; + + *cp++ = 'e'; + *cp++ = '5'; + *cp++ = 0; + *cp++ = 0; + isize = i_size_read(inode); + *((u32 *)cp) = cpu_to_le32(isize & 0xFFFFFFFF); + cp += 4; + *((u32 *)cp) = cpu_to_le32(isize >> 32); + cp += 4; + size -= 12; + + res = ext4_xattr_get(inode, EXT4_XATTR_INDEX_ENCRYPTION, + EXT4_XATTR_NAME_ENCRYPTION_CONTEXT, + cp, size); + + if (res < 0) + return res; + if (res > size) + return -ENOSPC; + + mdata->len = res + 12; + + *((u16 *) &mdata->metadata[2]) = cpu_to_le16(crc16(~0, mdata->metadata, mdata->len)); + return 0; +} + +int ext4_set_encryption_metadata(struct inode *inode, + struct ext4_encrypted_metadata *mdata) +{ + struct ext4_encryption_context *ctx; + unsigned char *cp = &mdata->metadata[0]; + handle_t *handle = NULL; + loff_t size; + unsigned bs = inode->i_sb->s_blocksize; + int res; + u16 crc; + + if (!inode_owner_or_capable(inode) && !capable(CAP_SYS_ADMIN)) + return -EACCES; + + if (!S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode)) + return -EINVAL; + + if (mdata->len != sizeof(struct ext4_encryption_context) + 12) + return -EINVAL; + + if (cp[0] != 'e' || cp[1] != '5') + return -EINVAL; + crc = le16_to_cpu(*(u16 *)(cp+2)); + cp[2] = cp[3] = 0; + cp += 4; + + if (crc != crc16(~0, mdata->metadata, mdata->len)) + return -EINVAL; + + size = le32_to_cpu(*(u32 *) cp); + cp += 4; + size += ((u64) le32_to_cpu(*(u32 *) cp)) << 32; + cp += 4; + + ctx = (struct ext4_encryption_context *) cp; + if ((ctx->format != EXT4_ENCRYPTION_CONTEXT_FORMAT_V1) || + !ext4_valid_contents_enc_mode(ctx->contents_encryption_mode) || + !ext4_valid_filenames_enc_mode(ctx->filenames_encryption_mode) || + (ctx->flags & ~EXT4_POLICY_FLAGS_VALID)) + return -EINVAL; + + res = ext4_convert_inline_data(inode); + if (res) + return res; + + res = filemap_write_and_wait(&inode->i_data); + if (res) + return res; + + mutex_lock(&inode->i_mutex); + if (round_up(size, bs) != round_up(i_size_read(inode), bs)) { + res = -EINVAL; + goto errout; + } + + handle = ext4_journal_start(inode, EXT4_HT_MISC, + ext4_jbd2_credits_xattr(inode)); + if (IS_ERR(handle)) + return PTR_ERR(handle); + res = ext4_xattr_set(inode, EXT4_XATTR_INDEX_ENCRYPTION, + EXT4_XATTR_NAME_ENCRYPTION_CONTEXT, ctx, + sizeof(struct ext4_encryption_context), 0); + if (res < 0) + goto errout; + ext4_set_inode_flag(inode, EXT4_INODE_ENCRYPT); + ext4_clear_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA); + + i_size_write(inode, size); + EXT4_I(inode)->i_disksize = size; + res = ext4_mark_inode_dirty(handle, inode); + if (res) + EXT4_ERROR_INODE(inode, "Failed to mark inode dirty"); + else + res = ext4_get_encryption_info(inode); +errout: + mutex_unlock(&inode->i_mutex); + if (handle) + ext4_journal_stop(handle); + return res; +} diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index 9fdbd06..1136f03 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -617,6 +617,10 @@ enum { #define EXT4_IOC_SET_ENCRYPTION_POLICY _IOR('f', 19, struct ext4_encryption_policy) #define EXT4_IOC_GET_ENCRYPTION_PWSALT _IOW('f', 20, __u8[16]) #define EXT4_IOC_GET_ENCRYPTION_POLICY _IOW('f', 21, struct ext4_encryption_policy) +#define EXT4_IOC_GET_ENCRYPTION_METADATA _IOWR('f', 22, struct ext4_encrypted_metadata) +#define EXT4_IOC_SET_ENCRYPTION_METADATA _IOR('f', 23, struct ext4_encrypted_metadata) +#define EXT4_IOC_GET_ENCRYPTED_FILENAME _IOWR('f', 24, struct ext4_encrypted_metadata) +#define EXT4_IOC_SET_ENCRYPTED_FILENAME _IOR('f', 25, struct ext4_set_encrypted_fname) #if defined(__KERNEL__) && defined(CONFIG_COMPAT) /* @@ -2311,6 +2315,10 @@ static inline void ext4_fname_free_filename(struct ext4_filename *fname) { } void ext4_free_crypt_info(struct ext4_crypt_info *ci); void ext4_free_encryption_info(struct inode *inode, struct ext4_crypt_info *ci); int _ext4_get_encryption_info(struct inode *inode); +int ext4_set_encryption_metadata(struct inode *inode, + struct ext4_encrypted_metadata *mdata); +int ext4_get_encryption_metadata(struct inode *inode, + struct ext4_encrypted_metadata *mdata); #ifdef CONFIG_EXT4_FS_ENCRYPTION int ext4_has_encryption_key(struct inode *inode); @@ -2546,6 +2554,10 @@ extern int ext4_generic_delete_entry(handle_t *handle, int buf_size, int csum_size); extern int ext4_empty_dir(struct inode *inode); +extern int ext4_get_encrypted_filename(struct file *filp, + struct ext4_encrypted_metadata *mdata); +extern int ext4_set_encrypted_filename(struct inode *dir, + struct ext4_set_encrypted_fname *efn); /* resize.c */ extern int ext4_group_add(struct super_block *sb, diff --git a/fs/ext4/ext4_crypto.h b/fs/ext4/ext4_crypto.h index ac7d4e8..eb7088a 100644 --- a/fs/ext4/ext4_crypto.h +++ b/fs/ext4/ext4_crypto.h @@ -156,4 +156,20 @@ static inline u32 encrypted_symlink_data_len(u32 l) return (l + sizeof(struct ext4_encrypted_symlink_data) - 1); } +/** + * Structure used for communicating encrypted metadata with userspace + */ +struct ext4_encrypted_metadata { + u32 len; + unsigned char metadata[288]; +}; + +/** + * Structured used for setting an encrypted file name + */ +struct ext4_set_encrypted_fname { + s32 fd; + u32 len; + unsigned char enc_fname[256]; +}; #endif /* _EXT4_CRYPTO_H */ diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c index 5e872fd..e86a39e 100644 --- a/fs/ext4/ioctl.c +++ b/fs/ext4/ioctl.c @@ -689,6 +689,90 @@ encryption_policy_out: return -EOPNOTSUPP; #endif } + case EXT4_IOC_GET_ENCRYPTION_METADATA: { +#ifdef CONFIG_EXT4_FS_ENCRYPTION + struct ext4_encrypted_metadata mdata; + int err = 0; + + if (get_user(mdata.len, (u32 __user *) arg)) + return -EFAULT; + if (mdata.len > sizeof(mdata.metadata)) + return -EINVAL; + + if (!ext4_encrypted_inode(inode)) + return -ENOENT; + err = ext4_get_encryption_metadata(inode, &mdata); + if (err) + return err; + if (copy_to_user((void __user *)arg, &mdata, sizeof(mdata))) + return -EFAULT; + return 0; +#else + return -EOPNOTSUPP; +#endif + } + case EXT4_IOC_SET_ENCRYPTION_METADATA: { +#ifdef CONFIG_EXT4_FS_ENCRYPTION + struct ext4_encrypted_metadata mdata; + int err = 0; + + if (ext4_encrypted_inode(inode)) + return -EINVAL; + if (copy_from_user(&mdata, + (struct ext4_encrypted_metadata __user *)arg, + sizeof(mdata))) + return -EFAULT; + err = mnt_want_write_file(filp); + if (err) + return err; + err = ext4_set_encryption_metadata(inode, &mdata); + mnt_drop_write_file(filp); + return err; +#else + return -EOPNOTSUPP; +#endif + } + case EXT4_IOC_GET_ENCRYPTED_FILENAME: { +#ifdef CONFIG_EXT4_FS_ENCRYPTION + struct ext4_encrypted_metadata mdata; + int err = 0; + + if (get_user(mdata.len, (u32 __user *) arg)) + return -EFAULT; + if (mdata.len > sizeof(mdata.metadata)) + return -EINVAL; + + if (!ext4_encrypted_inode(inode)) + return -ENOENT; + err = ext4_get_encrypted_filename(filp, &mdata); + if (err) + return err; + if (copy_to_user((void __user *)arg, &mdata, sizeof(mdata))) + return -EFAULT; + return 0; +#else + return -EOPNOTSUPP; +#endif + } + case EXT4_IOC_SET_ENCRYPTED_FILENAME: { +#ifdef CONFIG_EXT4_FS_ENCRYPTION + struct ext4_set_encrypted_fname enc_fname; + int err = 0; + + if (copy_from_user(&enc_fname, + (struct ext4_set_encrypted_fname __user *)arg, + sizeof(enc_fname))) + return -EFAULT; + err = mnt_want_write_file(filp); + if (err) + return err; + err = ext4_set_encrypted_filename(inode, &enc_fname); + mnt_drop_write_file(filp); + return err; +#else + return -EOPNOTSUPP; +#endif + } default: return -ENOTTY; } @@ -755,6 +839,9 @@ long ext4_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) case EXT4_IOC_SET_ENCRYPTION_POLICY: case EXT4_IOC_GET_ENCRYPTION_PWSALT: case EXT4_IOC_GET_ENCRYPTION_POLICY: + case EXT4_IOC_GET_ENCRYPTION_METADATA: + case EXT4_IOC_SET_ENCRYPTION_METADATA: + case EXT4_IOC_GET_ENCRYPTED_FILENAME: break; default: return -ENOIOCTLCMD; diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 06c3afc..9e4d983 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -33,6 +33,7 @@ #include <linux/quotaops.h> #include <linux/buffer_head.h> #include <linux/bio.h> +#include <linux/file.h> #include "ext4.h" #include "ext4_jbd2.h" @@ -2048,24 +2049,16 @@ out_frames: } /* - * ext4_add_entry() - * - * adds a file entry to the specified directory, using the same - * semantics as ext4_find_entry(). It returns NULL if it failed. - * - * NOTE!! The inode part of 'de' is left at 0 - which means you - * may not sleep between calling this and putting something into - * the entry, as someone else might have used it while you slept. + * Add a directory entry to a directory, given the filename and the + * inode it will point to. */ -static int ext4_add_entry(handle_t *handle, struct dentry *dentry, - struct inode *inode) +static int ext4_add_fname(handle_t *handle, struct inode *dir, + struct ext4_filename *fname, struct inode *inode) { - struct inode *dir = d_inode(dentry->d_parent); struct buffer_head *bh = NULL; struct ext4_dir_entry_2 *de; struct ext4_dir_entry_tail *t; struct super_block *sb; - struct ext4_filename fname; int retval; int dx_fallback=0; unsigned blocksize; @@ -2077,15 +2070,9 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry, sb = dir->i_sb; blocksize = sb->s_blocksize; - if (!dentry->d_name.len) - return -EINVAL; - - retval = ext4_fname_setup_filename(dir, &dentry->d_name, 0, &fname); - if (retval) - return retval; if (ext4_has_inline_data(dir)) { - retval = ext4_try_add_inline_entry(handle, &fname, dir, inode); + retval = ext4_try_add_inline_entry(handle, fname, dir, inode); if (retval < 0) goto out; if (retval == 1) { @@ -2095,7 +2082,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry, } if (is_dx(dir)) { - retval = ext4_dx_add_entry(handle, &fname, dir, inode); + retval = ext4_dx_add_entry(handle, fname, dir, inode); if (!retval || (retval != ERR_BAD_DX_DIR)) goto out; ext4_clear_inode_flag(dir, EXT4_INODE_INDEX); @@ -2110,14 +2097,14 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry, bh = NULL; goto out; } - retval = add_dirent_to_buf(handle, &fname, dir, inode, + retval = add_dirent_to_buf(handle, fname, dir, inode, NULL, bh); if (retval != -ENOSPC) goto out; if (blocks == 1 && !dx_fallback && ext4_has_feature_dir_index(sb)) { - retval = make_indexed_dir(handle, &fname, dir, + retval = make_indexed_dir(handle, fname, dir, inode, bh); bh = NULL; /* make_indexed_dir releases bh */ goto out; @@ -2139,9 +2126,8 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry, initialize_dirent_tail(t, blocksize); } - retval = add_dirent_to_buf(handle, &fname, dir, inode, de, bh); + retval = add_dirent_to_buf(handle, fname, dir, inode, de, bh); out: - ext4_fname_free_filename(&fname); brelse(bh); if (retval == 0) ext4_set_inode_state(inode, EXT4_STATE_NEWENTRY); @@ -2149,6 +2135,29 @@ out: } /* + * Create a directory entry associated with the specified dentry and + * inode. + */ +static int ext4_add_entry(handle_t *handle, struct dentry *dentry, + struct inode *inode) +{ + struct inode *dir = d_inode(dentry->d_parent); + struct ext4_filename fname; + int retval; + + if (!dentry->d_name.len) + return -EINVAL; + + retval = ext4_fname_setup_filename(dir, &dentry->d_name, 0, &fname); + if (retval) + return retval; + + retval = ext4_add_fname(handle, dir, &fname, inode); + ext4_fname_free_filename(&fname); + return retval; +} + +/* * Returns 0 for success, or a negative error value */ static int ext4_dx_add_entry(handle_t *handle, struct ext4_filename *fname, @@ -3858,3 +3867,111 @@ const struct inode_operations ext4_special_inode_operations = { .get_acl = ext4_get_acl, .set_acl = ext4_set_acl, }; + +int ext4_get_encrypted_filename(struct file *filp, + struct ext4_encrypted_metadata *mdata) +{ + struct dentry *dentry = filp->f_path.dentry; + struct inode *dir = dentry->d_parent->d_inode; + struct buffer_head *bh; + struct ext4_dir_entry_2 *de; + + if (!dir || !ext4_encrypted_inode(dir)) + return -EINVAL; + + if (!inode_owner_or_capable(dir) && !capable(CAP_SYS_ADMIN)) + return -EACCES; + + bh = ext4_find_entry(dir, &dentry->d_name, &de, NULL); + if (IS_ERR(bh)) + return PTR_ERR(bh); + if (de == NULL) + return -ENOENT; + + if (mdata->len < de->name_len) + return -ENOSPC; + mdata->len = de->name_len; + memcpy(mdata->metadata, de->name, de->name_len); + return 0; +} + +int ext4_set_encrypted_filename(struct inode *dir, + struct ext4_set_encrypted_fname *efn) +{ + handle_t *handle = NULL; + struct ext4_filename fname; + struct fd fd; + struct inode *inode; + umode_t mode; + int retval = 0; + + retval = inode_permission(dir, MAY_WRITE | MAY_EXEC); + if (retval) + return retval; + + if (efn->len >= sizeof(efn->enc_fname)) + return -EINVAL; + + fd = fdget(efn->fd); + if (!fd.file) + return -EBADF; + inode = file_inode(fd.file); + mode = inode->i_mode; + + retval = -EPERM; + if (!S_ISREG(mode)) + goto out; + + if ((mode & S_ISUID) || + ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP))) { + /* + * root or the inode owner can link even in the case + * of "unsafe" hard link sources. See + * safe_hardlink_sources() in fs/namei.c + */ + if (!inode_owner_or_capable(inode) && !capable(CAP_SYS_ADMIN)) { + retval = -EACCES; + goto out; + } + } + + retval = inode_permission(inode, MAY_READ | MAY_WRITE); + if (!retval && !inode_owner_or_capable(inode) && + !capable(CAP_SYS_ADMIN)) + goto out; + + if (!ext4_is_child_context_consistent_with_parent(dir, inode)) { + retval = -EPERM; + goto out; + } + + memset(&fname, 0, sizeof(fname)); + fname.disk_name.name = efn->enc_fname; + fname.disk_name.len = efn->len; + + handle = ext4_journal_start(dir, EXT4_HT_DIR, + (EXT4_DATA_TRANS_BLOCKS(dir->i_sb) + + EXT4_INDEX_EXTRA_TRANS_BLOCKS) + 2); + if (IS_ERR(handle)) { + retval = PTR_ERR(handle); + goto out; + } + + pr_err("ext4_add_fname\n"); + retval = ext4_add_fname(handle, dir, &fname, file_inode(fd.file)); + if (retval) + goto out; + + ext4_inc_count(handle, inode); + ext4_mark_inode_dirty(handle, inode); + if (S_ISDIR(inode->i_mode)) + ext4_inc_count(handle, dir); + ext4_update_dx_flag(dir); + ext4_mark_inode_dirty(handle, dir); + +out: + fdput(fd); + if (handle) + ext4_journal_stop(handle); + return retval; +} -- 2.5.0 -- To unsubscribe from this list: send the line "unsubscribe linux-ext4" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html