1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 
  26 
  27 #pragma ident   "%Z%%M% %I%     %E% SMI"
  28 
  29 
  30 #include <sys/types.h>
  31 #include <sys/systm.h>
  32 #include <sys/errno.h>
  33 #include <sys/zone.h>
  34 #include <sys/cred_impl.h>
  35 #include <sys/policy.h>
  36 
  37 typedef ushort_t        l_uid16_t;
  38 typedef ushort_t        l_gid16_t;
  39 typedef uint_t          l_uid_t;
  40 typedef uint_t          l_gid_t;
  41 
  42 #define LINUX_UID16_TO_UID32(uid16)     \
  43         (((uid16) == (l_uid16_t)-1) ? ((l_uid_t)-1) : (l_uid_t)(uid16))
  44 
  45 #define LINUX_GID16_TO_GID32(gid16)     \
  46         (((gid16) == (l_gid16_t)-1) ? ((l_gid_t)-1) : (l_gid_t)(gid16))
  47 
  48 #define LX_NGROUPS_MAX  32
  49 extern int setgroups(int, gid_t *);
  50 
  51 /*
  52  * This function is based on setreuid in common/syscall/uid.c and exists
  53  * because Solaris does not have a way to explicitly set the saved uid (suid)
  54  * from any other system call.
  55  */
  56 long
  57 lx_setresuid(l_uid_t ruid, l_uid_t euid, l_uid_t suid)
  58 {
  59         proc_t  *p;
  60         int     error = 0;
  61         int     do_nocd = 0;
  62         int     uidchge = 0;
  63         uid_t   oldruid = ruid;
  64         cred_t  *cr, *newcr;
  65         zoneid_t zoneid = getzoneid();
  66 
  67         if ((ruid != -1 && (ruid > MAXUID)) ||
  68             (euid != -1 && (euid > MAXUID)) ||
  69             (suid != -1 && (suid > MAXUID))) {
  70                 error = EINVAL;
  71                 goto done;
  72         }
  73 
  74         /*
  75          * Need to pre-allocate the new cred structure before grabbing
  76          * the p_crlock mutex.
  77          */
  78         newcr = cralloc();
  79 
  80         p = ttoproc(curthread);
  81 
  82 retry:
  83         mutex_enter(&p->p_crlock);
  84         cr = p->p_cred;
  85 
  86         if (ruid != -1 &&
  87             ruid != cr->cr_ruid && ruid != cr->cr_uid &&
  88             ruid != cr->cr_suid && secpolicy_allow_setid(cr, ruid, B_FALSE)) {
  89                 error = EPERM;
  90         } else if (euid != -1 &&
  91             euid != cr->cr_ruid && euid != cr->cr_uid &&
  92             euid != cr->cr_suid && secpolicy_allow_setid(cr, euid, B_FALSE)) {
  93                 error = EPERM;
  94         } else if (suid != -1 &&
  95             suid != cr->cr_ruid && suid != cr->cr_uid &&
  96             suid != cr->cr_suid && secpolicy_allow_setid(cr, suid, B_FALSE)) {
  97                 error = EPERM;
  98         } else {
  99                 if (!uidchge && ruid != -1 && cr->cr_ruid != ruid) {
 100                         /*
 101                          * The ruid of the process is going to change. In order
 102                          * to avoid a race condition involving the
 103                          * process count associated with the newly given ruid,
 104                          * we increment the count before assigning the
 105                          * credential to the process.
 106                          * To do that, we'll have to take pidlock, so we first
 107                          * release p_crlock.
 108                          */
 109                         mutex_exit(&p->p_crlock);
 110                         uidchge = 1;
 111                         mutex_enter(&pidlock);
 112                         upcount_inc(ruid, zoneid);
 113                         mutex_exit(&pidlock);
 114                         /*
 115                          * As we released p_crlock we can't rely on the cr
 116                          * we read. So retry the whole thing.
 117                          */
 118                         goto retry;
 119                 }
 120                 crhold(cr);
 121                 crcopy_to(cr, newcr);
 122                 p->p_cred = newcr;
 123 
 124                 if (euid != -1)
 125                         newcr->cr_uid = euid;
 126                 if (suid != -1)
 127                         newcr->cr_suid = suid;
 128                 if (ruid != -1) {
 129                         oldruid = newcr->cr_ruid;
 130                         newcr->cr_ruid = ruid;
 131                         ASSERT(ruid != oldruid ? uidchge : 1);
 132                 }
 133 
 134                 /*
 135                  * A process that gives up its privilege
 136                  * must be marked to produce no core dump.
 137                  */
 138                 if ((cr->cr_uid != newcr->cr_uid ||
 139                     cr->cr_ruid != newcr->cr_ruid ||
 140                     cr->cr_suid != newcr->cr_suid))
 141                         do_nocd = 1;
 142 
 143                 crfree(cr);
 144         }
 145         mutex_exit(&p->p_crlock);
 146 
 147         /*
 148          * We decrement the number of processes associated with the oldruid
 149          * to match the increment above, even if the ruid of the process
 150          * did not change or an error occurred (oldruid == uid).
 151          */
 152         if (uidchge) {
 153                 ASSERT(oldruid != -1 && ruid != -1);
 154                 mutex_enter(&pidlock);
 155                 upcount_dec(oldruid, zoneid);
 156                 mutex_exit(&pidlock);
 157         }
 158 
 159         if (error == 0) {
 160                 if (do_nocd) {
 161                         mutex_enter(&p->p_lock);
 162                         p->p_flag |= SNOCD;
 163                         mutex_exit(&p->p_lock);
 164                 }
 165                 crset(p, newcr);        /* broadcast to process threads */
 166                 goto done;
 167         }
 168         crfree(newcr);
 169 done:
 170         if (error)
 171                 return (set_errno(error));
 172         else
 173                 return (0);
 174 }
 175 
 176 long
 177 lx_setresuid16(l_uid16_t ruid16, l_uid16_t euid16, l_uid16_t suid16)
 178 {
 179         long    rval;
 180 
 181         rval = lx_setresuid(
 182                         LINUX_UID16_TO_UID32(ruid16),
 183                         LINUX_UID16_TO_UID32(euid16),
 184                         LINUX_UID16_TO_UID32(suid16));
 185 
 186         return (rval);
 187 }
 188 
 189 /*
 190  * This function is based on setregid in common/syscall/gid.c
 191  */
 192 long
 193 lx_setresgid(l_gid_t rgid, l_gid_t egid, l_gid_t sgid)
 194 {
 195         proc_t  *p;
 196         int     error = 0;
 197         int     do_nocd = 0;
 198         cred_t  *cr, *newcr;
 199 
 200         if ((rgid != -1 && (rgid > MAXUID)) ||
 201             (egid != -1 && (egid > MAXUID)) ||
 202             (sgid != -1 && (sgid > MAXUID))) {
 203                 error = EINVAL;
 204                 goto done;
 205         }
 206 
 207         /*
 208          * Need to pre-allocate the new cred structure before grabbing
 209          * the p_crlock mutex.
 210          */
 211         newcr = cralloc();
 212 
 213         p = ttoproc(curthread);
 214         mutex_enter(&p->p_crlock);
 215         cr = p->p_cred;
 216 
 217         if (rgid != -1 &&
 218             rgid != cr->cr_rgid && rgid != cr->cr_gid &&
 219             rgid != cr->cr_sgid && secpolicy_allow_setid(cr, -1, B_FALSE)) {
 220                 error = EPERM;
 221         } else if (egid != -1 &&
 222             egid != cr->cr_rgid && egid != cr->cr_gid &&
 223             egid != cr->cr_sgid && secpolicy_allow_setid(cr, -1, B_FALSE)) {
 224                 error = EPERM;
 225         } else if (sgid != -1 &&
 226             sgid != cr->cr_rgid && sgid != cr->cr_gid &&
 227             sgid != cr->cr_sgid && secpolicy_allow_setid(cr, -1, B_FALSE)) {
 228                 error = EPERM;
 229         } else {
 230                 crhold(cr);
 231                 crcopy_to(cr, newcr);
 232                 p->p_cred = newcr;
 233 
 234                 if (egid != -1)
 235                         newcr->cr_gid = egid;
 236                 if (sgid != -1)
 237                         newcr->cr_sgid = sgid;
 238                 if (rgid != -1)
 239                         newcr->cr_rgid = rgid;
 240 
 241                 /*
 242                  * A process that gives up its privilege
 243                  * must be marked to produce no core dump.
 244                  */
 245                 if ((cr->cr_gid != newcr->cr_gid ||
 246                     cr->cr_rgid != newcr->cr_rgid ||
 247                     cr->cr_sgid != newcr->cr_sgid))
 248                         do_nocd = 1;
 249 
 250                 crfree(cr);
 251         }
 252         mutex_exit(&p->p_crlock);
 253 
 254         if (error == 0) {
 255                 if (do_nocd) {
 256                         mutex_enter(&p->p_lock);
 257                         p->p_flag |= SNOCD;
 258                         mutex_exit(&p->p_lock);
 259                 }
 260                 crset(p, newcr);        /* broadcast to process threads */
 261                 goto done;
 262         }
 263         crfree(newcr);
 264 done:
 265         if (error)
 266                 return (set_errno(error));
 267         else
 268                 return (0);
 269 }
 270 
 271 long
 272 lx_setresgid16(l_gid16_t rgid16, l_gid16_t egid16, l_gid16_t sgid16)
 273 {
 274         long    rval;
 275 
 276         rval = lx_setresgid(
 277                         LINUX_GID16_TO_GID32(rgid16),
 278                         LINUX_GID16_TO_GID32(egid16),
 279                         LINUX_GID16_TO_GID32(sgid16));
 280 
 281         return (rval);
 282 }
 283 
 284 /*
 285  * Linux defines NGROUPS_MAX to be 32, but on Solaris it is only 16. We employ
 286  * the terrible hack below so that tests may proceed, if only on DEBUG kernels.
 287  */
 288 long
 289 lx_setgroups(int ngroups, gid_t *grouplist)
 290 {
 291 #ifdef DEBUG
 292         if (ngroups > ngroups_max && ngroups <= LX_NGROUPS_MAX)
 293                 ngroups = ngroups_max;
 294 #endif /* DEBUG */
 295 
 296         return (setgroups(ngroups, grouplist));
 297 }