Commit 596afb0b authored by Xiubo Li's avatar Xiubo Li Committed by Ilya Dryomov
Browse files

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: default avatarXiubo Li <xiubli@redhat.com>
Reviewed-by: default avatarMilind Changire <mchangir@redhat.com>
Signed-off-by: default avatarIlya Dryomov <idryomov@gmail.com>
parent 1d17de95
Loading
Loading
Loading
Loading
+164 −0
Original line number Diff line number Diff line
@@ -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?)
+3 −0
Original line number Diff line number Diff line
@@ -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)