tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Common chmod and chown routines



Hi,

I would like to remove a little bit of duplicated code across our
file-system implementations by introducing two new functions:
common_chmod() and common_chown().

As the names imply, they will contain the common code found in the
file-system specific chmod and chown implementations. Calling the
common code a "policy", this policy is outlined in a comment above
each of the functions.

Notes:
  - The changes affect ufs, ext2fs, ptyfs, tmpfs, and udf.

  - The original chmod policy for ptyfs lacked common tests
    wrt/sticky and sgit bits. What I suggest is, if the (or "a")
    file-system ignores these bits, to remove them on entry to the
    file-system specific implementation before proceeding.

Diff attached, please review. :)
        
Also: might be a naive question, but is there a reason for not storing a
"struct vattr" (or similar) in "struct vnode", so that whenever the
latter is available, we already have the owner, permissions, etc. in a
unified form? :)

Thanks,

-e.
Index: sys/kern/vfs_subr.c
===================================================================
RCS file: /usr/cvs/src/sys/kern/vfs_subr.c,v
retrieving revision 1.370
diff -u -p -r1.370 vfs_subr.c
--- sys/kern/vfs_subr.c 30 Mar 2009 16:38:05 -0000      1.370
+++ sys/kern/vfs_subr.c 18 Apr 2009 04:25:12 -0000
@@ -3202,3 +3202,139 @@ vfs_mount_print(struct mount *mp, int fu
        }
 }
 #endif /* DDB */
+
+/*
+ * Common routine to check if chmod() is allowed.
+ *
+ * Policy:
+ *   - You must be root, or
+ *   - You must own the file, and
+ *     - You must not set the "sticky" bit (meaningless, see chmod(2))
+ *     - You must be a member of the file's group if it's SGIDXXX: sticky/sgid 
stuff
+ *
+ * cred - credentials of the invoker
+ * vp - vnode of the file-system object
+ * cur_uid, cur_gid - current uid/gid of the file-system object
+ * new_mode - new mode for the file-system object
+ *
+ * Returns 0 if the change is allowed, or an error value otherwise.
+ */
+int
+common_chmod(kauth_cred_t cred, struct vnode *vp, uid_t cur_uid, gid_t cur_gid,
+    mode_t new_mode)
+{
+       int error;
+
+       /* Superuser can always change mode. */
+       error = kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER, NULL);
+       if (!error) {
+               goto out;
+       }
+
+       /* Otherwise, user must own the file. */
+       if (kauth_cred_geteuid(cred) != cur_uid) {
+               goto out;
+       }
+
+       /*
+        * Non-root users can't set the sticky bit on files.
+        */
+       if ((vp->v_type != VDIR) && (new_mode & S_ISTXT)) {
+               error = EFTYPE;
+               goto out;
+       }
+
+       /*
+        * If the invoker is trying to set the SGID bit on the file, check
+        * group membership.
+        */
+       if (new_mode & S_ISGID) {
+               int ismember;
+
+               error = kauth_cred_ismember_gid(cred, cur_gid, &ismember);
+               if (error)
+                       goto out;
+
+               if (!ismember) {
+                       error = EPERM;
+                       goto out;
+               }
+       }
+
+       error = 0;
+
+ out:
+       return (error);
+}
+
+/*
+ * Common routine to check if chown() is allowed.
+ *
+ * Policy:
+ *   - You must be root, or
+ *   - You must own the file, and
+ *     - You must not try to change ownership, and
+ *     - You must be member of the new group
+ *
+ * cred - credentials of the invoker
+ * cur_uid, cur_gid - current uid/gid of the file-system object
+ * new_uid, new_gid - target uid/gid of the file-system object
+ *
+ * Returns 0 if the change is allowed, or an error value otherwise.
+ */
+int    
+common_chown(kauth_cred_t cred, uid_t cur_uid, gid_t cur_gid, uid_t new_uid,
+    gid_t new_gid)
+{
+       int error, ismember;
+
+       /*
+        * You can only change ownership of a file if:
+        * You are the superuser, or...
+        */
+       error = kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER, NULL);
+       if (!error) {
+               goto out;
+       }
+
+       /*
+        * You own the file and...
+        */
+       if (kauth_cred_geteuid(cred) == cur_uid) {
+               /*
+                * You don't try to change ownership, and...
+                */
+                if (new_uid != cur_uid) {
+                       goto out;
+               }
+
+               /*
+                * You don't try to change group (no-op), or...
+                */
+               if (new_gid == cur_gid) {
+                       error = 0;
+                       goto out;
+               }
+
+               /*
+                * Your effective gid is the new gid, or...
+                */
+               if (kauth_cred_getegid(cred) == new_gid) {
+                       error = 0;
+                       goto out;
+               }
+
+               /*
+                * The new gid is one you're a member of.
+                */
+               ismember = 0;
+                if (kauth_cred_ismember_gid(cred, new_gid, &ismember) == 0 && 
ismember) {
+                       error = 0;
+                       goto out;
+               }
+       }
+
+ out:
+       return (error);
+}
+
Index: sys/fs/ptyfs/ptyfs_vnops.c
===================================================================
RCS file: /usr/cvs/src/sys/fs/ptyfs/ptyfs_vnops.c,v
retrieving revision 1.27
diff -u -p -r1.27 ptyfs_vnops.c
--- sys/fs/ptyfs/ptyfs_vnops.c  2 Jan 2008 11:48:43 -0000       1.27
+++ sys/fs/ptyfs/ptyfs_vnops.c  18 Apr 2009 04:45:32 -0000
@@ -464,10 +464,11 @@ ptyfs_chmod(struct vnode *vp, mode_t mod
        struct ptyfsnode *ptyfs = VTOPTYFS(vp);
        int error;
 
-       if (kauth_cred_geteuid(cred) != ptyfs->ptyfs_uid &&
-           (error = kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER,
-           NULL)) != 0)
-               return error;
+       error = common_chmod(cred, vp, ptyfs->ptyfs_uid, ptyfs->ptyfs_gid,
+           mode);
+       if (error)
+               return (error);
+
        ptyfs->ptyfs_mode &= ~ALLPERMS;
        ptyfs->ptyfs_mode |= (mode & ALLPERMS);
        return 0;
