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:
Jeff Layton 2020-09-03 13:31:10 -04:00 committed by Ilya Dryomov
parent af9ffa6df7
commit 79f2f6ad87
2 changed files with 150 additions and 17 deletions

View File

@ -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;

View File

@ -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)
{