xref: /haiku/src/system/kernel/usergroup.cpp (revision 7bbfd0ff4956fd50e18cfb6f6f994f3f9446d77d)
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 
31 static bool
32 is_privileged(Team* team)
33 {
34 	// currently only the root user is privileged
35 	return team->effective_uid == 0;
36 }
37 
38 
39 static status_t
40 common_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 
102 static status_t
103 common_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 
165 ssize_t
166 common_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 
209 static status_t
210 common_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 */
255 void
256 inherit_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 
268 status_t
269 update_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 
292 gid_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 
301 uid_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 
310 status_t
311 _kern_setregid(gid_t rgid, gid_t egid, bool setAllIfPrivileged)
312 {
313 	return common_setregid(rgid, egid, setAllIfPrivileged, true);
314 }
315 
316 
317 status_t
318 _kern_setreuid(uid_t ruid, uid_t euid, bool setAllIfPrivileged)
319 {
320 	return common_setreuid(ruid, euid, setAllIfPrivileged, true);
321 }
322 
323 
324 ssize_t
325 _kern_getgroups(int groupCount, gid_t* groupList)
326 {
327 	return common_getgroups(groupCount, groupList, true);
328 }
329 
330 
331 status_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 
341 gid_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 
350 uid_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 
359 status_t
360 _user_setregid(gid_t rgid, gid_t egid, bool setAllIfPrivileged)
361 {
362 	return common_setregid(rgid, egid, setAllIfPrivileged, false);
363 }
364 
365 
366 status_t
367 _user_setreuid(uid_t ruid, uid_t euid, bool setAllIfPrivileged)
368 {
369 	return common_setreuid(ruid, euid, setAllIfPrivileged, false);
370 }
371 
372 
373 ssize_t
374 _user_getgroups(int groupCount, gid_t* groupList)
375 {
376 	return common_getgroups(groupCount, groupList, false);
377 }
378 
379 
380 ssize_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