From: Xiubo Li <xiubli@xxxxxxxxxx> This will help check the mds auth access in client side. Always insert the server path in front of the target path when matching the paths. URL: https://tracker.ceph.com/issues/61333 Signed-off-by: Xiubo Li <xiubli@xxxxxxxxxx> --- fs/ceph/mds_client.c | 157 +++++++++++++++++++++++++++++++++++++++++++ fs/ceph/mds_client.h | 3 + 2 files changed, 160 insertions(+) diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 3fb0b0104f6b..81040052eb4d 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -5604,6 +5604,163 @@ void send_flush_mdlog(struct ceph_mds_session *s) mutex_unlock(&s->s_mutex); } +static int ceph_mds_auth_match(struct ceph_mds_client *mdsc, + struct ceph_mds_cap_auth *auth, + char *tpath) +{ + const struct cred *cred = get_current_cred(); + uint32_t caller_uid = from_kuid(&init_user_ns, cred->fsuid); + uint32_t caller_gid = from_kgid(&init_user_ns, cred->fsgid); + struct ceph_client *cl = mdsc->fsc->client; + const char *path = mdsc->fsc->mount_options->server_path; + bool gid_matched = false; + uint32_t gid, tlen, len; + int i, j; + + doutc(cl, "match.uid %lld\n", auth->match.uid); + if (auth->match.uid != MDS_AUTH_UID_ANY) { + if (auth->match.uid != caller_uid) + return 0; + if (auth->match.num_gids) { + for (i = 0; i < auth->match.num_gids; i++) { + if (caller_gid == auth->match.gids[i]) + gid_matched = true; + } + if (!gid_matched && cred->group_info->ngroups) { + for (i = 0; i < cred->group_info->ngroups; i++) { + gid = from_kgid(&init_user_ns, cred->group_info->gid[i]); + for (j = 0; j < auth->match.num_gids; j++) { + if (gid == auth->match.gids[j]) { + gid_matched = true; + break; + } + } + if (gid_matched) + break; + } + } + if (!gid_matched) + return 0; + } + } + + /* path match */ + if (auth->match.path) { + if (!tpath) + return 0; + + tlen = strlen(tpath); + len = strlen(auth->match.path); + if (len) { + char *_tpath = tpath; + bool free_tpath = false; + int m, n; + + doutc(cl, "server path %s, tpath %s, match.path %s\n", + path, tpath, auth->match.path); + if (path && (m = strlen(path)) != 1) { + /* mount path + '/' + tpath + an extra space */ + n = m + 1 + tlen + 1; + _tpath = kmalloc(n, GFP_NOFS); + if (!_tpath) + return -ENOMEM; + /* remove the leading '/' */ + snprintf(_tpath, n, "%s/%s", path + 1, tpath); + free_tpath = true; + tlen = strlen(_tpath); + } + + /* Remove the tailing '/' */ + while (tlen && _tpath[tlen - 1] == '/') { + _tpath[tlen - 1] = '\0'; + tlen -= 1; + } + doutc(cl, "_tpath %s\n", _tpath); + + /* In case first == _tpath && tlen == len: + * path=/foo --> /foo target_path=/foo --> match + * path=/foo/ --> /foo target_path=/foo --> match + * + * In case first == _tpath && tlen > len: + * path=/foo/ --> /foo target_path=/foo/ --> match + * path=/foo --> /foo target_path=/foo/ --> match + * path=/foo/ --> /foo target_path=/foo/d --> match + * path=/foo --> /foo target_path=/food --> mismatch + * + * All the other cases --> mismatch + */ + char *first = strstr(_tpath, auth->match.path); + if (first != _tpath) { + if (free_tpath) + kfree(_tpath); + return 0; + } + + if (tlen > len && _tpath[len] != '/') { + if (free_tpath) + kfree(_tpath); + return 0; + } + } + } + + doutc(cl, "matched\n"); + return 1; +} + +int ceph_mds_check_access(struct ceph_mds_client *mdsc, char *tpath, int mask) +{ + const struct cred *cred = get_current_cred(); + uint32_t caller_uid = from_kuid(&init_user_ns, cred->fsuid); + uint32_t caller_gid = from_kgid(&init_user_ns, cred->fsgid); + struct ceph_mds_cap_auth *rw_perms_s = NULL; + struct ceph_client *cl = mdsc->fsc->client; + bool root_squash_perms = true; + int i, err; + + doutc(cl, "tpath '%s', mask %d, caller_uid %d, caller_gid %d\n", + tpath, mask, caller_uid, caller_gid); + + for (i = 0; i < mdsc->s_cap_auths_num; i++) { + struct ceph_mds_cap_auth *s = &mdsc->s_cap_auths[i]; + + err = ceph_mds_auth_match(mdsc, s, tpath); + if (err < 0) { + return err; + } else if (err > 0) { + // always follow the last auth caps' permision + root_squash_perms = true; + rw_perms_s = NULL; + if ((mask & MAY_WRITE) && s->writeable && + s->match.root_squash && (!caller_uid || !caller_gid)) + root_squash_perms = false; + + if (((mask & MAY_WRITE) && !s->writeable) || + ((mask & MAY_READ) && !s->readable)) + rw_perms_s = s; + } + } + + doutc(cl, "root_squash_perms %d, rw_perms_s %p\n", root_squash_perms, + rw_perms_s); + if (root_squash_perms && rw_perms_s == NULL) { + doutc(cl, "access allowed\n"); + return 0; + } + + if (!root_squash_perms) { + doutc(cl, "root_squash is enabled and user(%d %d) isn't allowed to write", + caller_uid, caller_gid); + } + if (rw_perms_s) { + doutc(cl, "mds auth caps readable/writeable %d/%d while request r/w %d/%d", + rw_perms_s->readable, rw_perms_s->writeable, !!(mask & MAY_READ), + !!(mask & MAY_WRITE)); + } + doutc(cl, "access denied\n"); + return -EACCES; +} + /* * called before mount is ro, and before dentries are torn down. * (hmm, does this still race with new lookups?) diff --git a/fs/ceph/mds_client.h b/fs/ceph/mds_client.h index 71f4d1ff663f..8be57267c253 100644 --- a/fs/ceph/mds_client.h +++ b/fs/ceph/mds_client.h @@ -602,6 +602,9 @@ extern void ceph_reclaim_caps_nr(struct ceph_mds_client *mdsc, int nr); extern int ceph_iterate_session_caps(struct ceph_mds_session *session, int (*cb)(struct inode *, int mds, void *), void *arg); +extern int ceph_mds_check_access(struct ceph_mds_client *mdsc, char *tpath, + int mask); + extern void ceph_mdsc_pre_umount(struct ceph_mds_client *mdsc); static inline void ceph_mdsc_free_path(char *path, int len) -- 2.41.0