@@ -482,25 +483,16 @@ ptyfs_chown(struct vnode *vp, uid_t uid,
     struct lwp *l)
 {
        struct ptyfsnode *ptyfs = VTOPTYFS(vp);
-       int             error, ismember = 0;
+       int error;
 
        if (uid == (uid_t)VNOVAL)
                uid = ptyfs->ptyfs_uid;
        if (gid == (gid_t)VNOVAL)
                gid = ptyfs->ptyfs_gid;
-       /*
-        * If we don't own the file, are trying to change the owner
-        * of the file, or are not a member of the target group,
-        * the caller's credentials must imply super-user privilege
-        * or the call fails.
-        */
-       if ((kauth_cred_geteuid(cred) != ptyfs->ptyfs_uid || uid != 
ptyfs->ptyfs_uid ||
-           (gid != ptyfs->ptyfs_gid &&
-           !(kauth_cred_getegid(cred) == gid ||
-           (kauth_cred_ismember_gid(cred, gid, &ismember) == 0 && ismember)))) 
&&
-           ((error = kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER,
-           NULL)) != 0))
-               return error;
+
+       error = common_chown(cred, ptyfs->ptyfs_uid, ptyfs->ptyfs_gid, uid, 
gid);
+       if (error)
+               return (error);
 
        ptyfs->ptyfs_gid = gid;
        ptyfs->ptyfs_uid = uid;
