/src/samba/source3/smbd/sec_ctx.c
Line | Count | Source |
1 | | /* |
2 | | Unix SMB/CIFS implementation. |
3 | | uid/user handling |
4 | | Copyright (C) Tim Potter 2000 |
5 | | |
6 | | This program is free software; you can redistribute it and/or modify |
7 | | it under the terms of the GNU General Public License as published by |
8 | | the Free Software Foundation; either version 3 of the License, or |
9 | | (at your option) any later version. |
10 | | |
11 | | This program is distributed in the hope that it will be useful, |
12 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | GNU General Public License for more details. |
15 | | |
16 | | You should have received a copy of the GNU General Public License |
17 | | along with this program. If not, see <http://www.gnu.org/licenses/>. |
18 | | */ |
19 | | |
20 | | #include "includes.h" |
21 | | #include "system/passwd.h" |
22 | | #include "smbd/smbd.h" |
23 | | #include "smbd/globals.h" |
24 | | #include "libcli/security/security_token.h" |
25 | | #include "auth.h" |
26 | | #include "smbprofile.h" |
27 | | #include "../lib/util/setid.h" |
28 | | |
29 | | extern struct current_user current_user; |
30 | | |
31 | | /**************************************************************************** |
32 | | Are two UNIX tokens equal ? |
33 | | ****************************************************************************/ |
34 | | |
35 | | bool unix_token_equal(const struct security_unix_token *t1, const struct security_unix_token *t2) |
36 | 0 | { |
37 | 0 | if (t1->uid != t2->uid || t1->gid != t2->gid || |
38 | 0 | t1->ngroups != t2->ngroups) { |
39 | 0 | return false; |
40 | 0 | } |
41 | 0 | if (memcmp(t1->groups, t2->groups, |
42 | 0 | t1->ngroups*sizeof(gid_t)) != 0) { |
43 | 0 | return false; |
44 | 0 | } |
45 | 0 | return true; |
46 | 0 | } |
47 | | |
48 | | /**************************************************************************** |
49 | | Become the specified uid. |
50 | | ****************************************************************************/ |
51 | | |
52 | | static bool become_uid(uid_t uid) |
53 | 0 | { |
54 | | /* Check for dodgy uid values */ |
55 | |
|
56 | 0 | if (uid == (uid_t)-1 || |
57 | 0 | ((sizeof(uid_t) == 2) && (uid == (uid_t)65535))) { |
58 | 0 | if (!become_uid_done) { |
59 | 0 | DEBUG(1,("WARNING: using uid %d is a security risk\n", |
60 | 0 | (int)uid)); |
61 | 0 | become_uid_done = true; |
62 | 0 | } |
63 | 0 | } |
64 | | |
65 | | /* Set effective user id */ |
66 | |
|
67 | 0 | set_effective_uid(uid); |
68 | |
|
69 | 0 | return True; |
70 | 0 | } |
71 | | |
72 | | /**************************************************************************** |
73 | | Become the specified gid. |
74 | | ****************************************************************************/ |
75 | | |
76 | | static bool become_gid(gid_t gid) |
77 | 0 | { |
78 | | /* Check for dodgy gid values */ |
79 | |
|
80 | 0 | if (gid == (gid_t)-1 || ((sizeof(gid_t) == 2) && |
81 | 0 | (gid == (gid_t)65535))) { |
82 | 0 | if (!become_gid_done) { |
83 | 0 | DEBUG(1,("WARNING: using gid %d is a security risk\n", |
84 | 0 | (int)gid)); |
85 | 0 | become_gid_done = true; |
86 | 0 | } |
87 | 0 | } |
88 | | |
89 | | /* Set effective group id */ |
90 | |
|
91 | 0 | set_effective_gid(gid); |
92 | 0 | return True; |
93 | 0 | } |
94 | | |
95 | | /**************************************************************************** |
96 | | Drop back to root privileges in order to change to another user. |
97 | | ****************************************************************************/ |
98 | | |
99 | | static void gain_root(void) |
100 | 0 | { |
101 | 0 | if (non_root_mode()) { |
102 | 0 | return; |
103 | 0 | } |
104 | | |
105 | 0 | if (geteuid() != 0) { |
106 | 0 | set_effective_uid(0); |
107 | |
|
108 | 0 | if (geteuid() != 0) { |
109 | 0 | DEBUG(0, |
110 | 0 | ("Warning: You appear to have a trapdoor " |
111 | 0 | "uid system\n")); |
112 | 0 | } |
113 | 0 | } |
114 | |
|
115 | 0 | if (getegid() != 0) { |
116 | 0 | set_effective_gid(0); |
117 | |
|
118 | 0 | if (getegid() != 0) { |
119 | 0 | DEBUG(0, |
120 | 0 | ("Warning: You appear to have a trapdoor " |
121 | 0 | "gid system\n")); |
122 | 0 | } |
123 | 0 | } |
124 | 0 | } |
125 | | |
126 | | /**************************************************************************** |
127 | | Get the list of current groups. |
128 | | ****************************************************************************/ |
129 | | |
130 | | static int get_current_groups(gid_t gid, uint32_t *p_ngroups, gid_t **p_groups) |
131 | 0 | { |
132 | 0 | int i; |
133 | 0 | int ngroups; |
134 | 0 | gid_t *groups = NULL; |
135 | |
|
136 | 0 | (*p_ngroups) = 0; |
137 | 0 | (*p_groups) = NULL; |
138 | | |
139 | | /* this looks a little strange, but is needed to cope with |
140 | | systems that put the current egid in the group list |
141 | | returned from getgroups() (tridge) */ |
142 | 0 | save_re_gid(); |
143 | 0 | set_effective_gid(gid); |
144 | 0 | samba_setgid(gid); |
145 | |
|
146 | 0 | ngroups = sys_getgroups(0, NULL); |
147 | 0 | if (ngroups <= 0) { |
148 | 0 | goto fail; |
149 | 0 | } |
150 | | |
151 | 0 | if((groups = SMB_MALLOC_ARRAY(gid_t, ngroups+1)) == NULL) { |
152 | 0 | DEBUG(0,("setup_groups malloc fail !\n")); |
153 | 0 | goto fail; |
154 | 0 | } |
155 | | |
156 | 0 | if ((ngroups = sys_getgroups(ngroups,groups)) == -1) { |
157 | 0 | goto fail; |
158 | 0 | } |
159 | | |
160 | 0 | restore_re_gid(); |
161 | |
|
162 | 0 | (*p_ngroups) = ngroups; |
163 | 0 | (*p_groups) = groups; |
164 | |
|
165 | 0 | DEBUG( 4, ( "get_current_groups: user is in %u groups: ", ngroups)); |
166 | 0 | for (i = 0; i < ngroups; i++ ) { |
167 | 0 | DEBUG( 4, ( "%s%d", (i ? ", " : ""), (int)groups[i] ) ); |
168 | 0 | } |
169 | 0 | DEBUG( 4, ( "\n" ) ); |
170 | |
|
171 | 0 | return ngroups; |
172 | | |
173 | 0 | fail: |
174 | 0 | SAFE_FREE(groups); |
175 | 0 | restore_re_gid(); |
176 | 0 | return -1; |
177 | 0 | } |
178 | | |
179 | | /**************************************************************************** |
180 | | Create a new security context on the stack. It is the same as the old |
181 | | one. User changes are done using the set_sec_ctx() function. |
182 | | ****************************************************************************/ |
183 | | |
184 | | bool push_sec_ctx(void) |
185 | 0 | { |
186 | 0 | struct sec_ctx *ctx_p; |
187 | |
|
188 | 0 | START_PROFILE(push_sec_ctx); |
189 | | |
190 | | /* Check we don't overflow our stack */ |
191 | |
|
192 | 0 | if (sec_ctx_stack_ndx == MAX_SEC_CTX_DEPTH) { |
193 | 0 | DEBUG(0, ("Security context stack overflow!\n")); |
194 | 0 | smb_panic("Security context stack overflow!"); |
195 | 0 | } |
196 | | |
197 | | /* Store previous user context */ |
198 | | |
199 | 0 | sec_ctx_stack_ndx++; |
200 | |
|
201 | 0 | ctx_p = &sec_ctx_stack[sec_ctx_stack_ndx]; |
202 | |
|
203 | 0 | ctx_p->ut.uid = geteuid(); |
204 | 0 | ctx_p->ut.gid = getegid(); |
205 | |
|
206 | 0 | DEBUG(4, ("push_sec_ctx(%u, %u) : sec_ctx_stack_ndx = %d\n", |
207 | 0 | (unsigned int)ctx_p->ut.uid, (unsigned int)ctx_p->ut.gid, sec_ctx_stack_ndx )); |
208 | |
|
209 | 0 | ctx_p->token = security_token_duplicate(NULL, |
210 | 0 | sec_ctx_stack[sec_ctx_stack_ndx-1].token); |
211 | |
|
212 | 0 | ctx_p->ut.ngroups = sys_getgroups(0, NULL); |
213 | |
|
214 | 0 | if (ctx_p->ut.ngroups != 0) { |
215 | 0 | if (!(ctx_p->ut.groups = SMB_MALLOC_ARRAY(gid_t, ctx_p->ut.ngroups))) { |
216 | 0 | DEBUG(0, ("Out of memory in push_sec_ctx()\n")); |
217 | 0 | TALLOC_FREE(ctx_p->token); |
218 | 0 | return False; |
219 | 0 | } |
220 | | |
221 | 0 | sys_getgroups(ctx_p->ut.ngroups, ctx_p->ut.groups); |
222 | 0 | } else { |
223 | 0 | ctx_p->ut.groups = NULL; |
224 | 0 | } |
225 | | |
226 | 0 | END_PROFILE(push_sec_ctx); |
227 | |
|
228 | 0 | return True; |
229 | 0 | } |
230 | | |
231 | | #ifndef HAVE_DARWIN_INITGROUPS |
232 | | /**************************************************************************** |
233 | | Become the specified uid and gid. |
234 | | ****************************************************************************/ |
235 | | |
236 | | static bool become_id(uid_t uid, gid_t gid) |
237 | 0 | { |
238 | 0 | return become_gid(gid) && become_uid(uid); |
239 | 0 | } |
240 | | |
241 | | /**************************************************************************** |
242 | | Change UNIX security context. Calls panic if not successful so no return value. |
243 | | ****************************************************************************/ |
244 | | /* Normal credential switch path. */ |
245 | | |
246 | | static void set_unix_security_ctx(uid_t uid, gid_t gid, int ngroups, gid_t *groups) |
247 | 0 | { |
248 | | /* Start context switch */ |
249 | 0 | gain_root(); |
250 | 0 | #ifdef HAVE_SETGROUPS |
251 | 0 | if (sys_setgroups(gid, ngroups, groups) != 0 && !non_root_mode()) { |
252 | 0 | smb_panic("sys_setgroups failed"); |
253 | 0 | } |
254 | 0 | #endif |
255 | 0 | become_id(uid, gid); |
256 | | /* end context switch */ |
257 | 0 | } |
258 | | |
259 | | #else /* HAVE_DARWIN_INITGROUPS */ |
260 | | |
261 | | /* The Darwin groups implementation is a little unusual. The list of |
262 | | * groups in the kernel credential is not exhaustive, but more like |
263 | | * a cache. The full group list is held in userspace and checked |
264 | | * dynamically. |
265 | | * |
266 | | * This is an optional mechanism, and setgroups(2) opts out |
267 | | * of it. That is, if you call setgroups, then the list of groups you |
268 | | * set are the only groups that are ever checked. This is not what we |
269 | | * want. We want to opt in to the dynamic resolution mechanism, so we |
270 | | * need to specify the uid of the user whose group list (cache) we are |
271 | | * setting. |
272 | | * |
273 | | * The Darwin rules are: |
274 | | * 1. Thou shalt setegid, initgroups and seteuid IN THAT ORDER |
275 | | * 2. Thou shalt not pass more that NGROUPS_MAX to initgroups |
276 | | * 3. Thou shalt leave the first entry in the groups list well alone |
277 | | */ |
278 | | |
279 | | #include <sys/syscall.h> |
280 | | |
281 | | static void set_unix_security_ctx(uid_t uid, gid_t gid, int ngroups, gid_t *groups) |
282 | | { |
283 | | int max = NGROUPS_MAX; |
284 | | |
285 | | /* Start context switch */ |
286 | | gain_root(); |
287 | | |
288 | | become_gid(gid); |
289 | | |
290 | | |
291 | | if (syscall(SYS_initgroups, (ngroups > max) ? max : ngroups, |
292 | | groups, uid) == -1 && !non_root_mode()) { |
293 | | DEBUG(0, ("WARNING: failed to set group list " |
294 | | "(%d groups) for UID %d: %s\n", |
295 | | ngroups, uid, strerror(errno))); |
296 | | smb_panic("sys_setgroups failed"); |
297 | | } |
298 | | |
299 | | become_uid(uid); |
300 | | /* end context switch */ |
301 | | } |
302 | | |
303 | | #endif /* HAVE_DARWIN_INITGROUPS */ |
304 | | |
305 | | /**************************************************************************** |
306 | | Set the current security context to a given user. |
307 | | ****************************************************************************/ |
308 | | |
309 | | static void set_sec_ctx_internal(uid_t uid, gid_t gid, |
310 | | int ngroups, gid_t *groups, |
311 | | const struct security_token *token) |
312 | 0 | { |
313 | 0 | struct sec_ctx *ctx_p = &sec_ctx_stack[sec_ctx_stack_ndx]; |
314 | | |
315 | | /* Set the security context */ |
316 | |
|
317 | 0 | DEBUG(4, ("setting sec ctx (%u, %u) - sec_ctx_stack_ndx = %d\n", |
318 | 0 | (unsigned int)uid, (unsigned int)gid, sec_ctx_stack_ndx)); |
319 | |
|
320 | 0 | security_token_debug(DBGC_CLASS, 5, token); |
321 | 0 | debug_unix_user_token(DBGC_CLASS, 5, uid, gid, ngroups, groups); |
322 | | |
323 | | /* Change uid, gid and supplementary group list. */ |
324 | 0 | set_unix_security_ctx(uid, gid, ngroups, groups); |
325 | |
|
326 | 0 | ctx_p->ut.ngroups = ngroups; |
327 | |
|
328 | 0 | SAFE_FREE(ctx_p->ut.groups); |
329 | 0 | if (token && (token == ctx_p->token)) { |
330 | 0 | smb_panic("DUPLICATE_TOKEN"); |
331 | 0 | } |
332 | | |
333 | 0 | TALLOC_FREE(ctx_p->token); |
334 | |
|
335 | 0 | if (ngroups) { |
336 | 0 | ctx_p->ut.groups = (gid_t *)smb_xmemdup(groups, |
337 | 0 | sizeof(gid_t) * ngroups); |
338 | 0 | } else { |
339 | 0 | ctx_p->ut.groups = NULL; |
340 | 0 | } |
341 | |
|
342 | 0 | if (token) { |
343 | 0 | ctx_p->token = security_token_duplicate(NULL, token); |
344 | 0 | if (!ctx_p->token) { |
345 | 0 | smb_panic("security_token_duplicate failed"); |
346 | 0 | } |
347 | 0 | } else { |
348 | 0 | ctx_p->token = NULL; |
349 | 0 | } |
350 | | |
351 | 0 | ctx_p->ut.uid = uid; |
352 | 0 | ctx_p->ut.gid = gid; |
353 | | |
354 | | /* Update current_user stuff */ |
355 | |
|
356 | 0 | current_user.ut.uid = uid; |
357 | 0 | current_user.ut.gid = gid; |
358 | 0 | current_user.ut.ngroups = ngroups; |
359 | 0 | current_user.ut.groups = groups; |
360 | 0 | current_user.nt_user_token = ctx_p->token; |
361 | | |
362 | | /* |
363 | | * Delete any ChDir cache. We can't assume |
364 | | * the new uid has access to current working |
365 | | * directory. |
366 | | * BUG: https://bugzilla.samba.org/show_bug.cgi?id=14682 |
367 | | */ |
368 | 0 | SAFE_FREE(LastDir); |
369 | 0 | } |
370 | | |
371 | | void set_sec_ctx(uid_t uid, gid_t gid, int ngroups, gid_t *groups, const struct security_token *token) |
372 | 0 | { |
373 | 0 | START_PROFILE(set_sec_ctx); |
374 | 0 | set_sec_ctx_internal(uid, gid, ngroups, groups, token); |
375 | 0 | END_PROFILE(set_sec_ctx); |
376 | 0 | } |
377 | | |
378 | | /**************************************************************************** |
379 | | Become root context. |
380 | | ****************************************************************************/ |
381 | | |
382 | | void set_root_sec_ctx(void) |
383 | 0 | { |
384 | | /* May need to worry about supplementary groups at some stage */ |
385 | |
|
386 | 0 | START_PROFILE(set_root_sec_ctx); |
387 | 0 | set_sec_ctx_internal(0, 0, 0, NULL, NULL); |
388 | 0 | END_PROFILE(set_root_sec_ctx); |
389 | 0 | } |
390 | | |
391 | | /**************************************************************************** |
392 | | Pop a security context from the stack. |
393 | | ****************************************************************************/ |
394 | | |
395 | | bool pop_sec_ctx(void) |
396 | 0 | { |
397 | 0 | struct sec_ctx *ctx_p; |
398 | 0 | struct sec_ctx *prev_ctx_p; |
399 | |
|
400 | 0 | START_PROFILE(pop_sec_ctx); |
401 | | |
402 | | /* Check for stack underflow */ |
403 | |
|
404 | 0 | if (sec_ctx_stack_ndx == 0) { |
405 | 0 | DEBUG(0, ("Security context stack underflow!\n")); |
406 | 0 | smb_panic("Security context stack underflow!"); |
407 | 0 | } |
408 | | |
409 | 0 | ctx_p = &sec_ctx_stack[sec_ctx_stack_ndx]; |
410 | | |
411 | | /* Clear previous user info */ |
412 | |
|
413 | 0 | ctx_p->ut.uid = (uid_t)-1; |
414 | 0 | ctx_p->ut.gid = (gid_t)-1; |
415 | |
|
416 | 0 | SAFE_FREE(ctx_p->ut.groups); |
417 | 0 | ctx_p->ut.ngroups = 0; |
418 | |
|
419 | 0 | TALLOC_FREE(ctx_p->token); |
420 | | |
421 | | /* Pop back previous user */ |
422 | |
|
423 | 0 | sec_ctx_stack_ndx--; |
424 | |
|
425 | 0 | prev_ctx_p = &sec_ctx_stack[sec_ctx_stack_ndx]; |
426 | | |
427 | | /* Change uid, gid and supplementary group list. */ |
428 | 0 | set_unix_security_ctx(prev_ctx_p->ut.uid, |
429 | 0 | prev_ctx_p->ut.gid, |
430 | 0 | prev_ctx_p->ut.ngroups, |
431 | 0 | prev_ctx_p->ut.groups); |
432 | | |
433 | | /* Update current_user stuff */ |
434 | |
|
435 | 0 | current_user.ut.uid = prev_ctx_p->ut.uid; |
436 | 0 | current_user.ut.gid = prev_ctx_p->ut.gid; |
437 | 0 | current_user.ut.ngroups = prev_ctx_p->ut.ngroups; |
438 | 0 | current_user.ut.groups = prev_ctx_p->ut.groups; |
439 | 0 | current_user.nt_user_token = prev_ctx_p->token; |
440 | |
|
441 | 0 | END_PROFILE(pop_sec_ctx); |
442 | |
|
443 | 0 | DEBUG(4, ("pop_sec_ctx (%u, %u) - sec_ctx_stack_ndx = %d\n", |
444 | 0 | (unsigned int)geteuid(), (unsigned int)getegid(), sec_ctx_stack_ndx)); |
445 | |
|
446 | 0 | return True; |
447 | 0 | } |
448 | | |
449 | | /* Initialise the security context system */ |
450 | | |
451 | | void init_sec_ctx(void) |
452 | 0 | { |
453 | 0 | int i; |
454 | 0 | struct sec_ctx *ctx_p; |
455 | | |
456 | | /* Initialise security context stack */ |
457 | |
|
458 | 0 | memset(sec_ctx_stack, 0, sizeof(struct sec_ctx) * MAX_SEC_CTX_DEPTH); |
459 | |
|
460 | 0 | for (i = 0; i < MAX_SEC_CTX_DEPTH; i++) { |
461 | 0 | sec_ctx_stack[i].ut.uid = (uid_t)-1; |
462 | 0 | sec_ctx_stack[i].ut.gid = (gid_t)-1; |
463 | 0 | } |
464 | | |
465 | | /* Initialise first level of stack. It is the current context */ |
466 | 0 | ctx_p = &sec_ctx_stack[0]; |
467 | |
|
468 | 0 | ctx_p->ut.uid = geteuid(); |
469 | 0 | ctx_p->ut.gid = getegid(); |
470 | |
|
471 | 0 | get_current_groups(ctx_p->ut.gid, &ctx_p->ut.ngroups, &ctx_p->ut.groups); |
472 | |
|
473 | 0 | ctx_p->token = NULL; /* Maps to guest user. */ |
474 | | |
475 | | /* Initialise current_user global */ |
476 | |
|
477 | 0 | current_user.ut.uid = ctx_p->ut.uid; |
478 | 0 | current_user.ut.gid = ctx_p->ut.gid; |
479 | 0 | current_user.ut.ngroups = ctx_p->ut.ngroups; |
480 | 0 | current_user.ut.groups = ctx_p->ut.groups; |
481 | | |
482 | | /* The conn and vuid are usually taken care of by other modules. |
483 | | We initialise them here. */ |
484 | |
|
485 | 0 | current_user.conn = NULL; |
486 | 0 | current_user.vuid = UID_FIELD_INVALID; |
487 | 0 | current_user.nt_user_token = NULL; |
488 | 0 | } |
489 | | |
490 | | /************************************************************* |
491 | | Called when we're inside a become_root() temporary escalation |
492 | | of privileges and the nt_user_token is NULL. Return the last |
493 | | active token on the context stack. We know there is at least |
494 | | one valid non-NULL token on the stack so panic if we underflow. |
495 | | *************************************************************/ |
496 | | |
497 | | const struct security_token *sec_ctx_active_token(void) |
498 | 0 | { |
499 | 0 | int stack_index = sec_ctx_stack_ndx; |
500 | 0 | struct sec_ctx *ctx_p = &sec_ctx_stack[stack_index]; |
501 | |
|
502 | 0 | while (ctx_p->token == NULL) { |
503 | 0 | stack_index--; |
504 | 0 | if (stack_index < 0) { |
505 | 0 | DEBUG(0, ("Security context active token " |
506 | 0 | "stack underflow!\n")); |
507 | 0 | smb_panic("Security context active token " |
508 | 0 | "stack underflow!"); |
509 | 0 | } |
510 | 0 | ctx_p = &sec_ctx_stack[stack_index]; |
511 | 0 | } |
512 | 0 | return ctx_p->token; |
513 | 0 | } |