xref: /haiku/src/system/kernel/usergroup.cpp (revision 21258e2674226d6aa732321b6f8494841895af5f)
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 				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 
101 static status_t
102 common_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 
164 ssize_t
165 common_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 
203 static status_t
204 common_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 */
249 void
250 inherit_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 
265 status_t
266 update_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 
289 gid_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 
298 uid_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 
307 status_t
308 _kern_setregid(gid_t rgid, gid_t egid, bool setAllIfPrivileged)
309 {
310 	return common_setregid(rgid, egid, setAllIfPrivileged, true);
311 }
312 
313 
314 status_t
315 _kern_setreuid(uid_t ruid, uid_t euid, bool setAllIfPrivileged)
316 {
317 	return common_setreuid(ruid, euid, setAllIfPrivileged, true);
318 }
319 
320 
321 ssize_t
322 _kern_getgroups(int groupCount, gid_t* groupList)
323 {
324 	return common_getgroups(groupCount, groupList, true);
325 }
326 
327 
328 status_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 
338 gid_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 
347 uid_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 
356 status_t
357 _user_setregid(gid_t rgid, gid_t egid, bool setAllIfPrivileged)
358 {
359 	return common_setregid(rgid, egid, setAllIfPrivileged, false);
360 }
361 
362 
363 status_t
364 _user_setreuid(uid_t ruid, uid_t euid, bool setAllIfPrivileged)
365 {
366 	return common_setreuid(ruid, euid, setAllIfPrivileged, false);
367 }
368 
369 
370 ssize_t
371 _user_getgroups(int groupCount, gid_t* groupList)
372 {
373 	return common_getgroups(groupCount, groupList, false);
374 }
375 
376 
377 ssize_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