Index: sys/fs/udf/udf_vnops.c
===================================================================
RCS file: /usr/cvs/src/sys/fs/udf/udf_vnops.c,v
retrieving revision 1.38
diff -u -p -r1.38 udf_vnops.c
--- sys/fs/udf/udf_vnops.c      20 Mar 2009 23:06:52 -0000      1.38
+++ sys/fs/udf/udf_vnops.c      18 Apr 2009 04:28:52 -0000
@@ -935,9 +935,8 @@ udf_chown(struct vnode *vp, uid_t new_ui
          kauth_cred_t cred)
 {
        struct udf_node  *udf_node = VTOI(vp);
-       uid_t euid, uid;
-       gid_t egid, gid;
-       int issuperuser, ismember;
+       uid_t uid;
+       gid_t gid;
        int error;
 
 #ifdef notyet
@@ -965,26 +964,10 @@ udf_chown(struct vnode *vp, uid_t new_ui
        if ((gid_t) ((uint32_t) gid) != gid)
                return EINVAL;
 
-       /*
-        * If we don't own the file, are trying to change the owner of the
-        * file, or are not a member of the target group, the caller's
-        * credentials must imply super-user privilege or the call fails.
-        */
-
        /* check permissions */
-       euid  = kauth_cred_geteuid(cred);
-       egid  = kauth_cred_getegid(cred);
-       if ((error = kauth_cred_ismember_gid(cred, new_gid, &ismember)))
-               return error;
-       error = kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER, NULL);
-       issuperuser = (error == 0);
-
-       if (!issuperuser) {
-               if ((new_uid != uid) || (euid != uid))
-                       return EPERM;
-               if ((new_gid != gid) && !(egid == new_gid || ismember)) 
-                       return EPERM;
-       }
+       error = common_chown(cred, uid, gid, new_uid, new_gid);
+       if (error)
+               return (error);
 
        /* change the ownership */
        udf_setownership(udf_node, new_uid, new_gid);
@@ -1000,9 +983,8 @@ static int
 udf_chmod(struct vnode *vp, mode_t mode, kauth_cred_t cred)
 {
        struct udf_node  *udf_node = VTOI(vp);
-       uid_t euid, uid;
-       gid_t egid, gid;
-       int issuperuser, ismember;
+       uid_t uid;
+       gid_t gid;
        int error;
 
 #ifdef notyet
@@ -1019,22 +1001,9 @@ udf_chmod(struct vnode *vp, mode_t mode,
        udf_getownership(udf_node, &uid, &gid);
 
        /* check permissions */
-       euid  = kauth_cred_geteuid(cred);
-       egid  = kauth_cred_getegid(cred);
-       if ((error = kauth_cred_ismember_gid(cred, gid, &ismember)))
-               return error;
-       error = kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER, NULL);
-       issuperuser = (error == 0);
-
-       if ((euid != uid) && !issuperuser)
-               return EPERM;
-       if (euid != 0) {
-               if (vp->v_type != VDIR && (mode & S_ISTXT))
-                       return EFTYPE;
-
-               if ((!ismember) && (mode & S_ISGID))
-                       return EPERM;
-       }
+       error = common_chmod(cred, vp, uid, gid, mode);
+       if (error)
+               return (error);
 
        /* change mode */
        udf_setaccessmode(udf_node, mode);
Index: sys/fs/tmpfs/tmpfs_subr.c
===================================================================
RCS file: /usr/cvs/src/sys/fs/tmpfs/tmpfs_subr.c,v
retrieving revision 1.48
diff -u -p -r1.48 tmpfs_subr.c
--- sys/fs/tmpfs/tmpfs_subr.c   19 Jun 2008 19:03:44 -0000      1.48
+++ sys/fs/tmpfs/tmpfs_subr.c   18 Apr 2009 04:28:25 -0000
@@ -1017,7 +1017,7 @@ tmpfs_chflags(struct vnode *vp, int flag
 int
 tmpfs_chmod(struct vnode *vp, mode_t mode, kauth_cred_t cred, struct lwp *l)
 {
-       int error, ismember = 0;
+       int error;
        struct tmpfs_node *node;
 
        KASSERT(VOP_ISLOCKED(vp));
@@ -1032,21 +1032,9 @@ tmpfs_chmod(struct vnode *vp, mode_t mod
        if (node->tn_flags & (IMMUTABLE | APPEND))
                return EPERM;
 
-       /* XXX: The following comes from UFS code, and can be found in
-        * several other file systems.  Shouldn't this be centralized
-        * somewhere? */
-       if (kauth_cred_geteuid(cred) != node->tn_uid &&
-           (error = kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER,
-           NULL)))
-               return error;
-       if (kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER, NULL) != 0) {
-               if (vp->v_type != VDIR && (mode & S_ISTXT))
-                       return EFTYPE;
-
-               if ((kauth_cred_ismember_gid(cred, node->tn_gid,
-                   &ismember) != 0 || !ismember) && (mode & S_ISGID))
-                       return EPERM;
-       }
+       error = common_chmod(cred, vp, node->tn_uid, node->tn_gid, mode);
+       if (error)
+               return (error);
 
        node->tn_mode = (mode & ALLPERMS);
 
@@ -1071,7 +1059,7 @@ int
 tmpfs_chown(struct vnode *vp, uid_t uid, gid_t gid, kauth_cred_t cred,
     struct lwp *l)
 {
-       int error, ismember = 0;
+       int error;
        struct tmpfs_node *node;
 
        KASSERT(VOP_ISLOCKED(vp));
@@ -1094,15 +1082,9 @@ tmpfs_chown(struct vnode *vp, uid_t uid,
        if (node->tn_flags & (IMMUTABLE | APPEND))
                return EPERM;
 
-       /* XXX: The following comes from UFS code, and can be found in
-        * several other file systems.  Shouldn't this be centralized
-        * somewhere? */
-       if ((kauth_cred_geteuid(cred) != node->tn_uid || uid != node->tn_uid ||
-           (gid != node->tn_gid && !(kauth_cred_getegid(cred) == node->tn_gid 
||
-           (kauth_cred_ismember_gid(cred, gid, &ismember) == 0 && ismember)))) 
&&
-           ((error = kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER,
-           NULL)) != 0))
-               return error;
+       error = common_chown(cred, node->tn_uid, node->tn_gid, uid, gid);
+       if (error)
+               return (error);
 
        node->tn_uid = uid;
        node->tn_gid = gid;
Index: sys/ufs/ufs/ufs_vnops.c
===================================================================
RCS file: /usr/cvs/src/sys/ufs/ufs/ufs_vnops.c,v
retrieving revision 1.173
diff -u -p -r1.173 ufs_vnops.c
--- sys/ufs/ufs/ufs_vnops.c     22 Feb 2009 20:28:07 -0000      1.173
+++ sys/ufs/ufs/ufs_vnops.c     18 Apr 2009 04:47:24 -0000
@@ -640,21 +640,16 @@ static int
 ufs_chmod(struct vnode *vp, int mode, kauth_cred_t cred, struct lwp *l)
 {
        struct inode    *ip;
-       int             error, ismember = 0;
+       int             error;
 
        UFS_WAPBL_JLOCK_ASSERT(vp->v_mount);
 
        ip = VTOI(vp);
-       if (kauth_cred_geteuid(cred) != ip->i_uid &&
-           (error = kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER, 
NULL)))
+
+       error = common_chmod(cred, vp, ip->i_uid, ip->i_gid, mode);
+       if (error)
                return (error);
-       if (kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER, NULL)) {
-               if (vp->v_type != VDIR && (mode & S_ISTXT))
-                       return (EFTYPE);
-               if ((kauth_cred_ismember_gid(cred, ip->i_gid, &ismember) != 0 ||
-                   !ismember) && (mode & ISGID))
-                       return (EPERM);
-       }
+
        ip->i_mode &= ~ALLPERMS;
        ip->i_mode |= (mode & ALLPERMS);
        ip->i_flag |= IN_CHANGE;
@@ -672,7 +667,7 @@ ufs_chown(struct vnode *vp, uid_t uid, g
        struct lwp *l)
 {
        struct inode    *ip;
-       int             error, ismember = 0;
+       int             error = 0;
 #ifdef QUOTA
        uid_t           ouid;
        gid_t           ogid;
@@ -685,19 +680,9 @@ ufs_chown(struct vnode *vp, uid_t uid, g
                uid = ip->i_uid;
        if (gid == (gid_t)VNOVAL)
                gid = ip->i_gid;
-       /*
-        * If we don't own the file, are trying to change the owner
-        * of the file, or are not a member of the target group,
-        * the caller's credentials must imply super-user privilege
-        * or the call fails.
-        */
-       if ((kauth_cred_geteuid(cred) != ip->i_uid || uid != ip->i_uid ||
-           (gid != ip->i_gid &&
-           !(kauth_cred_getegid(cred) == gid ||
-           (kauth_cred_ismember_gid(cred, gid, &ismember) == 0 &&
-           ismember)))) &&
-           ((error = kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER,
-           NULL)) != 0))
+
+       error = common_chown(cred, ip->i_uid, ip->i_gid, uid, gid);
+       if (error)
                return (error);
 
 #ifdef QUOTA
Index: sys/ufs/ext2fs/ext2fs_vnops.c
===================================================================
RCS file: /usr/cvs/src/sys/ufs/ext2fs/ext2fs_vnops.c,v
retrieving revision 1.83
diff -u -p -r1.83 ext2fs_vnops.c
--- sys/ufs/ext2fs/ext2fs_vnops.c       23 Nov 2008 10:09:25 -0000      1.83
+++ sys/ufs/ext2fs/ext2fs_vnops.c       18 Apr 2009 04:27:54 -0000
@@ -442,19 +442,12 @@ static int
 ext2fs_chmod(struct vnode *vp, int mode, kauth_cred_t cred, struct lwp *l)
 {
        struct inode *ip = VTOI(vp);
-       int error, ismember = 0;
+       int error;
 
-       if (kauth_cred_geteuid(cred) != ip->i_uid &&
-           (error = kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER,
-           NULL)))
+       error = common_chmod(cred, vp, ip->i_uid, ip->i_gid, mode);
+       if (error)
                return (error);
-       if (kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER, NULL)) {
-               if (vp->v_type != VDIR && (mode & S_ISTXT))
-                       return (EFTYPE);
-               if ((kauth_cred_ismember_gid(cred, ip->i_gid, &ismember) != 0 ||
-                   !ismember) && (mode & ISGID))
-                       return (EPERM);
-       }
+
        ip->i_e2fs_mode &= ~ALLPERMS;
        ip->i_e2fs_mode |= (mode & ALLPERMS);
        ip->i_flag |= IN_CHANGE;
@@ -472,23 +465,17 @@ ext2fs_chown(struct vnode *vp, uid_t uid
        struct inode *ip = VTOI(vp);
        uid_t ouid;
        gid_t ogid;
-       int error = 0, ismember = 0;
+       int error;
 
        if (uid == (uid_t)VNOVAL)
                uid = ip->i_uid;
        if (gid == (gid_t)VNOVAL)
                gid = ip->i_gid;
-       /*
-        * If we don't own the file, are trying to change the owner
-        * of the file, or are not a member of the target group,
-        * the caller must be superuser or the call fails.
-        */
-       if ((kauth_cred_geteuid(cred) != ip->i_uid || uid != ip->i_uid ||
-           (gid != ip->i_gid &&
-           !(kauth_cred_getegid(cred) == gid ||
-           (kauth_cred_ismember_gid(cred, gid, &ismember) == 0 && ismember)))) 
&&
-           (error = kauth_authorize_generic(cred, KAUTH_GENERIC_ISSUSER, 
NULL)))
+
+       error = common_chown(cred, ip->i_uid, ip->i_gid, uid, gid);
+       if (error)
                return (error);
+
        ogid = ip->i_gid;
        ouid = ip->i_uid;
 


Home | Main Index | Thread Index | Old Index