* [PATCH v4 1/4] ext4 crypto: add missing locking for keyring_key access
2015-12-16 22:46 [PATCH v4 0/4] ext4 crypto: backup and restore encrypted files Theodore Ts'o
@ 2015-12-16 22:46 ` Theodore Ts'o
2015-12-16 22:46 ` [PATCH v4 2/4] ext4 crypto: add ciphertext_access mount option Theodore Ts'o
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Theodore Ts'o @ 2015-12-16 22:46 UTC (permalink / raw)
To: Ext4 Developers List; +Cc: Theodore Ts'o, stable
Cc: stable@kernel.org
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
---
fs/ext4/crypto_key.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/fs/ext4/crypto_key.c b/fs/ext4/crypto_key.c
index c5882b3..9a16d1e 100644
--- a/fs/ext4/crypto_key.c
+++ b/fs/ext4/crypto_key.c
@@ -213,9 +213,11 @@ retry:
res = -ENOKEY;
goto out;
}
+ down_read(&keyring_key->sem);
ukp = user_key_payload(keyring_key);
if (ukp->datalen != sizeof(struct ext4_encryption_key)) {
res = -EINVAL;
+ up_read(&keyring_key->sem);
goto out;
}
master_key = (struct ext4_encryption_key *)ukp->data;
@@ -226,10 +228,12 @@ retry:
"ext4: key size incorrect: %d\n",
master_key->size);
res = -ENOKEY;
+ up_read(&keyring_key->sem);
goto out;
}
res = ext4_derive_key_aes(ctx.nonce, master_key->raw,
raw_key);
+ up_read(&keyring_key->sem);
if (res)
goto out;
got_key:
--
2.5.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v4 2/4] ext4 crypto: add ciphertext_access mount option
2015-12-16 22:46 [PATCH v4 0/4] ext4 crypto: backup and restore encrypted files Theodore Ts'o
2015-12-16 22:46 ` [PATCH v4 1/4] ext4 crypto: add missing locking for keyring_key access Theodore Ts'o
@ 2015-12-16 22:46 ` Theodore Ts'o
2015-12-16 22:46 ` [PATCH v4 3/4] ext4 crypto: simplify interfaces to directory entry insert functions Theodore Ts'o
2015-12-16 22:46 ` [PATCH v4 4/4] ext4 crypto: add ioctls to allow backup of encryption metadata Theodore Ts'o
3 siblings, 0 replies; 5+ messages in thread
From: Theodore Ts'o @ 2015-12-16 22:46 UTC (permalink / raw)
To: Ext4 Developers List; +Cc: Theodore Ts'o
Add a mount option which allows root to be able to access the
ciphertext of a file by reading it using O_DIRECT.
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
---
fs/ext4/ext4.h | 3 +++
fs/ext4/file.c | 5 ++++-
fs/ext4/indirect.c | 24 +++++++++++++++++++-----
fs/ext4/inode.c | 17 ++++++++++-------
fs/ext4/super.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 84 insertions(+), 13 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 1e20fa9..cf7a885 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -1052,6 +1052,7 @@ struct ext4_inode_info {
#define EXT4_MOUNT_DIOREAD_NOLOCK 0x400000 /* Enable support for dio read nolocking */
#define EXT4_MOUNT_JOURNAL_CHECKSUM 0x800000 /* Journal checksums */
#define EXT4_MOUNT_JOURNAL_ASYNC_COMMIT 0x1000000 /* Journal Async Commit */
+#define EXT4_MOUNT_CIPHERTEXT_ACCESS 0x2000000 /* Direct I/O to ciphertext */
#define EXT4_MOUNT_DELALLOC 0x8000000 /* Delalloc support */
#define EXT4_MOUNT_DATA_ERR_ABORT 0x10000000 /* Abort on file data write */
#define EXT4_MOUNT_BLOCK_VALIDITY 0x20000000 /* Block validity checking */
@@ -2564,6 +2565,8 @@ extern int ext4_alloc_flex_bg_array(struct super_block *sb,
ext4_group_t ngroup);
extern const char *ext4_decode_error(struct super_block *sb, int errno,
char nbuf[16]);
+extern struct inode *ext4_alloc_shadow_inode(struct inode *inode);
+extern void ext4_free_shadow_inode(struct inode *shadow);
extern __printf(4, 5)
void __ext4_error(struct super_block *, const char *, unsigned int,
diff --git a/fs/ext4/file.c b/fs/ext4/file.c
index 749b222..60683ab 100644
--- a/fs/ext4/file.c
+++ b/fs/ext4/file.c
@@ -388,7 +388,10 @@ static int ext4_file_open(struct inode * inode, struct file * filp)
ret = ext4_get_encryption_info(inode);
if (ret)
return -EACCES;
- if (ext4_encryption_info(inode) == NULL)
+ if ((ext4_encryption_info(inode) == NULL) &&
+ !(test_opt(inode->i_sb, CIPHERTEXT_ACCESS) &&
+ ((filp->f_flags & O_ACCMODE) == O_RDONLY) &&
+ capable(CAP_SYS_ADMIN)))
return -ENOKEY;
}
/*
diff --git a/fs/ext4/indirect.c b/fs/ext4/indirect.c
index 355ef9c..e685736 100644
--- a/fs/ext4/indirect.c
+++ b/fs/ext4/indirect.c
@@ -649,17 +649,17 @@ ssize_t ext4_ind_direct_IO(struct kiocb *iocb, struct iov_iter *iter,
{
struct file *file = iocb->ki_filp;
struct inode *inode = file->f_mapping->host;
+ struct inode *shadow = inode;
struct ext4_inode_info *ei = EXT4_I(inode);
handle_t *handle;
ssize_t ret;
int orphan = 0;
size_t count = iov_iter_count(iter);
int retries = 0;
+ loff_t final_size = offset + count;
if (iov_iter_rw(iter) == WRITE) {
- loff_t final_size = offset + count;
-
- if (final_size > inode->i_size) {
+ if (final_size > i_size_read(inode)) {
/* Credits for sb + inode write */
handle = ext4_journal_start(inode, EXT4_HT_INODE, 2);
if (IS_ERR(handle)) {
@@ -676,6 +676,18 @@ ssize_t ext4_ind_direct_IO(struct kiocb *iocb, struct iov_iter *iter,
ext4_journal_stop(handle);
}
}
+ if (iov_iter_rw(iter) == READ &&
+ ext4_encrypted_inode(inode) &&
+ is_sync_kiocb(iocb) &&
+ final_size > i_size_read(inode)) {
+ shadow = ext4_alloc_shadow_inode(inode);
+ if (shadow)
+ i_size_write(shadow,
+ round_up(i_size_read(inode),
+ inode->i_sb->s_blocksize));
+ else
+ shadow = inode;
+ }
retry:
if (iov_iter_rw(iter) == READ && ext4_should_dioread_nolock(inode)) {
@@ -695,7 +707,7 @@ retry:
ret = dax_do_io(iocb, inode, iter, offset,
ext4_get_block, NULL, 0);
else
- ret = __blockdev_direct_IO(iocb, inode,
+ ret = __blockdev_direct_IO(iocb, shadow,
inode->i_sb->s_bdev, iter,
offset, ext4_get_block, NULL,
NULL, 0);
@@ -706,7 +718,7 @@ locked:
ret = dax_do_io(iocb, inode, iter, offset,
ext4_get_block, NULL, DIO_LOCKING);
else
- ret = blockdev_direct_IO(iocb, inode, iter, offset,
+ ret = blockdev_direct_IO(iocb, shadow, iter, offset,
ext4_get_block);
if (unlikely(iov_iter_rw(iter) == WRITE && ret < 0)) {
@@ -757,6 +769,8 @@ locked:
ret = err;
}
out:
+ if (shadow != inode)
+ ext4_free_shadow_inode(shadow);
return ret;
}
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index ff2f3cd..16f6537 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -3279,9 +3279,6 @@ static ssize_t ext4_ext_direct_IO(struct kiocb *iocb, struct iov_iter *iter,
get_block_func = ext4_get_block_write;
dio_flags = DIO_LOCKING;
}
-#ifdef CONFIG_EXT4_FS_ENCRYPTION
- BUG_ON(ext4_encrypted_inode(inode) && S_ISREG(inode->i_mode));
-#endif
if (IS_DAX(inode))
ret = dax_do_io(iocb, inode, iter, offset, get_block_func,
ext4_end_io_dio, dio_flags);
@@ -3344,10 +3341,16 @@ static ssize_t ext4_direct_IO(struct kiocb *iocb, struct iov_iter *iter,
size_t count = iov_iter_count(iter);
ssize_t ret;
-#ifdef CONFIG_EXT4_FS_ENCRYPTION
- if (ext4_encrypted_inode(inode) && S_ISREG(inode->i_mode))
- return 0;
-#endif
+ if (ext4_encrypted_inode(inode) && S_ISREG(inode->i_mode)) {
+ if (iov_iter_rw(iter) == WRITE)
+ return 0;
+ if (test_opt(inode->i_sb, CIPHERTEXT_ACCESS) &&
+ capable(CAP_SYS_ADMIN)) {
+ if (iov_iter_rw(iter) == WRITE)
+ return -EPERM;
+ } else
+ return 0;
+ }
/*
* If we are doing data journalling we don't support O_DIRECT
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index 486e869..6173b46 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1033,6 +1033,49 @@ void ext4_clear_inode(struct inode *inode)
#endif
}
+/*
+ * Create a copy of the inode structure so when we are reading the
+ * last block of an encrypted inode using direct I/O to get the
+ * ciphertext, we can futz with the i_size in the shadow inode. This
+ * is necessary so that we can make a copy of the full AES block when
+ * i_size is not a multiple of the AES block size.
+ */
+struct inode *ext4_alloc_shadow_inode(struct inode *inode)
+{
+ struct ext4_inode_info *shadow_ei, *ei = EXT4_I(inode);
+ struct inode *shadow;
+
+ shadow_ei = kmem_cache_alloc(ext4_inode_cachep, GFP_NOFS);
+ if (!shadow_ei)
+ return NULL;
+
+ memcpy(shadow_ei, ei, sizeof(struct ext4_inode_info));
+ shadow = &shadow_ei->vfs_inode;
+
+ init_rwsem(&shadow_ei->xattr_sem);
+ init_rwsem(&shadow_ei->i_data_sem);
+ init_rwsem(&shadow_ei->i_mmap_sem);
+ i_size_ordered_init(shadow);
+ mutex_init(&shadow->i_mutex);
+ spin_lock_init(&shadow_ei->i_raw_lock);
+ spin_lock_init(&shadow_ei->i_prealloc_lock);
+ spin_lock_init(&(shadow_ei->i_block_reservation_lock));
+ spin_lock_init(&shadow_ei->i_completed_io_lock);
+ rwlock_init(&shadow_ei->i_es_lock);
+ ext4_es_init_tree(&shadow_ei->i_es_tree);
+ INIT_LIST_HEAD(&shadow_ei->i_es_list);
+ shadow_ei->i_es_all_nr = 0;
+ shadow_ei->i_es_shk_nr = 0;
+
+ return shadow;
+}
+
+void ext4_free_shadow_inode(struct inode *shadow)
+{
+ ext4_es_remove_extent(shadow, 0, EXT_MAX_BLOCKS);
+ kmem_cache_free(ext4_inode_cachep, EXT4_I(shadow));
+}
+
static struct inode *ext4_nfs_get_inode(struct super_block *sb,
u64 ino, u32 generation)
{
@@ -1182,6 +1225,7 @@ enum {
Opt_journal_path, Opt_journal_checksum, Opt_journal_async_commit,
Opt_abort, Opt_data_journal, Opt_data_ordered, Opt_data_writeback,
Opt_data_err_abort, Opt_data_err_ignore, Opt_test_dummy_encryption,
+ Opt_ciphertext_access, Opt_nociphertext_access,
Opt_usrjquota, Opt_grpjquota, Opt_offusrjquota, Opt_offgrpjquota,
Opt_jqfmt_vfsold, Opt_jqfmt_vfsv0, Opt_jqfmt_vfsv1, Opt_quota,
Opt_noquota, Opt_barrier, Opt_nobarrier, Opt_err,
@@ -1273,6 +1317,8 @@ static const match_table_t tokens = {
{Opt_noinit_itable, "noinit_itable"},
{Opt_max_dir_size_kb, "max_dir_size_kb=%u"},
{Opt_test_dummy_encryption, "test_dummy_encryption"},
+ {Opt_ciphertext_access, "ciphertext_access"},
+ {Opt_nociphertext_access, "nociphertext_access"},
{Opt_removed, "check=none"}, /* mount option from ext2/3 */
{Opt_removed, "nocheck"}, /* mount option from ext2/3 */
{Opt_removed, "reservation"}, /* mount option from ext2/3 */
@@ -1475,6 +1521,8 @@ static const struct mount_opts {
{Opt_jqfmt_vfsv1, QFMT_VFS_V1, MOPT_QFMT},
{Opt_max_dir_size_kb, 0, MOPT_GTE0},
{Opt_test_dummy_encryption, 0, MOPT_GTE0},
+ {Opt_ciphertext_access, EXT4_MOUNT_CIPHERTEXT_ACCESS, MOPT_SET},
+ {Opt_nociphertext_access, EXT4_MOUNT_CIPHERTEXT_ACCESS, MOPT_CLEAR},
{Opt_err, 0, 0}
};
--
2.5.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v4 3/4] ext4 crypto: simplify interfaces to directory entry insert functions
2015-12-16 22:46 [PATCH v4 0/4] ext4 crypto: backup and restore encrypted files Theodore Ts'o
2015-12-16 22:46 ` [PATCH v4 1/4] ext4 crypto: add missing locking for keyring_key access Theodore Ts'o
2015-12-16 22:46 ` [PATCH v4 2/4] ext4 crypto: add ciphertext_access mount option Theodore Ts'o
@ 2015-12-16 22:46 ` Theodore Ts'o
2015-12-16 22:46 ` [PATCH v4 4/4] ext4 crypto: add ioctls to allow backup of encryption metadata Theodore Ts'o
3 siblings, 0 replies; 5+ messages in thread
From: Theodore Ts'o @ 2015-12-16 22:46 UTC (permalink / raw)
To: Ext4 Developers List; +Cc: Theodore Ts'o
A number of functions include ext4_add_dx_entry, make_indexed_dir,
etc. are being passed a dentry even though the only thing they use is
the containing parent. We can shrink the code size slightly by maing
this replacement. This will also be useful in cases where we don't
have a dentry as the argument to the directory entry insert functions.
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
---
fs/ext4/ext4.h | 3 +--
fs/ext4/inline.c | 10 ++++------
fs/ext4/namei.c | 15 ++++++---------
3 files changed, 11 insertions(+), 17 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index cf7a885..9fdbd06 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -3007,8 +3007,7 @@ extern int ext4_da_write_inline_data_end(struct inode *inode, loff_t pos,
struct page *page);
extern int ext4_try_add_inline_entry(handle_t *handle,
struct ext4_filename *fname,
- struct dentry *dentry,
- struct inode *inode);
+ struct inode *dir, struct inode *inode);
extern int ext4_try_create_inline_dir(handle_t *handle,
struct inode *parent,
struct inode *inode);
diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c
index d884989..dfe3b9b 100644
--- a/fs/ext4/inline.c
+++ b/fs/ext4/inline.c
@@ -995,12 +995,11 @@ void ext4_show_inline_dir(struct inode *dir, struct buffer_head *bh,
*/
static int ext4_add_dirent_to_inline(handle_t *handle,
struct ext4_filename *fname,
- struct dentry *dentry,
+ struct inode *dir,
struct inode *inode,
struct ext4_iloc *iloc,
void *inline_start, int inline_size)
{
- struct inode *dir = d_inode(dentry->d_parent);
int err;
struct ext4_dir_entry_2 *de;
@@ -1245,12 +1244,11 @@ out:
* the new created block.
*/
int ext4_try_add_inline_entry(handle_t *handle, struct ext4_filename *fname,
- struct dentry *dentry, struct inode *inode)
+ struct inode *dir, struct inode *inode)
{
int ret, inline_size;
void *inline_start;
struct ext4_iloc iloc;
- struct inode *dir = d_inode(dentry->d_parent);
ret = ext4_get_inode_loc(dir, &iloc);
if (ret)
@@ -1264,7 +1262,7 @@ int ext4_try_add_inline_entry(handle_t *handle, struct ext4_filename *fname,
EXT4_INLINE_DOTDOT_SIZE;
inline_size = EXT4_MIN_INLINE_DATA_SIZE - EXT4_INLINE_DOTDOT_SIZE;
- ret = ext4_add_dirent_to_inline(handle, fname, dentry, inode, &iloc,
+ ret = ext4_add_dirent_to_inline(handle, fname, dir, inode, &iloc,
inline_start, inline_size);
if (ret != -ENOSPC)
goto out;
@@ -1285,7 +1283,7 @@ int ext4_try_add_inline_entry(handle_t *handle, struct ext4_filename *fname,
if (inline_size) {
inline_start = ext4_get_inline_xattr_pos(dir, &iloc);
- ret = ext4_add_dirent_to_inline(handle, fname, dentry,
+ ret = ext4_add_dirent_to_inline(handle, fname, dir,
inode, &iloc, inline_start,
inline_size);
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index a969ab3..06c3afc 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -273,7 +273,7 @@ static struct buffer_head * ext4_dx_find_entry(struct inode *dir,
struct ext4_filename *fname,
struct ext4_dir_entry_2 **res_dir);
static int ext4_dx_add_entry(handle_t *handle, struct ext4_filename *fname,
- struct dentry *dentry, struct inode *inode);
+ struct inode *dir, struct inode *inode);
/* checksumming functions */
void initialize_dirent_tail(struct ext4_dir_entry_tail *t,
@@ -1928,10 +1928,9 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
* directory, and adds the dentry to the indexed directory.
*/
static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
- struct dentry *dentry,
+ struct inode *dir,
struct inode *inode, struct buffer_head *bh)
{
- struct inode *dir = d_inode(dentry->d_parent);
struct buffer_head *bh2;
struct dx_root *root;
struct dx_frame frames[2], *frame;
@@ -2086,8 +2085,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
return retval;
if (ext4_has_inline_data(dir)) {
- retval = ext4_try_add_inline_entry(handle, &fname,
- dentry, inode);
+ retval = ext4_try_add_inline_entry(handle, &fname, dir, inode);
if (retval < 0)
goto out;
if (retval == 1) {
@@ -2097,7 +2095,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
}
if (is_dx(dir)) {
- retval = ext4_dx_add_entry(handle, &fname, dentry, 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);
@@ -2119,7 +2117,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
if (blocks == 1 && !dx_fallback &&
ext4_has_feature_dir_index(sb)) {
- retval = make_indexed_dir(handle, &fname, dentry,
+ retval = make_indexed_dir(handle, &fname, dir,
inode, bh);
bh = NULL; /* make_indexed_dir releases bh */
goto out;
@@ -2154,12 +2152,11 @@ out:
* Returns 0 for success, or a negative error value
*/
static int ext4_dx_add_entry(handle_t *handle, struct ext4_filename *fname,
- struct dentry *dentry, struct inode *inode)
+ struct inode *dir, struct inode *inode)
{
struct dx_frame frames[2], *frame;
struct dx_entry *entries, *at;
struct buffer_head *bh;
- struct inode *dir = d_inode(dentry->d_parent);
struct super_block *sb = dir->i_sb;
struct ext4_dir_entry_2 *de;
int err;
--
2.5.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v4 4/4] ext4 crypto: add ioctls to allow backup of encryption metadata
2015-12-16 22:46 [PATCH v4 0/4] ext4 crypto: backup and restore encrypted files Theodore Ts'o
` (2 preceding siblings ...)
2015-12-16 22:46 ` [PATCH v4 3/4] ext4 crypto: simplify interfaces to directory entry insert functions Theodore Ts'o
@ 2015-12-16 22:46 ` Theodore Ts'o
3 siblings, 0 replies; 5+ messages in thread
From: Theodore Ts'o @ 2015-12-16 22:46 UTC (permalink / raw)
To: Ext4 Developers List; +Cc: Theodore Ts'o
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@mit.edu>
---
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
^ permalink raw reply related [flat|nested] 5+ messages in thread