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