Merge tag 'fixes-v5.10a' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris...
[linux-2.6-microblaze.git] / security / safesetid / lsm.c
index 7760019..8a176b6 100644 (file)
 /* Flag indicating whether initialization completed */
 int safesetid_initialized;
 
-struct setuid_ruleset __rcu *safesetid_setuid_rules;
+struct setid_ruleset __rcu *safesetid_setuid_rules;
+struct setid_ruleset __rcu *safesetid_setgid_rules;
+
 
 /* Compute a decision for a transition from @src to @dst under @policy. */
-enum sid_policy_type _setuid_policy_lookup(struct setuid_ruleset *policy,
-               kuid_t src, kuid_t dst)
+enum sid_policy_type _setid_policy_lookup(struct setid_ruleset *policy,
+               kid_t src, kid_t dst)
 {
-       struct setuid_rule *rule;
+       struct setid_rule *rule;
        enum sid_policy_type result = SIDPOL_DEFAULT;
 
-       hash_for_each_possible(policy->rules, rule, next, __kuid_val(src)) {
-               if (!uid_eq(rule->src_uid, src))
-                       continue;
-               if (uid_eq(rule->dst_uid, dst))
-                       return SIDPOL_ALLOWED;
+       if (policy->type == UID) {
+               hash_for_each_possible(policy->rules, rule, next, __kuid_val(src.uid)) {
+                       if (!uid_eq(rule->src_id.uid, src.uid))
+                               continue;
+                       if (uid_eq(rule->dst_id.uid, dst.uid))
+                               return SIDPOL_ALLOWED;
+                       result = SIDPOL_CONSTRAINED;
+               }
+       } else if (policy->type == GID) {
+               hash_for_each_possible(policy->rules, rule, next, __kgid_val(src.gid)) {
+                       if (!gid_eq(rule->src_id.gid, src.gid))
+                               continue;
+                       if (gid_eq(rule->dst_id.gid, dst.gid)){
+                               return SIDPOL_ALLOWED;
+                       }
+                       result = SIDPOL_CONSTRAINED;
+               }
+       } else {
+               /* Should not reach here, report the ID as contrainsted */
                result = SIDPOL_CONSTRAINED;
        }
        return result;
@@ -47,15 +63,26 @@ enum sid_policy_type _setuid_policy_lookup(struct setuid_ruleset *policy,
  * Compute a decision for a transition from @src to @dst under the active
  * policy.
  */
-static enum sid_policy_type setuid_policy_lookup(kuid_t src, kuid_t dst)
+static enum sid_policy_type setid_policy_lookup(kid_t src, kid_t dst, enum setid_type new_type)
 {
        enum sid_policy_type result = SIDPOL_DEFAULT;
-       struct setuid_ruleset *pol;
+       struct setid_ruleset *pol;
 
        rcu_read_lock();
-       pol = rcu_dereference(safesetid_setuid_rules);
-       if (pol)
-               result = _setuid_policy_lookup(pol, src, dst);
+       if (new_type == UID)
+               pol = rcu_dereference(safesetid_setuid_rules);
+       else if (new_type == GID)
+               pol = rcu_dereference(safesetid_setgid_rules);
+       else { /* Should not reach here */
+               result = SIDPOL_CONSTRAINED;
+               rcu_read_unlock();
+               return result;
+       }
+
+       if (pol) {
+               pol->type = new_type;
+               result = _setid_policy_lookup(pol, src, dst);
+       }
        rcu_read_unlock();
        return result;
 }
@@ -65,57 +92,101 @@ static int safesetid_security_capable(const struct cred *cred,
                                      int cap,
                                      unsigned int opts)
 {
-       /* We're only interested in CAP_SETUID. */
-       if (cap != CAP_SETUID)
+       /* We're only interested in CAP_SETUID and CAP_SETGID. */
+       if (cap != CAP_SETUID && cap != CAP_SETGID)
                return 0;
 
        /*
-        * If CAP_SETUID is currently used for a set*uid() syscall, we want to
+        * If CAP_SET{U/G}ID is currently used for a setid() syscall, we want to
         * let it go through here; the real security check happens later, in the
-        * task_fix_setuid hook.
+        * task_fix_set{u/g}id hook.
+         *
+         * NOTE:
+         * Until we add support for restricting setgroups() calls, GID security
+         * policies offer no meaningful security since we always return 0 here
+         * when called from within the setgroups() syscall and there is no
+         * additional hook later on to enforce security policies for setgroups().
         */
        if ((opts & CAP_OPT_INSETID) != 0)
                return 0;
 
-       /*
-        * If no policy applies to this task, allow the use of CAP_SETUID for
-        * other purposes.
-        */
-       if (setuid_policy_lookup(cred->uid, INVALID_UID) == SIDPOL_DEFAULT)
+       switch (cap) {
+       case CAP_SETUID:
+               /*
+               * If no policy applies to this task, allow the use of CAP_SETUID for
+               * other purposes.
+               */
+               if (setid_policy_lookup((kid_t){.uid = cred->uid}, INVALID_ID, UID) == SIDPOL_DEFAULT)
+                       return 0;
+               /*
+                * Reject use of CAP_SETUID for functionality other than calling
+                * set*uid() (e.g. setting up userns uid mappings).
+                */
+               pr_warn("Operation requires CAP_SETUID, which is not available to UID %u for operations besides approved set*uid transitions\n",
+                       __kuid_val(cred->uid));
+               return -EPERM;
+               break;
+       case CAP_SETGID:
+               /*
+               * If no policy applies to this task, allow the use of CAP_SETGID for
+               * other purposes.
+               */
+               if (setid_policy_lookup((kid_t){.gid = cred->gid}, INVALID_ID, GID) == SIDPOL_DEFAULT)
+                       return 0;
+               /*
+                * Reject use of CAP_SETUID for functionality other than calling
+                * set*gid() (e.g. setting up userns gid mappings).
+                */
+               pr_warn("Operation requires CAP_SETGID, which is not available to GID %u for operations besides approved set*gid transitions\n",
+                       __kuid_val(cred->uid));
+               return -EPERM;
+               break;
+       default:
+               /* Error, the only capabilities were checking for is CAP_SETUID/GID */
                return 0;
-
-       /*
-        * Reject use of CAP_SETUID for functionality other than calling
-        * set*uid() (e.g. setting up userns uid mappings).
-        */
-       pr_warn("Operation requires CAP_SETUID, which is not available to UID %u for operations besides approved set*uid transitions\n",
-               __kuid_val(cred->uid));
-       return -EPERM;
+               break;
+       }
+       return 0;
 }
 
 /*
  * Check whether a caller with old credentials @old is allowed to switch to
- * credentials that contain @new_uid.
+ * credentials that contain @new_id.
  */
-static bool uid_permitted_for_cred(const struct cred *old, kuid_t new_uid)
+static bool id_permitted_for_cred(const struct cred *old, kid_t new_id, enum setid_type new_type)
 {
        bool permitted;
 
-       /* If our old creds already had this UID in it, it's fine. */
-       if (uid_eq(new_uid, old->uid) || uid_eq(new_uid, old->euid) ||
-           uid_eq(new_uid, old->suid))
-               return true;
+       /* If our old creds already had this ID in it, it's fine. */
+       if (new_type == UID) {
+               if (uid_eq(new_id.uid, old->uid) || uid_eq(new_id.uid, old->euid) ||
+                       uid_eq(new_id.uid, old->suid))
+                       return true;
+       } else if (new_type == GID){
+               if (gid_eq(new_id.gid, old->gid) || gid_eq(new_id.gid, old->egid) ||
+                       gid_eq(new_id.gid, old->sgid))
+                       return true;
+       } else /* Error, new_type is an invalid type */
+               return false;
 
        /*
         * Transitions to new UIDs require a check against the policy of the old
         * RUID.
         */
        permitted =
-           setuid_policy_lookup(old->uid, new_uid) != SIDPOL_CONSTRAINED;
+           setid_policy_lookup((kid_t){.uid = old->uid}, new_id, new_type) != SIDPOL_CONSTRAINED;
+
        if (!permitted) {
-               pr_warn("UID transition ((%d,%d,%d) -> %d) blocked\n",
-                       __kuid_val(old->uid), __kuid_val(old->euid),
-                       __kuid_val(old->suid), __kuid_val(new_uid));
+               if (new_type == UID) {
+                       pr_warn("UID transition ((%d,%d,%d) -> %d) blocked\n",
+                               __kuid_val(old->uid), __kuid_val(old->euid),
+                               __kuid_val(old->suid), __kuid_val(new_id.uid));
+               } else if (new_type == GID) {
+                       pr_warn("GID transition ((%d,%d,%d) -> %d) blocked\n",
+                               __kgid_val(old->gid), __kgid_val(old->egid),
+                               __kgid_val(old->sgid), __kgid_val(new_id.gid));
+               } else /* Error, new_type is an invalid type */
+                       return false;
        }
        return permitted;
 }
@@ -131,18 +202,42 @@ static int safesetid_task_fix_setuid(struct cred *new,
 {
 
        /* Do nothing if there are no setuid restrictions for our old RUID. */
-       if (setuid_policy_lookup(old->uid, INVALID_UID) == SIDPOL_DEFAULT)
+       if (setid_policy_lookup((kid_t){.uid = old->uid}, INVALID_ID, UID) == SIDPOL_DEFAULT)
+               return 0;
+
+       if (id_permitted_for_cred(old, (kid_t){.uid = new->uid}, UID) &&
+           id_permitted_for_cred(old, (kid_t){.uid = new->euid}, UID) &&
+           id_permitted_for_cred(old, (kid_t){.uid = new->suid}, UID) &&
+           id_permitted_for_cred(old, (kid_t){.uid = new->fsuid}, UID))
+               return 0;
+
+       /*
+        * Kill this process to avoid potential security vulnerabilities
+        * that could arise from a missing allowlist entry preventing a
+        * privileged process from dropping to a lesser-privileged one.
+        */
+       force_sig(SIGKILL);
+       return -EACCES;
+}
+
+static int safesetid_task_fix_setgid(struct cred *new,
+                                    const struct cred *old,
+                                    int flags)
+{
+
+       /* Do nothing if there are no setgid restrictions for our old RGID. */
+       if (setid_policy_lookup((kid_t){.gid = old->gid}, INVALID_ID, GID) == SIDPOL_DEFAULT)
                return 0;
 
-       if (uid_permitted_for_cred(old, new->uid) &&
-           uid_permitted_for_cred(old, new->euid) &&
-           uid_permitted_for_cred(old, new->suid) &&
-           uid_permitted_for_cred(old, new->fsuid))
+       if (id_permitted_for_cred(old, (kid_t){.gid = new->gid}, GID) &&
+           id_permitted_for_cred(old, (kid_t){.gid = new->egid}, GID) &&
+           id_permitted_for_cred(old, (kid_t){.gid = new->sgid}, GID) &&
+           id_permitted_for_cred(old, (kid_t){.gid = new->fsgid}, GID))
                return 0;
 
        /*
         * Kill this process to avoid potential security vulnerabilities
-        * that could arise from a missing whitelist entry preventing a
+        * that could arise from a missing allowlist entry preventing a
         * privileged process from dropping to a lesser-privileged one.
         */
        force_sig(SIGKILL);
@@ -151,6 +246,7 @@ static int safesetid_task_fix_setuid(struct cred *new,
 
 static struct security_hook_list safesetid_security_hooks[] = {
        LSM_HOOK_INIT(task_fix_setuid, safesetid_task_fix_setuid),
+       LSM_HOOK_INIT(task_fix_setgid, safesetid_task_fix_setgid),
        LSM_HOOK_INIT(capable, safesetid_security_capable)
 };