Commit 79f2f6ad authored by Jeff Layton's avatar Jeff Layton Committed by Ilya Dryomov
Browse files

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: default avatarJeff Layton <jlayton@kernel.org>
Reviewed-by: default avatarXiubo Li <xiubli@redhat.com>
Reviewed-and-tested-by: default avatarLuís Henriques <lhenriques@suse.de>
Reviewed-by: default avatarMilind Changire <mchangir@redhat.com>
Signed-off-by: default avatarIlya Dryomov <idryomov@gmail.com>
parent af9ffa6d
Loading
Loading
Loading
Loading
+49 −5
Original line number Diff line number Diff line
@@ -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,13 +1020,20 @@ static int ceph_symlink(struct mnt_idmap *idmap, struct inode *dir,
		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;
		}
	req->r_parent = dir;
	ihold(dir);
	}

	set_bit(CEPH_MDS_R_PARENT_LOCKED, &req->r_req_flags);
	req->r_dentry = dget(dentry);
+101 −12
Original line number Diff line number Diff line
@@ -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,17 +1101,32 @@ 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 (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),
					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);
@@ -1091,6 +1136,7 @@ int ceph_fill_inode(struct inode *inode, struct page *locked_page,
				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 */
		}

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