xref: /haiku/src/system/kernel/usergroup.cpp (revision 820dca4df6c7bf955c46e8f6521b9408f50b2900)
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