]> git.proxmox.com Git - mirror_ubuntu-kernels.git/commitdiff
ceph: add ceph_mds_check_access() helper
authorXiubo Li <xiubli@redhat.com>
Tue, 7 Nov 2023 06:55:46 +0000 (14:55 +0800)
committerIlya Dryomov <idryomov@gmail.com>
Thu, 23 May 2024 08:35:46 +0000 (10:35 +0200)
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>
fs/ceph/mds_client.c
fs/ceph/mds_client.h

index 6a1e68549475ebcfc5c1fd4c4be2dd15bf446087..c2157f6e0c698219d2884461a3463bdf0216f970 100644 (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?)
index cc106f1aa7d2a9cb021695fbff838d0908d8d9c9..dba514fb7fab8c0945cfa7b69b1690e9eabffbbe 100644 (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)