1 /* 2 * Copyright 2008-2011, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include <usergroup.h> 8 9 #include <errno.h> 10 #include <limits.h> 11 #include <sys/stat.h> 12 13 #include <new> 14 15 #include <heap.h> 16 #include <kernel.h> 17 #include <syscalls.h> 18 #include <team.h> 19 #include <thread.h> 20 #include <thread_types.h> 21 #include <util/AutoLock.h> 22 #include <vfs.h> 23 24 #include <AutoDeleter.h> 25 26 27 // #pragma mark - Implementation Private 28 29 30 static bool 31 is_privileged(Team* team) 32 { 33 // currently only the root user is privileged 34 return team->effective_uid == 0; 35 } 36 37 38 static status_t 39 common_setregid(gid_t rgid, gid_t egid, bool setAllIfPrivileged, bool kernel) 40 { 41 Team* team = thread_get_current_thread()->team; 42 43 TeamLocker teamLocker(team); 44 45 bool privileged = kernel || is_privileged(team); 46 47 gid_t ssgid = team->saved_set_gid; 48 49 // real gid 50 if (rgid == (gid_t)-1) { 51 rgid = team->real_gid; 52 } else { 53 if (setAllIfPrivileged) { 54 // setgid() semantics: If privileged set both, real, effective and 55 // saved set-gid, otherwise set the effective gid. 56 if (privileged) { 57 InterruptsSpinLocker schedulerLocker(gSchedulerLock); 58 team->saved_set_gid = rgid; 59 team->real_gid = rgid; 60 team->effective_gid = rgid; 61 return B_OK; 62 } 63 64 // not privileged -- set only the effective gid 65 egid = rgid; 66 rgid = team->real_gid; 67 } else { 68 // setregid() semantics: set the real gid, if allowed to 69 // Note: We allow setting the real gid to the effective gid. This 70 // is unspecified by the specs, but is common practice. 71 if (!privileged && rgid != team->real_gid 72 && rgid != team->effective_gid) { 73 return EPERM; 74 } 75 76 // Note: Also common practice is to set the saved set-gid when the 77 // real gid is set. 78 if (rgid != team->real_gid) 79 ssgid = rgid; 80 } 81 } 82 83 // effective gid 84 if (egid == (gid_t)-1) { 85 egid = team->effective_gid; 86 } else { 87 if (!privileged && egid != team->effective_gid 88 && egid != team->real_gid && egid != team->saved_set_gid) { 89 return EPERM; 90 } 91 } 92 93 // Getting here means all checks were successful -- set the gids. 94 InterruptsSpinLocker schedulerLocker(gSchedulerLock); 95 team->real_gid = rgid; 96 team->effective_gid = egid; 97 team->saved_set_gid = ssgid; 98 99 return B_OK; 100 } 101 102 103 static status_t 104 common_setreuid(uid_t ruid, uid_t euid, bool setAllIfPrivileged, bool kernel) 105 { 106 Team* team = thread_get_current_thread()->team; 107 108 TeamLocker teamLocker(team); 109 110 bool privileged = kernel || is_privileged(team); 111 112 uid_t ssuid = team->saved_set_uid; 113 114 // real uid 115 if (ruid == (uid_t)-1) { 116 ruid = team->real_uid; 117 } else { 118 if (setAllIfPrivileged) { 119 // setuid() semantics: If privileged set both, real, effective and 120 // saved set-uid, otherwise set the effective uid. 121 if (privileged) { 122 InterruptsSpinLocker schedulerLocker(gSchedulerLock); 123 team->saved_set_uid = ruid; 124 team->real_uid = ruid; 125 team->effective_uid = ruid; 126 return B_OK; 127 } 128 129 // not privileged -- set only the effective uid 130 euid = ruid; 131 ruid = team->real_uid; 132 } else { 133 // setreuid() semantics: set the real uid, if allowed to 134 // Note: We allow setting the real uid to the effective uid. This 135 // is unspecified by the specs, but is common practice. 136 if (!privileged && ruid != team->real_uid 137 && ruid != team->effective_uid) { 138 return EPERM; 139 } 140 141 // Note: Also common practice is to set the saved set-uid when the 142 // real uid is set. 143 if (ruid != team->real_uid) 144 ssuid = ruid; 145 } 146 } 147 148 // effective uid 149 if (euid == (uid_t)-1) { 150 euid = team->effective_uid; 151 } else { 152 if (!privileged && euid != team->effective_uid 153 && euid != team->real_uid && euid != team->saved_set_uid) { 154 return EPERM; 155 } 156 } 157 158 // Getting here means all checks were successful -- set the uids. 159 InterruptsSpinLocker schedulerLocker(gSchedulerLock); 160 team->real_uid = ruid; 161 team->effective_uid = euid; 162 team->saved_set_uid = ssuid; 163 164 return B_OK; 165 } 166 167 168 ssize_t 169 common_getgroups(int groupCount, gid_t* groupList, bool kernel) 170 { 171 Team* team = thread_get_current_thread()->team; 172 173 TeamLocker teamLocker(team); 174 175 const gid_t* groups = team->supplementary_groups; 176 int actualCount = team->supplementary_group_count; 177 178 // follow the specification and return always at least one group 179 if (actualCount == 0) { 180 groups = &team->effective_gid; 181 actualCount = 1; 182 } 183 184 // if groupCount 0 is supplied, we only return the number of groups 185 if (groupCount == 0) 186 return actualCount; 187 188 // check for sufficient space 189 if (groupCount < actualCount) 190 return B_BAD_VALUE; 191 192 // copy 193 if (kernel) { 194 memcpy(groupList, groups, actualCount * sizeof(gid_t)); 195 } else { 196 if (!IS_USER_ADDRESS(groupList) 197 || user_memcpy(groupList, groups, 198 actualCount * sizeof(gid_t)) != B_OK) { 199 return B_BAD_ADDRESS; 200 } 201 } 202 203 return actualCount; 204 } 205 206 207 static status_t 208 common_setgroups(int groupCount, const gid_t* groupList, bool kernel) 209 { 210 if (groupCount < 0 || groupCount > NGROUPS_MAX) 211 return B_BAD_VALUE; 212 213 gid_t* newGroups = NULL; 214 if (groupCount > 0) { 215 newGroups = (gid_t*)malloc_referenced(sizeof(gid_t) * groupCount); 216 if (newGroups == NULL) 217 return B_NO_MEMORY; 218 219 if (kernel) { 220 memcpy(newGroups, groupList, sizeof(gid_t) * groupCount); 221 } else { 222 if (!IS_USER_ADDRESS(groupList) 223 || user_memcpy(newGroups, groupList, 224 sizeof(gid_t) * groupCount) != B_OK) { 225 free(newGroups); 226 return B_BAD_ADDRESS; 227 } 228 } 229 } 230 231 Team* team = thread_get_current_thread()->team; 232 233 TeamLocker teamLocker(team); 234 235 gid_t* toFree = team->supplementary_groups; 236 team->supplementary_groups = newGroups; 237 team->supplementary_group_count = groupCount; 238 239 teamLocker.Unlock(); 240 241 malloc_referenced_release(toFree); 242 243 return B_OK; 244 } 245 246 247 // #pragma mark - Kernel Private 248 249 250 /*! Copies the user and group information from \a parent to \a team. 251 The caller must hold the lock to both \a team and \a parent. 252 */ 253 void 254 inherit_parent_user_and_group(Team* team, Team* parent) 255 { 256 InterruptsSpinLocker schedulerLocker(gSchedulerLock); 257 258 team->saved_set_uid = parent->saved_set_uid; 259 team->real_uid = parent->real_uid; 260 team->effective_uid = parent->effective_uid; 261 team->saved_set_gid = parent->saved_set_gid; 262 team->real_gid = parent->real_gid; 263 team->effective_gid = parent->effective_gid; 264 265 schedulerLocker.Unlock(); 266 267 malloc_referenced_acquire(parent->supplementary_groups); 268 team->supplementary_groups = parent->supplementary_groups; 269 team->supplementary_group_count = parent->supplementary_group_count; 270 } 271 272 273 status_t 274 update_set_id_user_and_group(Team* team, const char* file) 275 { 276 struct stat st; 277 status_t status = vfs_read_stat(-1, file, true, &st, false); 278 if (status != B_OK) 279 return status; 280 281 TeamLocker teamLocker(team); 282 InterruptsSpinLocker schedulerLocker(gSchedulerLock); 283 284 if ((st.st_mode & S_ISUID) != 0) { 285 team->saved_set_uid = st.st_uid; 286 team->effective_uid = st.st_uid; 287 } 288 289 if ((st.st_mode & S_ISGID) != 0) { 290 team->saved_set_gid = st.st_gid; 291 team->effective_gid = st.st_gid; 292 } 293 294 return B_OK; 295 } 296 297 298 gid_t 299 _kern_getgid(bool effective) 300 { 301 Team* team = thread_get_current_thread()->team; 302 303 InterruptsSpinLocker schedulerLocker(gSchedulerLock); 304 305 return effective ? team->effective_gid : team->real_gid; 306 } 307 308 309 uid_t 310 _kern_getuid(bool effective) 311 { 312 Team* team = thread_get_current_thread()->team; 313 314 InterruptsSpinLocker schedulerLocker(gSchedulerLock); 315 316 return effective ? team->effective_uid : team->real_uid; 317 } 318 319 320 status_t 321 _kern_setregid(gid_t rgid, gid_t egid, bool setAllIfPrivileged) 322 { 323 return common_setregid(rgid, egid, setAllIfPrivileged, true); 324 } 325 326 327 status_t 328 _kern_setreuid(uid_t ruid, uid_t euid, bool setAllIfPrivileged) 329 { 330 return common_setreuid(ruid, euid, setAllIfPrivileged, true); 331 } 332 333 334 ssize_t 335 _kern_getgroups(int groupCount, gid_t* groupList) 336 { 337 return common_getgroups(groupCount, groupList, true); 338 } 339 340 341 status_t 342 _kern_setgroups(int groupCount, const gid_t* groupList) 343 { 344 return common_setgroups(groupCount, groupList, true); 345 } 346 347 348 // #pragma mark - Syscalls 349 350 351 gid_t 352 _user_getgid(bool effective) 353 { 354 Team* team = thread_get_current_thread()->team; 355 356 TeamLocker teamLocker(team); 357 358 return effective ? team->effective_gid : team->real_gid; 359 } 360 361 362 uid_t 363 _user_getuid(bool effective) 364 { 365 Team* team = thread_get_current_thread()->team; 366 367 TeamLocker teamLocker(team); 368 369 return effective ? team->effective_uid : team->real_uid; 370 } 371 372 373 status_t 374 _user_setregid(gid_t rgid, gid_t egid, bool setAllIfPrivileged) 375 { 376 return common_setregid(rgid, egid, setAllIfPrivileged, false); 377 } 378 379 380 status_t 381 _user_setreuid(uid_t ruid, uid_t euid, bool setAllIfPrivileged) 382 { 383 return common_setreuid(ruid, euid, setAllIfPrivileged, false); 384 } 385 386 387 ssize_t 388 _user_getgroups(int groupCount, gid_t* groupList) 389 { 390 return common_getgroups(groupCount, groupList, false); 391 } 392 393 394 ssize_t 395 _user_setgroups(int groupCount, const gid_t* groupList) 396 { 397 // check privilege 398 { 399 Team* team = thread_get_current_thread()->team; 400 TeamLocker teamLocker(team); 401 if (!is_privileged(team)) 402 return EPERM; 403 } 404 405 return common_setgroups(groupCount, groupList, false); 406 } 407