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

ceph: fix NULL pointer dereference in ceph_mds_auth_match()

The CephFS kernel client has regression starting from 6.18-rc1.
We have issue in ceph_mds_auth_match() if fs_name == NULL:

const char fs_name = mdsc->fsc->mount_options->mds_namespace;
...
if (auth->match.fs_name && strcmp(auth->match.fs_name, fs_name)) {
/ fsname mismatch, try next one */
return 0;
}

Patrick Donnelly suggested that: In summary, we should definitely start
decoding `fs_name` from the MDSMap and do strict authorizations checks
against it. Note that the `-o mds_namespace=foo` should only be used for
selecting the file system to mount and nothing else. It's possible
no mds_namespace is specified but the kernel will mount the only
file system that exists which may have name "foo".

This patch reworks ceph_mdsmap_decode() and namespace_equals() with
the goal of supporting the suggested concept. Now struct ceph_mdsmap
contains m_fs_name field that receives copy of extracted FS name
by ceph_extract_encoded_string(). For the case of "old" CephFS file
systems, it is used "cephfs" name.

[ idryomov: replace redundant %*pE with %s in ceph_mdsmap_decode(),
get rid of a series of strlen() calls in ceph_namespace_match(),
drop changes to namespace_equals() body to avoid treating empty
mds_namespace as equal, drop changes to ceph_mdsc_handle_fsmap()
as namespace_equals() isn't an equivalent substitution there ]

Cc: stable@vger.kernel.org
Fixes: 22c73d52a6d0 ("ceph: fix multifs mds auth caps issue")
Link: https://tracker.ceph.com/issues/73886
Signed-off-by: Viacheslav Dubeyko <Slava.Dubeyko@ibm.com>
Reviewed-by: Patrick Donnelly <pdonnell@ibm.com>
Tested-by: Patrick Donnelly <pdonnell@ibm.com>
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>

authored by

Viacheslav Dubeyko and committed by
Ilya Dryomov
7987cce3 bc8dedae

+43 -11
+3 -2
fs/ceph/mds_client.c
··· 5671 5671 u32 caller_uid = from_kuid(&init_user_ns, cred->fsuid); 5672 5672 u32 caller_gid = from_kgid(&init_user_ns, cred->fsgid); 5673 5673 struct ceph_client *cl = mdsc->fsc->client; 5674 - const char *fs_name = mdsc->fsc->mount_options->mds_namespace; 5674 + const char *fs_name = mdsc->mdsmap->m_fs_name; 5675 5675 const char *spath = mdsc->fsc->mount_options->server_path; 5676 5676 bool gid_matched = false; 5677 5677 u32 gid, tlen, len; ··· 5679 5679 5680 5680 doutc(cl, "fsname check fs_name=%s match.fs_name=%s\n", 5681 5681 fs_name, auth->match.fs_name ? auth->match.fs_name : ""); 5682 - if (auth->match.fs_name && strcmp(auth->match.fs_name, fs_name)) { 5682 + 5683 + if (!ceph_namespace_match(auth->match.fs_name, fs_name)) { 5683 5684 /* fsname mismatch, try next one */ 5684 5685 return 0; 5685 5686 }
+19 -7
fs/ceph/mdsmap.c
··· 353 353 __decode_and_drop_type(p, end, u8, bad_ext); 354 354 } 355 355 if (mdsmap_ev >= 8) { 356 - u32 fsname_len; 356 + size_t fsname_len; 357 + 357 358 /* enabled */ 358 359 ceph_decode_8_safe(p, end, m->m_enabled, bad_ext); 360 + 359 361 /* fs_name */ 360 - ceph_decode_32_safe(p, end, fsname_len, bad_ext); 362 + m->m_fs_name = ceph_extract_encoded_string(p, end, 363 + &fsname_len, 364 + GFP_NOFS); 365 + if (IS_ERR(m->m_fs_name)) { 366 + m->m_fs_name = NULL; 367 + goto nomem; 368 + } 361 369 362 370 /* validate fsname against mds_namespace */ 363 - if (!namespace_equals(mdsc->fsc->mount_options, *p, 371 + if (!namespace_equals(mdsc->fsc->mount_options, m->m_fs_name, 364 372 fsname_len)) { 365 - pr_warn_client(cl, "fsname %*pE doesn't match mds_namespace %s\n", 366 - (int)fsname_len, (char *)*p, 373 + pr_warn_client(cl, "fsname %s doesn't match mds_namespace %s\n", 374 + m->m_fs_name, 367 375 mdsc->fsc->mount_options->mds_namespace); 368 376 goto bad; 369 377 } 370 - /* skip fsname after validation */ 371 - ceph_decode_skip_n(p, end, fsname_len, bad); 378 + } else { 379 + m->m_enabled = false; 380 + m->m_fs_name = kstrdup(CEPH_OLD_FS_NAME, GFP_NOFS); 381 + if (!m->m_fs_name) 382 + goto nomem; 372 383 } 373 384 /* damaged */ 374 385 if (mdsmap_ev >= 9) { ··· 441 430 kfree(m->m_info); 442 431 } 443 432 kfree(m->m_data_pg_pools); 433 + kfree(m->m_fs_name); 444 434 kfree(m); 445 435 } 446 436
+1
fs/ceph/mdsmap.h
··· 45 45 bool m_enabled; 46 46 bool m_damaged; 47 47 int m_num_laggy; 48 + char *m_fs_name; 48 49 }; 49 50 50 51 static inline struct ceph_entity_addr *
+14 -2
fs/ceph/super.h
··· 104 104 struct fscrypt_dummy_policy dummy_enc_policy; 105 105 }; 106 106 107 + #define CEPH_NAMESPACE_WILDCARD "*" 108 + 109 + static inline bool ceph_namespace_match(const char *pattern, 110 + const char *target) 111 + { 112 + if (!pattern || !pattern[0] || 113 + !strcmp(pattern, CEPH_NAMESPACE_WILDCARD)) 114 + return true; 115 + 116 + return !strcmp(pattern, target); 117 + } 118 + 107 119 /* 108 120 * Check if the mds namespace in ceph_mount_options matches 109 121 * the passed in namespace string. First time match (when 110 122 * ->mds_namespace is NULL) is treated specially, since 111 123 * ->mds_namespace needs to be initialized by the caller. 112 124 */ 113 - static inline int namespace_equals(struct ceph_mount_options *fsopt, 114 - const char *namespace, size_t len) 125 + static inline bool namespace_equals(struct ceph_mount_options *fsopt, 126 + const char *namespace, size_t len) 115 127 { 116 128 return !(fsopt->mds_namespace && 117 129 (strlen(fsopt->mds_namespace) != len ||
+6
include/linux/ceph/ceph_fs.h
··· 31 31 #define CEPH_INO_CEPH 2 /* hidden .ceph dir */ 32 32 #define CEPH_INO_GLOBAL_SNAPREALM 3 /* global dummy snaprealm */ 33 33 34 + /* 35 + * name for "old" CephFS file systems, 36 + * see ceph.git e2b151d009640114b2565c901d6f41f6cd5ec652 37 + */ 38 + #define CEPH_OLD_FS_NAME "cephfs" 39 + 34 40 /* arbitrary limit on max # of monitors (cluster of 3 is typical) */ 35 41 #define CEPH_MAX_MON 31 36 42