ceph: add ceph_mds_check_access() helper

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.

[ idryomov: use u32 instead of uint32_t ]

Link: https://tracker.ceph.com/issues/61333
Signed-off-by: Xiubo Li <xiubli@redhat.com>
Reviewed-by: Milind Changire <mchangir@redhat.com>
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
This commit is contained in:
Xiubo Li 2023-11-07 14:55:46 +08:00 committed by Ilya Dryomov
parent 1d17de9534
commit 596afb0b89
2 changed files with 167 additions and 0 deletions

View File

@ -5603,6 +5603,170 @@ 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();
u32 caller_uid = from_kuid(&init_user_ns, cred->fsuid);
u32 caller_gid = from_kgid(&init_user_ns, cred->fsgid);
struct ceph_client *cl = mdsc->fsc->client;
const char *spath = mdsc->fsc->mount_options->server_path;
bool gid_matched = false;
u32 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",
spath, tpath, auth->match.path);
if (spath && (m = strlen(spath)) != 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", spath + 1, tpath);
free_tpath = true;
tlen = strlen(_tpath);
}
/*
* Please note the tailing '/' for match.path has already
* been removed when parsing.
*
* Remove the tailing '/' for the target path.
*/
while (tlen && _tpath[tlen - 1] == '/') {
_tpath[tlen - 1] = '\0';
tlen -= 1;
}
doutc(cl, "_tpath %s\n", _tpath);
/*
* In case first == _tpath && tlen == len:
* match.path=/foo --> /foo _path=/foo --> match
* match.path=/foo/ --> /foo _path=/foo --> match
*
* In case first == _tmatch.path && tlen > len:
* match.path=/foo/ --> /foo _path=/foo/ --> match
* match.path=/foo --> /foo _path=/foo/ --> match
* match.path=/foo/ --> /foo _path=/foo/d --> match
* match.path=/foo --> /foo _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();
u32 caller_uid = from_kuid(&init_user_ns, cred->fsuid);
u32 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?)

View File

@ -602,6 +602,9 @@ extern void ceph_queue_cap_unlink_work(struct ceph_mds_client *mdsc);
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)