ceph: create symlinks with encrypted and base64-encoded targets
When creating symlinks in encrypted directories, encrypt and base64-encode the target with the new inode's key before sending to the MDS. When filling a symlinked inode, base64-decode it into a buffer that we'll keep in ci->i_symlink. When get_link is called, decrypt the buffer into a new one that will hang off i_link. Signed-off-by: Jeff Layton <jlayton@kernel.org> Reviewed-by: Xiubo Li <xiubli@redhat.com> Reviewed-and-tested-by: Luís Henriques <lhenriques@suse.de> Reviewed-by: Milind Changire <mchangir@redhat.com> Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
This commit is contained in:
parent
af9ffa6df7
commit
79f2f6ad87
|
@ -948,6 +948,43 @@ static int ceph_create(struct mnt_idmap *idmap, struct inode *dir,
|
|||
return ceph_mknod(idmap, dir, dentry, mode, 0);
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_FS_ENCRYPTION)
|
||||
static int prep_encrypted_symlink_target(struct ceph_mds_request *req,
|
||||
const char *dest)
|
||||
{
|
||||
int err;
|
||||
int len = strlen(dest);
|
||||
struct fscrypt_str osd_link = FSTR_INIT(NULL, 0);
|
||||
|
||||
err = fscrypt_prepare_symlink(req->r_parent, dest, len, PATH_MAX,
|
||||
&osd_link);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = fscrypt_encrypt_symlink(req->r_new_inode, dest, len, &osd_link);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
req->r_path2 = kmalloc(CEPH_BASE64_CHARS(osd_link.len) + 1, GFP_KERNEL);
|
||||
if (!req->r_path2) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
len = ceph_base64_encode(osd_link.name, osd_link.len, req->r_path2);
|
||||
req->r_path2[len] = '\0';
|
||||
out:
|
||||
fscrypt_fname_free_buffer(&osd_link);
|
||||
return err;
|
||||
}
|
||||
#else
|
||||
static int prep_encrypted_symlink_target(struct ceph_mds_request *req,
|
||||
const char *dest)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int ceph_symlink(struct mnt_idmap *idmap, struct inode *dir,
|
||||
struct dentry *dentry, const char *dest)
|
||||
{
|
||||
|
@ -983,14 +1020,21 @@ static int ceph_symlink(struct mnt_idmap *idmap, struct inode *dir,
|
|||
goto out_req;
|
||||
}
|
||||
|
||||
req->r_path2 = kstrdup(dest, GFP_KERNEL);
|
||||
if (!req->r_path2) {
|
||||
err = -ENOMEM;
|
||||
goto out_req;
|
||||
}
|
||||
req->r_parent = dir;
|
||||
ihold(dir);
|
||||
|
||||
if (IS_ENCRYPTED(req->r_new_inode)) {
|
||||
err = prep_encrypted_symlink_target(req, dest);
|
||||
if (err)
|
||||
goto out_req;
|
||||
} else {
|
||||
req->r_path2 = kstrdup(dest, GFP_KERNEL);
|
||||
if (!req->r_path2) {
|
||||
err = -ENOMEM;
|
||||
goto out_req;
|
||||
}
|
||||
}
|
||||
|
||||
set_bit(CEPH_MDS_R_PARENT_LOCKED, &req->r_req_flags);
|
||||
req->r_dentry = dget(dentry);
|
||||
req->r_num_caps = 2;
|
||||
|
|
113
fs/ceph/inode.c
113
fs/ceph/inode.c
|
@ -35,6 +35,7 @@
|
|||
*/
|
||||
|
||||
static const struct inode_operations ceph_symlink_iops;
|
||||
static const struct inode_operations ceph_encrypted_symlink_iops;
|
||||
|
||||
static void ceph_inode_work(struct work_struct *work);
|
||||
|
||||
|
@ -640,6 +641,7 @@ void ceph_free_inode(struct inode *inode)
|
|||
#ifdef CONFIG_FS_ENCRYPTION
|
||||
kfree(ci->fscrypt_auth);
|
||||
#endif
|
||||
fscrypt_free_inode(inode);
|
||||
kmem_cache_free(ceph_inode_cachep, ci);
|
||||
}
|
||||
|
||||
|
@ -837,6 +839,34 @@ void ceph_fill_file_time(struct inode *inode, int issued,
|
|||
inode, time_warp_seq, ci->i_time_warp_seq);
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_FS_ENCRYPTION)
|
||||
static int decode_encrypted_symlink(const char *encsym, int enclen, u8 **decsym)
|
||||
{
|
||||
int declen;
|
||||
u8 *sym;
|
||||
|
||||
sym = kmalloc(enclen + 1, GFP_NOFS);
|
||||
if (!sym)
|
||||
return -ENOMEM;
|
||||
|
||||
declen = ceph_base64_decode(encsym, enclen, sym);
|
||||
if (declen < 0) {
|
||||
pr_err("%s: can't decode symlink (%d). Content: %.*s\n",
|
||||
__func__, declen, enclen, encsym);
|
||||
kfree(sym);
|
||||
return -EIO;
|
||||
}
|
||||
sym[declen + 1] = '\0';
|
||||
*decsym = sym;
|
||||
return declen;
|
||||
}
|
||||
#else
|
||||
static int decode_encrypted_symlink(const char *encsym, int symlen, u8 **decsym)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Populate an inode based on info from mds. May be called on new or
|
||||
* existing inodes.
|
||||
|
@ -1071,26 +1101,42 @@ int ceph_fill_inode(struct inode *inode, struct page *locked_page,
|
|||
inode->i_fop = &ceph_file_fops;
|
||||
break;
|
||||
case S_IFLNK:
|
||||
inode->i_op = &ceph_symlink_iops;
|
||||
if (!ci->i_symlink) {
|
||||
u32 symlen = iinfo->symlink_len;
|
||||
char *sym;
|
||||
|
||||
spin_unlock(&ci->i_ceph_lock);
|
||||
|
||||
if (symlen != i_size_read(inode)) {
|
||||
pr_err("%s %llx.%llx BAD symlink "
|
||||
"size %lld\n", __func__,
|
||||
ceph_vinop(inode),
|
||||
i_size_read(inode));
|
||||
if (IS_ENCRYPTED(inode)) {
|
||||
if (symlen != i_size_read(inode))
|
||||
pr_err("%s %llx.%llx BAD symlink size %lld\n",
|
||||
__func__, ceph_vinop(inode),
|
||||
i_size_read(inode));
|
||||
|
||||
err = decode_encrypted_symlink(iinfo->symlink,
|
||||
symlen, (u8 **)&sym);
|
||||
if (err < 0) {
|
||||
pr_err("%s decoding encrypted symlink failed: %d\n",
|
||||
__func__, err);
|
||||
goto out;
|
||||
}
|
||||
symlen = err;
|
||||
i_size_write(inode, symlen);
|
||||
inode->i_blocks = calc_inode_blocks(symlen);
|
||||
}
|
||||
} else {
|
||||
if (symlen != i_size_read(inode)) {
|
||||
pr_err("%s %llx.%llx BAD symlink size %lld\n",
|
||||
__func__, ceph_vinop(inode),
|
||||
i_size_read(inode));
|
||||
i_size_write(inode, symlen);
|
||||
inode->i_blocks = calc_inode_blocks(symlen);
|
||||
}
|
||||
|
||||
err = -ENOMEM;
|
||||
sym = kstrndup(iinfo->symlink, symlen, GFP_NOFS);
|
||||
if (!sym)
|
||||
goto out;
|
||||
err = -ENOMEM;
|
||||
sym = kstrndup(iinfo->symlink, symlen, GFP_NOFS);
|
||||
if (!sym)
|
||||
goto out;
|
||||
}
|
||||
|
||||
spin_lock(&ci->i_ceph_lock);
|
||||
if (!ci->i_symlink)
|
||||
|
@ -1098,7 +1144,17 @@ int ceph_fill_inode(struct inode *inode, struct page *locked_page,
|
|||
else
|
||||
kfree(sym); /* lost a race */
|
||||
}
|
||||
inode->i_link = ci->i_symlink;
|
||||
|
||||
if (IS_ENCRYPTED(inode)) {
|
||||
/*
|
||||
* Encrypted symlinks need to be decrypted before we can
|
||||
* cache their targets in i_link. Don't touch it here.
|
||||
*/
|
||||
inode->i_op = &ceph_encrypted_symlink_iops;
|
||||
} else {
|
||||
inode->i_link = ci->i_symlink;
|
||||
inode->i_op = &ceph_symlink_iops;
|
||||
}
|
||||
break;
|
||||
case S_IFDIR:
|
||||
inode->i_op = &ceph_dir_iops;
|
||||
|
@ -2126,6 +2182,32 @@ static void ceph_inode_work(struct work_struct *work)
|
|||
iput(inode);
|
||||
}
|
||||
|
||||
static const char *ceph_encrypted_get_link(struct dentry *dentry,
|
||||
struct inode *inode,
|
||||
struct delayed_call *done)
|
||||
{
|
||||
struct ceph_inode_info *ci = ceph_inode(inode);
|
||||
|
||||
if (!dentry)
|
||||
return ERR_PTR(-ECHILD);
|
||||
|
||||
return fscrypt_get_symlink(inode, ci->i_symlink, i_size_read(inode),
|
||||
done);
|
||||
}
|
||||
|
||||
static int ceph_encrypted_symlink_getattr(struct mnt_idmap *idmap,
|
||||
const struct path *path,
|
||||
struct kstat *stat, u32 request_mask,
|
||||
unsigned int query_flags)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = ceph_getattr(idmap, path, stat, request_mask, query_flags);
|
||||
if (ret)
|
||||
return ret;
|
||||
return fscrypt_symlink_getattr(path, stat);
|
||||
}
|
||||
|
||||
/*
|
||||
* symlinks
|
||||
*/
|
||||
|
@ -2136,6 +2218,13 @@ static const struct inode_operations ceph_symlink_iops = {
|
|||
.listxattr = ceph_listxattr,
|
||||
};
|
||||
|
||||
static const struct inode_operations ceph_encrypted_symlink_iops = {
|
||||
.get_link = ceph_encrypted_get_link,
|
||||
.setattr = ceph_setattr,
|
||||
.getattr = ceph_encrypted_symlink_getattr,
|
||||
.listxattr = ceph_listxattr,
|
||||
};
|
||||
|
||||
int __ceph_setattr(struct inode *inode, struct iattr *attr,
|
||||
struct ceph_iattr *cia)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue