Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

ceph: add support to readdir for encrypted names

To make it simpler to decrypt names in a readdir reply (i.e. before
we have a dentry), add a new ceph_encode_encrypted_fname()-like helper
that takes a qstr pointer instead of a dentry pointer.

Once we've decrypted the names in a readdir reply, we no longer need the
crypttext, so overwrite them in ceph_mds_reply_dir_entry with the
unencrypted names. Then in both ceph_readdir_prepopulate() and
ceph_readdir() we will use the dencrypted name directly.

[ jlayton: convert some BUG_ONs into error returns ]

Signed-off-by: Xiubo Li <xiubli@redhat.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
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>

authored by

Xiubo Li and committed by
Ilya Dryomov
af9ffa6d 3859af9e

+144 -26
+20 -7
fs/ceph/crypto.c
··· 192 192 swap(req->r_fscrypt_auth, as->fscrypt_auth); 193 193 } 194 194 195 - int ceph_encode_encrypted_fname(const struct inode *parent, 196 - struct dentry *dentry, char *buf) 195 + int ceph_encode_encrypted_dname(const struct inode *parent, 196 + struct qstr *d_name, char *buf) 197 197 { 198 198 u32 len; 199 199 int elen; 200 200 int ret; 201 201 u8 *cryptbuf; 202 202 203 - WARN_ON_ONCE(!fscrypt_has_encryption_key(parent)); 203 + if (!fscrypt_has_encryption_key(parent)) { 204 + memcpy(buf, d_name->name, d_name->len); 205 + return d_name->len; 206 + } 204 207 205 208 /* 206 209 * Convert cleartext d_name to ciphertext. If result is longer than ··· 211 208 * 212 209 * See: fscrypt_setup_filename 213 210 */ 214 - if (!fscrypt_fname_encrypted_size(parent, dentry->d_name.len, NAME_MAX, 215 - &len)) 211 + if (!fscrypt_fname_encrypted_size(parent, d_name->len, NAME_MAX, &len)) 216 212 return -ENAMETOOLONG; 217 213 218 214 /* Allocate a buffer appropriate to hold the result */ ··· 220 218 if (!cryptbuf) 221 219 return -ENOMEM; 222 220 223 - ret = fscrypt_fname_encrypt(parent, &dentry->d_name, cryptbuf, len); 221 + ret = fscrypt_fname_encrypt(parent, d_name, cryptbuf, len); 224 222 if (ret) { 225 223 kfree(cryptbuf); 226 224 return ret; ··· 245 243 kfree(cryptbuf); 246 244 dout("base64-encoded ciphertext name = %.*s\n", elen, buf); 247 245 return elen; 246 + } 247 + 248 + int ceph_encode_encrypted_fname(const struct inode *parent, 249 + struct dentry *dentry, char *buf) 250 + { 251 + WARN_ON_ONCE(!fscrypt_has_encryption_key(parent)); 252 + 253 + return ceph_encode_encrypted_dname(parent, &dentry->d_name, buf); 248 254 } 249 255 250 256 /** ··· 296 286 * generating a nokey name via fscrypt. 297 287 */ 298 288 if (!fscrypt_has_encryption_key(fname->dir)) { 299 - memcpy(oname->name, fname->name, fname->name_len); 289 + if (fname->no_copy) 290 + oname->name = fname->name; 291 + else 292 + memcpy(oname->name, fname->name, fname->name_len); 300 293 oname->len = fname->name_len; 301 294 if (is_nokey) 302 295 *is_nokey = true;
+10
fs/ceph/crypto.h
··· 19 19 unsigned char *ctext; // binary crypttext (if any) 20 20 u32 name_len; // length of name buffer 21 21 u32 ctext_len; // length of crypttext 22 + bool no_copy; 22 23 }; 23 24 24 25 struct ceph_fscrypt_auth { ··· 77 76 struct ceph_acl_sec_ctx *as); 78 77 void ceph_fscrypt_as_ctx_to_req(struct ceph_mds_request *req, 79 78 struct ceph_acl_sec_ctx *as); 79 + int ceph_encode_encrypted_dname(const struct inode *parent, 80 + struct qstr *d_name, char *buf); 80 81 int ceph_encode_encrypted_fname(const struct inode *parent, 81 82 struct dentry *dentry, char *buf); 82 83 ··· 122 119 static inline void ceph_fscrypt_as_ctx_to_req(struct ceph_mds_request *req, 123 120 struct ceph_acl_sec_ctx *as_ctx) 124 121 { 122 + } 123 + 124 + static inline int ceph_encode_encrypted_dname(const struct inode *parent, 125 + struct qstr *d_name, char *buf) 126 + { 127 + memcpy(buf, d_name->name, d_name->len); 128 + return d_name->len; 125 129 } 126 130 127 131 static inline int ceph_encode_encrypted_fname(const struct inode *parent,
+30 -6
fs/ceph/dir.c
··· 9 9 10 10 #include "super.h" 11 11 #include "mds_client.h" 12 + #include "crypto.h" 12 13 13 14 /* 14 15 * Directory operations: readdir, lookup, create, link, unlink, ··· 242 241 di = ceph_dentry(dentry); 243 242 if (d_unhashed(dentry) || 244 243 d_really_is_negative(dentry) || 245 - di->lease_shared_gen != shared_gen) { 244 + di->lease_shared_gen != shared_gen || 245 + ((dentry->d_flags & DCACHE_NOKEY_NAME) && 246 + fscrypt_has_encryption_key(dir))) { 246 247 spin_unlock(&dentry->d_lock); 247 248 dput(dentry); 248 249 err = -EAGAIN; ··· 343 340 ctx->pos = 2; 344 341 } 345 342 343 + err = fscrypt_prepare_readdir(inode); 344 + if (err) 345 + return err; 346 + 346 347 spin_lock(&ci->i_ceph_lock); 347 348 /* request Fx cap. if have Fx, we don't need to release Fs cap 348 349 * for later create/unlink. */ ··· 396 389 req = ceph_mdsc_create_request(mdsc, op, USE_AUTH_MDS); 397 390 if (IS_ERR(req)) 398 391 return PTR_ERR(req); 392 + 399 393 err = ceph_alloc_readdir_reply_buffer(req, inode); 400 394 if (err) { 401 395 ceph_mdsc_put_request(req); ··· 410 402 req->r_inode_drop = CEPH_CAP_FILE_EXCL; 411 403 } 412 404 if (dfi->last_name) { 413 - req->r_path2 = kstrdup(dfi->last_name, GFP_KERNEL); 405 + struct qstr d_name = { .name = dfi->last_name, 406 + .len = strlen(dfi->last_name) }; 407 + 408 + req->r_path2 = kzalloc(NAME_MAX + 1, GFP_KERNEL); 414 409 if (!req->r_path2) { 415 410 ceph_mdsc_put_request(req); 416 411 return -ENOMEM; 412 + } 413 + 414 + err = ceph_encode_encrypted_dname(inode, &d_name, 415 + req->r_path2); 416 + if (err < 0) { 417 + ceph_mdsc_put_request(req); 418 + return err; 417 419 } 418 420 } else if (is_hash_order(ctx->pos)) { 419 421 req->r_args.readdir.offset_hash = ··· 529 511 for (; i < rinfo->dir_nr; i++) { 530 512 struct ceph_mds_reply_dir_entry *rde = rinfo->dir_entries + i; 531 513 532 - BUG_ON(rde->offset < ctx->pos); 514 + if (rde->offset < ctx->pos) { 515 + pr_warn("%s: rde->offset 0x%llx ctx->pos 0x%llx\n", 516 + __func__, rde->offset, ctx->pos); 517 + return -EIO; 518 + } 519 + 520 + if (WARN_ON_ONCE(!rde->inode.in)) 521 + return -EIO; 533 522 534 523 ctx->pos = rde->offset; 535 524 dout("readdir (%d/%d) -> %llx '%.*s' %p\n", 536 525 i, rinfo->dir_nr, ctx->pos, 537 526 rde->name_len, rde->name, &rde->inode.in); 538 - 539 - BUG_ON(!rde->inode.in); 540 527 541 528 if (!dir_emit(ctx, rde->name, rde->name_len, 542 529 ceph_present_ino(inode->i_sb, le64_to_cpu(rde->inode.in->ino)), ··· 555 532 dout("filldir stopping us...\n"); 556 533 return 0; 557 534 } 535 + 536 + /* Reset the lengths to their original allocated vals */ 558 537 ctx->pos++; 559 538 } 560 539 ··· 611 586 dfi->dir_ordered_count); 612 587 spin_unlock(&ci->i_ceph_lock); 613 588 } 614 - 615 589 dout("readdir %p file %p done.\n", inode, file); 616 590 return 0; 617 591 }
+8 -4
fs/ceph/inode.c
··· 1752 1752 struct ceph_mds_session *session) 1753 1753 { 1754 1754 struct dentry *parent = req->r_dentry; 1755 - struct ceph_inode_info *ci = ceph_inode(d_inode(parent)); 1755 + struct inode *inode = d_inode(parent); 1756 + struct ceph_inode_info *ci = ceph_inode(inode); 1756 1757 struct ceph_mds_reply_info_parsed *rinfo = &req->r_reply_info; 1757 1758 struct qstr dname; 1758 1759 struct dentry *dn; ··· 1827 1826 tvino.snap = le64_to_cpu(rde->inode.in->snapid); 1828 1827 1829 1828 if (rinfo->hash_order) { 1830 - u32 hash = ceph_str_hash(ci->i_dir_layout.dl_dir_hash, 1831 - rde->name, rde->name_len); 1832 - hash = ceph_frag_value(hash); 1829 + u32 hash = ceph_frag_value(rde->raw_hash); 1833 1830 if (hash != last_hash) 1834 1831 fpos_offset = 2; 1835 1832 last_hash = hash; ··· 1849 1850 dout("d_alloc badness\n"); 1850 1851 err = -ENOMEM; 1851 1852 goto out; 1853 + } 1854 + if (rde->is_nokey) { 1855 + spin_lock(&dn->d_lock); 1856 + dn->d_flags |= DCACHE_NOKEY_NAME; 1857 + spin_unlock(&dn->d_lock); 1852 1858 } 1853 1859 } else if (d_really_is_positive(dn) && 1854 1860 (ceph_ino(d_inode(dn)) != tvino.ino ||
+74 -7
fs/ceph/mds_client.c
··· 440 440 441 441 info->dir_nr = num; 442 442 while (num) { 443 + struct inode *inode = d_inode(req->r_dentry); 444 + struct ceph_inode_info *ci = ceph_inode(inode); 443 445 struct ceph_mds_reply_dir_entry *rde = info->dir_entries + i; 446 + struct fscrypt_str tname = FSTR_INIT(NULL, 0); 447 + struct fscrypt_str oname = FSTR_INIT(NULL, 0); 448 + struct ceph_fname fname; 449 + u32 altname_len, _name_len; 450 + u8 *altname, *_name; 451 + 444 452 /* dentry */ 445 - ceph_decode_32_safe(p, end, rde->name_len, bad); 446 - ceph_decode_need(p, end, rde->name_len, bad); 447 - rde->name = *p; 448 - *p += rde->name_len; 449 - dout("parsed dir dname '%.*s'\n", rde->name_len, rde->name); 453 + ceph_decode_32_safe(p, end, _name_len, bad); 454 + ceph_decode_need(p, end, _name_len, bad); 455 + _name = *p; 456 + *p += _name_len; 457 + dout("parsed dir dname '%.*s'\n", _name_len, _name); 458 + 459 + if (info->hash_order) 460 + rde->raw_hash = ceph_str_hash(ci->i_dir_layout.dl_dir_hash, 461 + _name, _name_len); 450 462 451 463 /* dentry lease */ 452 464 err = parse_reply_info_lease(p, end, &rde->lease, features, 453 - &rde->altname_len, &rde->altname); 465 + &altname_len, &altname); 454 466 if (err) 455 467 goto out_bad; 468 + 469 + /* 470 + * Try to dencrypt the dentry names and update them 471 + * in the ceph_mds_reply_dir_entry struct. 472 + */ 473 + fname.dir = inode; 474 + fname.name = _name; 475 + fname.name_len = _name_len; 476 + fname.ctext = altname; 477 + fname.ctext_len = altname_len; 478 + /* 479 + * The _name_len maybe larger than altname_len, such as 480 + * when the human readable name length is in range of 481 + * (CEPH_NOHASH_NAME_MAX, CEPH_NOHASH_NAME_MAX + SHA256_DIGEST_SIZE), 482 + * then the copy in ceph_fname_to_usr will corrupt the 483 + * data if there has no encryption key. 484 + * 485 + * Just set the no_copy flag and then if there has no 486 + * encryption key the oname.name will be assigned to 487 + * _name always. 488 + */ 489 + fname.no_copy = true; 490 + if (altname_len == 0) { 491 + /* 492 + * Set tname to _name, and this will be used 493 + * to do the base64_decode in-place. It's 494 + * safe because the decoded string should 495 + * always be shorter, which is 3/4 of origin 496 + * string. 497 + */ 498 + tname.name = _name; 499 + 500 + /* 501 + * Set oname to _name too, and this will be 502 + * used to do the dencryption in-place. 503 + */ 504 + oname.name = _name; 505 + oname.len = _name_len; 506 + } else { 507 + /* 508 + * This will do the decryption only in-place 509 + * from altname cryptext directly. 510 + */ 511 + oname.name = altname; 512 + oname.len = altname_len; 513 + } 514 + rde->is_nokey = false; 515 + err = ceph_fname_to_usr(&fname, &tname, &oname, &rde->is_nokey); 516 + if (err) { 517 + pr_err("%s unable to decode %.*s, got %d\n", __func__, 518 + _name_len, _name, err); 519 + goto out_bad; 520 + } 521 + rde->name = oname.name; 522 + rde->name_len = oname.len; 456 523 457 524 /* inode */ 458 525 err = parse_reply_info_in(p, end, &rde->inode, features); ··· 3738 3671 if (err == 0) { 3739 3672 if (result == 0 && (req->r_op == CEPH_MDS_OP_READDIR || 3740 3673 req->r_op == CEPH_MDS_OP_LSSNAP)) 3741 - ceph_readdir_prepopulate(req, req->r_session); 3674 + err = ceph_readdir_prepopulate(req, req->r_session); 3742 3675 } 3743 3676 current->journal_info = NULL; 3744 3677 mutex_unlock(&req->r_fill_mutex);
+2 -2
fs/ceph/mds_client.h
··· 96 96 }; 97 97 98 98 struct ceph_mds_reply_dir_entry { 99 + bool is_nokey; 99 100 char *name; 100 - u8 *altname; 101 101 u32 name_len; 102 - u32 altname_len; 102 + u32 raw_hash; 103 103 struct ceph_mds_reply_lease *lease; 104 104 struct ceph_mds_reply_info_in inode; 105 105 loff_t offset;