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 <util/ThreadAutoLock.h>
23#include <vfs.h>
24
25#include <AutoDeleter.h>
26
27
28// #pragma mark - Implementation Private
29
30
31static bool
32is_privileged(Team* team)
33{
34	// currently only the root user is privileged
35	return team->effective_uid == 0;
36}
37
38
39static status_t
40common_setregid(gid_t rgid, gid_t egid, bool setAllIfPrivileged, bool kernel)
41{
42	Team* team = thread_get_current_thread()->team;
43
44	TeamLocker teamLocker(team);
45
46	bool privileged = kernel || is_privileged(team);
47
48	gid_t ssgid = team->saved_set_gid;
49
50	// real gid
51	if (rgid == (gid_t)-1) {
52		rgid = team->real_gid;
53	} else {
54		if (setAllIfPrivileged) {
55			// setgid() semantics: If privileged set both, real, effective and
56			// saved set-gid, otherwise set the effective gid.
57			if (privileged) {
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	team->real_gid = rgid;
95	team->effective_gid = egid;
96	team->saved_set_gid = ssgid;
97
98	return B_OK;
99}
100
101
102static status_t
103common_setreuid(uid_t ruid, uid_t euid, bool setAllIfPrivileged, bool kernel)
104{
105	Team* team = thread_get_current_thread()->team;
106
107	TeamLocker teamLocker(team);
108
109	bool privileged = kernel || is_privileged(team);
110
111	uid_t ssuid = team->saved_set_uid;
112
113	// real uid
114	if (ruid == (uid_t)-1) {
115		ruid = team->real_uid;
116	} else {
117		if (setAllIfPrivileged) {
118			// setuid() semantics: If privileged set both, real, effective and
119			// saved set-uid, otherwise set the effective uid.
120			if (privileged) {
121				team->saved_set_uid = ruid;
122				team->real_uid = ruid;
123				team->effective_uid = ruid;
124				return B_OK;
125			}
126
127			// not privileged -- set only the effective uid
128			euid = ruid;
129			ruid = team->real_uid;
130		} else {
131			// setreuid() semantics: set the real uid, if allowed to
132			// Note: We allow setting the real uid to the effective uid. This
133			// is unspecified by the specs, but is common practice.
134			if (!privileged && ruid != team->real_uid
135				&& ruid != team->effective_uid) {
136				return EPERM;
137			}
138
139			// Note: Also common practice is to set the saved set-uid when the
140			// real uid is set.
141			if (ruid != team->real_uid)
142				ssuid = ruid;
143		}
144	}
145
146	// effective uid
147	if (euid == (uid_t)-1) {
148		euid = team->effective_uid;
149	} else {
150		if (!privileged && euid != team->effective_uid
151			&& euid != team->real_uid && euid != team->saved_set_uid) {
152			return EPERM;
153		}
154	}
155
156	// Getting here means all checks were successful -- set the uids.
157	team->real_uid = ruid;
158	team->effective_uid = euid;
159	team->saved_set_uid = ssuid;
160
161	return B_OK;
162}
163
164
165ssize_t
166common_getgroups(int groupCount, gid_t* groupList, bool kernel)
167{
168	Team* team = thread_get_current_thread()->team;
169
170	TeamLocker teamLocker(team);
171
172	const gid_t* groups = NULL;
173	int actualCount = 0;
174
175	if (team->supplementary_groups != NULL) {
176		groups = team->supplementary_groups->groups;
177		actualCount = team->supplementary_groups->count;
178	}
179
180	// follow the specification and return always at least one group
181	if (actualCount == 0) {
182		groups = &team->effective_gid;
183		actualCount = 1;
184	}
185
186	// if groupCount 0 is supplied, we only return the number of groups
187	if (groupCount == 0)
188		return actualCount;
189
190	// check for sufficient space
191	if (groupCount < actualCount)
192		return B_BAD_VALUE;
193
194	// copy
195	if (kernel) {
196		memcpy(groupList, groups, actualCount * sizeof(gid_t));
197	} else {
198		if (!IS_USER_ADDRESS(groupList)
199			|| user_memcpy(groupList, groups,
200					actualCount * sizeof(gid_t)) != B_OK) {
201			return B_BAD_ADDRESS;
202		}
203	}
204
205	return actualCount;
206}
207
208
209static status_t
210common_setgroups(int groupCount, const gid_t* groupList, bool kernel)
211{
212	if (groupCount < 0 || groupCount > NGROUPS_MAX)
213		return B_BAD_VALUE;
214
215	BKernel::GroupsArray* newGroups = NULL;
216	if (groupCount > 0) {
217		newGroups = (BKernel::GroupsArray*)malloc(sizeof(BKernel::GroupsArray)
218			+ (sizeof(gid_t) * groupCount));
219		if (newGroups == NULL)
220			return B_NO_MEMORY;
221		new(newGroups) BKernel::GroupsArray;
222
223		if (kernel) {
224			memcpy(newGroups->groups, groupList, sizeof(gid_t) * groupCount);
225		} else {
226			if (!IS_USER_ADDRESS(groupList)
227				|| user_memcpy(newGroups->groups, groupList,
228					sizeof(gid_t) * groupCount) != B_OK) {
229				delete newGroups;
230				return B_BAD_ADDRESS;
231			}
232		}
233		newGroups->count = groupCount;
234	}
235
236	Team* team = thread_get_current_thread()->team;
237	TeamLocker teamLocker(team);
238
239	BReference<BKernel::GroupsArray> previous = team->supplementary_groups;
240		// so it will not be (potentially) destroyed until after we unlock
241	team->supplementary_groups.SetTo(newGroups, true);
242
243	teamLocker.Unlock();
244
245	return B_OK;
246}
247
248
249// #pragma mark - Kernel Private
250
251
252/*!	Copies the user and group information from \a parent to \a team.
253	The caller must hold the lock to both \a team and \a parent.
254*/
255void
256inherit_parent_user_and_group(Team* team, Team* parent)
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	team->supplementary_groups = parent->supplementary_groups;
265}
266
267
268status_t
269update_set_id_user_and_group(Team* team, const char* file)
270{
271	struct stat st;
272	status_t status = vfs_read_stat(-1, file, true, &st, false);
273	if (status != B_OK)
274		return status;
275
276	TeamLocker teamLocker(team);
277
278	if ((st.st_mode & S_ISUID) != 0) {
279		team->saved_set_uid = st.st_uid;
280		team->effective_uid = st.st_uid;
281	}
282
283	if ((st.st_mode & S_ISGID) != 0) {
284		team->saved_set_gid = st.st_gid;
285		team->effective_gid = st.st_gid;
286	}
287
288	return B_OK;
289}
290
291
292gid_t
293_kern_getgid(bool effective)
294{
295	Team* team = thread_get_current_thread()->team;
296
297	return effective ? team->effective_gid : team->real_gid;
298}
299
300
301uid_t
302_kern_getuid(bool effective)
303{
304	Team* team = thread_get_current_thread()->team;
305
306	return effective ? team->effective_uid : team->real_uid;
307}
308
309
310status_t
311_kern_setregid(gid_t rgid, gid_t egid, bool setAllIfPrivileged)
312{
313	return common_setregid(rgid, egid, setAllIfPrivileged, true);
314}
315
316
317status_t
318_kern_setreuid(uid_t ruid, uid_t euid, bool setAllIfPrivileged)
319{
320	return common_setreuid(ruid, euid, setAllIfPrivileged, true);
321}
322
323
324ssize_t
325_kern_getgroups(int groupCount, gid_t* groupList)
326{
327	return common_getgroups(groupCount, groupList, true);
328}
329
330
331status_t
332_kern_setgroups(int groupCount, const gid_t* groupList)
333{
334	return common_setgroups(groupCount, groupList, true);
335}
336
337
338// #pragma mark - Syscalls
339
340
341gid_t
342_user_getgid(bool effective)
343{
344	Team* team = thread_get_current_thread()->team;
345
346	return effective ? team->effective_gid : team->real_gid;
347}
348
349
350uid_t
351_user_getuid(bool effective)
352{
353	Team* team = thread_get_current_thread()->team;
354
355	return effective ? team->effective_uid : team->real_uid;
356}
357
358
359status_t
360_user_setregid(gid_t rgid, gid_t egid, bool setAllIfPrivileged)
361{
362	return common_setregid(rgid, egid, setAllIfPrivileged, false);
363}
364
365
366status_t
367_user_setreuid(uid_t ruid, uid_t euid, bool setAllIfPrivileged)
368{
369	return common_setreuid(ruid, euid, setAllIfPrivileged, false);
370}
371
372
373ssize_t
374_user_getgroups(int groupCount, gid_t* groupList)
375{
376	return common_getgroups(groupCount, groupList, false);
377}
378
379
380ssize_t
381_user_setgroups(int groupCount, const gid_t* groupList)
382{
383	// check privilege
384	Team* team = thread_get_current_thread()->team;
385	if (!is_privileged(team))
386		return EPERM;
387
388	return common_setgroups(groupCount, groupList, false);
389}
390