/[pdpsoft]/trunk/grid-mw-security/cgul/fileutil/fileutil.c
ViewVC logotype

Contents of /trunk/grid-mw-security/cgul/fileutil/fileutil.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1510 - (show annotations) (download) (as text)
Sun Feb 14 12:31:32 2010 UTC (12 years, 5 months ago) by msalle
File MIME type: text/x-chdr
File size: 19471 byte(s)
Bringing fileutil back in sync with version in gLExec


1 /**
2 * Copyright (c) Members of the EGEE Collaboration. 2010.
3 * See http://www.eu-egee.org/partners/ for details on the copyright
4 * holders.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 * Authors: Oscar Koeroo, Mischa Sall\'e, Aram Verstegen
19 * NIKHEF Amsterdam, the Netherlands
20 * <grid-mw-security@nikhef.nl>
21 */
22
23 #include <sys/file.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include "fileutil.h"
31 #include "../safefile-1.0/safe_id_range_list.h"
32 #include "../safefile-1.0/safe_is_path_trusted.h"
33
34 static int priv_drop(int unpriv_uid,int unpriv_gid);
35 static int raise_priv(uid_t euid, gid_t egid);
36
37 /**
38 * Private method.
39 * Drops privilege to an unprivileged account. When unpriv_uid and/or unpriv_gid
40 * are negative, they will be ignored and the information is taken from the real
41 * uid/gid (primary) combination.
42 * Returns 0 when successful, or the return code of set[ug]id() on error.
43 * */
44 int priv_drop(int unpriv_uid,int unpriv_gid) {
45 /* drop priv when needed: euid==0, uid!=0 */
46 uid_t euid=geteuid(),target_uid;
47 gid_t egid=getegid(),target_gid;
48 int rc;
49
50 /* Get correct target_gid */
51 target_gid=( unpriv_gid<0 ? getgid() : (gid_t)unpriv_gid );
52 /* Anything to be done? */
53 rc=( target_gid==0 || target_gid==egid ? 0 : setegid(target_gid) );
54 /* If error: don't continue */
55 if (rc!=0) return rc;
56
57 /* Get correct target_uid */
58 target_uid=( unpriv_uid<0 ? getuid() : (uid_t)unpriv_uid );
59 /* Anything to be done? */
60 rc=( target_uid==0 || target_uid==euid ? 0 : seteuid(target_uid) );
61 /* Error: try to restore */
62 if (rc!=0) setegid(egid); /* ignore rc of THIS process: damage control */
63
64 return 0;
65 }
66
67 /**
68 * Private method.
69 * Tries to raise privilege level back to euid/egid.
70 * Return -1 when fails or impossible (neither euid or real uid is root), 0
71 * upon success.
72 * */
73 int raise_priv(uid_t euid, gid_t egid) {
74 uid_t uid=getuid();
75
76 /* reset euid/egid if: it was (effective) root or real user is root */
77 if (euid==0 || uid==0) {
78 if (setegid(egid) || seteuid(euid))
79 return -1;
80 else
81 return 0;
82 }
83 return -1;
84 }
85 /**
86 * Does given lock action on file given by filedescriptor fd using mechanism
87 * defined by lock_type. lock_type can be a multiple types in which case they
88 * will be all used. LCK_NOLOCK is a special lock type which just does nothing
89 * and will not be combined with others. Valid lock types:
90 * LCK_NOLOCK - no locking
91 * LCK_FCNTL - fcntl() locking
92 * LCK_FLOCK - flock() locking
93 * Valid actions are:
94 * LCK_READ - set shared read lock
95 * LCK_WRITE - set exclusive write lock
96 * LCK_UNLOCK - unset lock
97 * Locks are exclusive for writing and shared for reading: multiple processes
98 * can read simultaneously, but writing is exclusive, both for reading and
99 * writing.
100 * Returns -1 on error, 0 on success.
101 */
102 int cgul_filelock(int fd, int lock_type, int action) {
103 struct flock lck_struct;
104 int rc1,rc2,lck;
105
106 /* Can have multiple lock_types */
107
108 if (lock_type & LCK_NOLOCK)
109 return 0;
110
111 /* FLOCK */
112 if (lock_type & LCK_FLOCK) {
113 switch (action) { /* Only one action at the time */
114 case LCK_READ: lck=LOCK_SH; break;
115 case LCK_WRITE: lck=LOCK_EX; break;
116 case LCK_UNLOCK: lck=LOCK_UN; break;
117 default: return -1;
118 }
119 rc1=flock(fd, lck);
120 } else
121 rc1=0;
122 /* FCNTL */
123 if (lock_type & LCK_FCNTL) {
124 switch (action) { /* Only one action at the time */
125 case LCK_READ: lck_struct.l_type=F_RDLCK; break;
126 case LCK_WRITE: lck_struct.l_type=F_WRLCK; break;
127 case LCK_UNLOCK: lck_struct.l_type=F_UNLCK; break;
128 default: return -1;
129 }
130 lck_struct.l_whence=SEEK_SET;
131 lck_struct.l_start=0;
132 lck_struct.l_len=0;
133 rc2=fcntl(fd,F_SETLKW,&lck_struct); /* -1 error */
134 } else
135 rc2=0;
136 return (rc1 || rc2 ? -1 : 0);
137 }
138
139
140 /**
141 * Reads proxy from *path using given lock_type (see cgul_filelock). It tries to
142 * drop privilege to real-uid/real-gid when euid==0 and uid!=0.
143 * Space needed will be malloc-ed.
144 * Upon successful completion config contains the contents of path.
145 * Return values:
146 * 0: success
147 * -1: I/O error
148 * -2: privilege-drop error
149 * -3: permissions error
150 * -4: memory error
151 * -5: too many retries needed during reading
152 * -6: locking failed
153 */
154 int cgul_read_proxy(const char *path, int lock_type, char **proxy) {
155 const int tries=10; /* max number of retries for reading a changing file */
156 int i,fd,rc=0;
157 struct stat st1,st2,*sptr1,*sptr2,*sptr3;
158 uid_t uid=getuid(),euid=geteuid();
159 gid_t gid=getgid(),egid=getegid();
160 char *buf,*buf_new; /* *proxy will be updated when everything is ok */
161 ssize_t size;
162
163 /* Drop privilege to real uid and real gid, only when we can and are
164 * not-root */
165 if ( euid==0 && uid!=0 && priv_drop(uid,gid) )
166 return -2;
167 /* Open file */
168 if ((fd=open(path,O_RDONLY))==-1) {
169 raise_priv(euid,egid); return -1;
170 }
171 /* Lock file */
172 if (cgul_filelock(fd,lock_type,LCK_READ)) {
173 close(fd); raise_priv(euid,egid); return -6;
174 }
175 /* Stat the file before reading:
176 * Need ownership and mode for allowed values, size for malloc */
177 if (fstat(fd,&st1)) {
178 close(fd); raise_priv(euid,egid); return -1;
179 }
180 /* Check we own it (only uid) and it is unreadable/unwriteable for anyone
181 * else */
182 if ( st1.st_uid!=uid ||
183 st1.st_mode & S_IRGRP || st1.st_mode & S_IWGRP ||
184 st1.st_mode & S_IROTH || st1.st_mode & S_IWOTH ) {
185 close(fd); raise_priv(euid,egid); return -3;
186 }
187 /* Get expected space: need 1 extra for trailing '\0' */
188 if ( (buf=(char *)malloc((size_t)(st1.st_size+sizeof(char))))==NULL) {
189 close(fd); raise_priv(euid,egid); return -4;
190 }
191 /* use pointers to the two so that we can swap them easily */
192 sptr1=&st1; sptr2=&st2;
193 /* reading retry loop */
194 for (i=0; i<tries; i++) {
195 /* Read file: if statted size changes, we will try again */
196 size=read(fd,buf,(size_t)sptr1->st_size);
197 buf[size]='\0'; /* Important: read doesn't add the '\0' */
198 /* Stat the file */
199 if (fstat(fd,sptr2)==-1) { /* cannot even stat: I/O error */
200 rc=-1; break;
201 }
202 /* Size, mtime and ctime should have stayed the same, especially ctime
203 * is good as we can't change it with touch ! */
204 if ( sptr2->st_size == sptr1->st_size && /* size equal */
205 sptr2->st_mtime== sptr1->st_mtime && /* mtime equal */
206 sptr2->st_ctime== sptr1->st_ctime) { /* ctime equal */
207 /* Just check the return of the read, we might have an I/O error */
208 rc= (size==(ssize_t)sptr1->st_size ? 0 : -1);
209 break;
210 }
211
212 /* File has changed during reading: retry */
213 if (i<tries-1) { /* will be doing a retry */
214 buf_new=(char *)realloc(buf,(size_t)(sptr2->st_size+sizeof(char)));
215 if ( buf_new==NULL ) {
216 rc=-4; break;
217 }
218 buf=buf_new;
219 /* swap struct pointers */
220 sptr3=sptr2; sptr2=sptr1; sptr1=sptr3;
221 /* wait */
222 usleep(500);
223 /* About to read again, make sure we're (again) at the start */
224 if (lseek(fd,0,SEEK_SET)!=0) { /* I/O error */
225 rc=-1; break;
226 }
227 } else /* failed too many times */
228 rc=-5;
229 }
230
231 /* unlock and close the file, ignore exitval: we have read already */
232 cgul_filelock(fd,lock_type,LCK_UNLOCK);
233 close(fd);
234 /* reset euid/egid if it was (effective) root. Ignore exit value. */
235 raise_priv(euid,egid);
236 /* finalize */
237 if (rc!=0) {
238 free(buf); return rc;
239 }
240 /* Only now put buf in *proxy */
241 *proxy=buf;
242 return 0;
243 }
244
245 /**
246 * Used to read in a config file, the path is checked to be trusted using
247 * safe_is_path_trusted_r() from the safefile library of J. Kupsch.
248 * Upon successful completion config contains the contents of the file at path.
249 * trust_uid and trust_gid are mandatory, but can be 0 in which case they are
250 * effectively ignored.
251 * In case userswitching is possible, privilege is dropped to either the trusted
252 * id's when non-zero or to the real uid/gid.
253 * In switching mode the level of trust has to be confidential, otherwise
254 * trusted (=read but not write by untrusted people) is enough.
255 * Trusted means: user= {root,trust_uid,real uid}
256 * group={root,trust_gid}
257 * Return values:
258 * 0: succes
259 * -1: I/O error, including when file changed during reading in any way other
260 * than access time.
261 * -2: privilege-drop error
262 * -3: permission error (untrusted path)
263 * -4: memory error
264 * -5: unknown or safefile error
265 */
266 int cgul_read_config(const char *path, char **config,
267 uid_t trust_uid, gid_t trust_gid) {
268 int fd,rc,trust,switching;
269 uid_t euid=geteuid(),uid=getuid(),target_uid;
270 gid_t egid=getegid(),gid=getgid(),target_gid;
271 struct safe_id_range_list ulist,glist;
272 struct stat st_before,st_after;
273 char *buf;
274
275 /* Expected level of trust depends on mode: user switching or not */
276 if (euid==0 || uid==0) {
277 switching=1;
278 /* Switch id to trust_uid/trust_gid unless it's root, in that case
279 * switch to real uid/gid */
280 target_uid=trust_uid==0 ? uid : trust_uid;
281 target_gid=trust_gid==0 ? gid : trust_gid;
282 /* when target_uid!=0 then set privileges */
283 if (target_uid!=0 && priv_drop(target_uid,target_gid))
284 return -2;
285 } else {
286 /* Nothing to switch, trust_uid/trust_gid will be used to check the file
287 * permissions only. */
288 target_uid=0; /* target_uid is also added to the safe id list */
289 target_gid=0; /* just in case target_gid would be added to the safe id
290 list */
291 switching=0;
292 }
293
294 /* initialize the lists of trusted uid/gid, can basically only fail when
295 * out of memory */
296 if ( safe_init_id_range_list(&ulist) ||
297 safe_init_id_range_list(&glist) ||
298 safe_add_id_to_list(&ulist,trust_uid) ||
299 safe_add_id_to_list(&ulist,target_uid) || /* ignored (0) when
300 non-switching */
301 safe_add_id_to_list(&glist,trust_gid) ) {
302 raise_priv(euid,egid); return -4;
303 }
304 /* Do an lstat so that we can compare modes etc. before/after */
305 if (lstat(path,&st_before)) {
306 raise_priv(euid,egid); return -2;
307 }
308
309 /* Check trust */
310 trust=safe_is_path_trusted_r(path,&ulist,&glist);
311 /* free the range lists */
312 safe_destroy_id_range_list(&ulist);
313 safe_destroy_id_range_list(&glist);
314 /* Check the level of trust */
315 switch (trust) {
316 case SAFE_PATH_ERROR:
317 /* checking failed */
318 raise_priv(euid,egid); return -5;
319 case SAFE_PATH_UNTRUSTED:
320 /* Perms are wrong */
321 raise_priv(euid,egid); return -3;
322 case SAFE_PATH_TRUSTED:
323 case SAFE_PATH_TRUSTED_STICKY_DIR:
324 /* Only good in non-switching mode: */
325 if (switching) { /* perms are wrong */
326 raise_priv(euid,egid); return -3;
327 }
328 /* not-switching, perms are ok */
329 break;
330 case SAFE_PATH_TRUSTED_CONFIDENTIAL:
331 /* GOOD */
332 break;
333 default:
334 /* Unknown state, should not be reached */
335 raise_priv(euid,egid); return -5;
336 }
337 /* Open file and stat the file (latter for size) */
338 if ((fd=open(path,O_RDONLY))==-1) {
339 raise_priv(euid,egid); return -1;
340 }
341 /* Get expected space, don't forget trailing '\0' */
342 if ( (buf=(char *)malloc((size_t)(st_before.st_size+sizeof(char))))==NULL)
343 {
344 close(fd); raise_priv(euid,egid); return -4;
345 }
346 /* Read the file, check we get right size */
347 if ( read(fd,buf,st_before.st_size)!=(ssize_t)st_before.st_size ||
348 fstat(fd,&st_after) || /* Do stat fd */
349 st_before.st_dev !=st_after.st_dev || /* device */
350 st_before.st_ino !=st_after.st_ino || /* inode */
351 st_before.st_size !=st_after.st_size || /* size */
352 st_before.st_mode !=st_after.st_mode || /* mode */
353 st_before.st_uid !=st_after.st_uid || /* uid */
354 st_before.st_gid !=st_after.st_gid || /* gid */
355 st_before.st_mtime !=st_after.st_mtime || /* modification time */
356 st_before.st_ctime !=st_after.st_ctime ) /* creation time */
357 /* something changed or went wrong: classify all as I/O error, because
358 * we were reading a trusted or confidential file.
359 * Don't return yet, we want to free the memory centrally */
360 rc=-1;
361 else {
362 /* add trailing '\0' */
363 buf[st_after.st_size]='\0';
364 rc=0;
365 }
366 /* Close file */
367 close(fd);
368 /* reset euid/egid if it was (effective) root. Ignore exit value. */
369 raise_priv(euid,egid);
370 /* finalize */
371 if (rc!=0)
372 free(buf);
373 else /* Only now put buf in *proxy */
374 *config=buf;
375 return rc;
376 }
377
378 /**
379 * Writes proxy from *proxy to *path using given lock_type (see cgul_filelock).
380 * When (e)uid==0 it tries to drop privilege to given write_uid, write_gid. When
381 * either of these is -1, the real uid/gid is used instead, if one of those is
382 * root, the corresponding effective uid/gid is used instead.
383 * Return values:
384 * 0: success
385 * -1: I/O error
386 * -2: privilege-drop error
387 * -3: permissions error, including file directly in / or not absolute
388 * -4: memory error
389 * -6: locking failed
390 */
391 int cgul_write_proxy(const char *path, int lock_type, const char *proxy,
392 int write_uid, int write_gid) {
393 const mode_t filemode=S_IRUSR | S_IWUSR;
394 const mode_t dirmode=S_IRUSR | S_IWUSR | S_IXUSR;
395 int fd,rc;
396 uid_t euid=geteuid(), uid=getuid(), target_uid;
397 gid_t egid=getegid(), gid=getgid(), target_gid;
398 size_t expsize=strlen(proxy)/sizeof(char);
399 char *pos,*pathcopy;
400
401 /* Set write uid */
402 if (write_uid>=0)
403 target_uid=write_uid;
404 else
405 target_uid=(uid==0 ? euid : uid); /* when real==root: stay effective */
406 /* Set write gid */
407 if (write_gid>=0)
408 target_gid=write_gid;
409 else
410 target_gid=(gid==0 ? egid : gid); /* when real==root: stay effective */
411
412 /* Drop privilege when (e)uid == 0 */
413 if ( (euid==0 || uid==0) && priv_drop(target_uid,target_gid))
414 return -2;
415 /* make copy of the path */
416 if ( (pathcopy=strdup(path))==NULL ) {
417 /* out of mem */
418 raise_priv(euid,egid); return -4;
419 }
420 /* Check filename */
421 if ( (pos=strrchr(pathcopy,'/'))==NULL) {
422 free(pathcopy); raise_priv(euid,egid); return -3;
423 }
424 /* Create parent directories where needed */
425 pos[0]='\0';
426 if ((rc=cgul_mkdir_with_parents(pathcopy,dirmode))!=0) {
427 free(pathcopy); raise_priv(euid,egid); return rc;
428 }
429 free(pathcopy);
430 /* Open the file */
431 if ( (fd=open(path,O_WRONLY | O_CREAT,filemode))==-1 ) {
432 raise_priv(euid,egid); return -1;
433 }
434 /* Lock the file */
435 if ( cgul_filelock(fd,lock_type,LCK_WRITE) ) {
436 close(fd); raise_priv(euid,egid); return -6;
437 }
438 /* Do a chmod and chown, in case it already existed. If this fails, the file
439 * has the wrong permissions.
440 * Chowning is in principal only for the group. */
441 if ( fchmod(fd,filemode) || fchown(fd,target_uid,target_gid) ) {
442 close(fd); raise_priv(euid,egid); return -3;
443 }
444 /* Truncate and write file */
445 if ( ftruncate(fd,0)!=0 ||
446 write(fd,proxy,expsize)!=(ssize_t)expsize ) {
447 close(fd); raise_priv(euid,egid); return -1; /* write error */
448 }
449 /* unlock: ignore the exit code */
450 cgul_filelock(fd,lock_type,LCK_UNLOCK);
451 /* close the file, don't ignore exit values: might have write error */
452 if (close(fd))
453 rc=-1;
454 else
455 rc=0;
456
457 /* reset euid/egid if it was (effective) root. Ignore exit value. */
458 raise_priv(euid,egid);
459 return 0;
460 }
461
462 /**
463 * Writes proxy to unique filename created from path_template using mkstemp().
464 * path_template will be overridden with the actual filename.
465 * When (e)uid==0 it tries to drop privilege to given write_uid, write_gid. When
466 * either of these is -1, the real uid/gid is used instead, if one of those is
467 * root, the corresponding effective uid/gid is used instead.
468 * Any directory in path_template will be attempted to be created if it doesn't
469 * exist, with mode 0600.
470 * Return values:
471 * 0: success
472 * -1: I/O error, this includes a failure of mkstemp which can be due to a
473 * wrong template. It MUST contain 6 consecutive X's.
474 * -2: privilege-drop error
475 * -3: illegal path_template: in / or not absolute.
476 * -4: memory error
477 * -5: invalid template: it MUST end with 6 X's
478 */
479 int cgul_write_uniq_proxy(char *path_template, const char *proxy,
480 int write_uid, int write_gid) {
481 const mode_t filemode=S_IRUSR | S_IWUSR;
482 const mode_t dirmode=S_IRUSR | S_IWUSR | S_IXUSR;
483 int fd,rc;
484 char *pos;
485 uid_t uid=getuid(), euid=geteuid(), target_uid;
486 gid_t gid=getgid(), egid=getegid(), target_gid;
487 size_t expsize=strlen(proxy)/sizeof(char);
488
489 /* Check template format, see mkstemp() */
490 pos=(char *)&(path_template[strlen(path_template)-6]);
491 if (strncmp(pos,"XXXXXX",6)!=0)
492 return -5;
493
494 /* Set write uid */
495 if (write_uid>=0)
496 target_uid=write_uid;
497 else
498 target_uid=(uid==0 ? euid : uid); /* when real==root: stay effective */
499 /* Set write gid */
500 if (write_gid>=0)
501 target_gid=write_gid;
502 else
503 target_gid=(gid==0 ? egid : gid); /* when real==root: stay effective */
504
505 /* Drop privilege when (e)uid == 0 */
506 if ( (euid==0 || uid==0) && priv_drop(target_uid,target_gid))
507 return -2;
508
509 /* Check filename */
510 if ( (pos=strrchr(path_template,'/'))==NULL) { /* illegal pathname */
511 raise_priv(euid,egid); return -3;
512 }
513 /* Create parent directories where needed */
514 pos[0]='\0';
515 if ((rc=cgul_mkdir_with_parents(path_template,dirmode))!=0) {
516 raise_priv(euid,egid); return rc;
517 }
518 pos[0]='/';
519 /* Open unique filename */
520 if ( (fd=mkstemp(path_template))==-1) {
521 raise_priv(euid,egid); return -1;
522 }
523 /* chmod and write the file */
524 if (fchmod(fd,filemode) ||
525 write(fd,proxy,expsize)!=(ssize_t)expsize ) {
526 close(fd); raise_priv(euid,egid); return -1;
527 }
528 /* close the file, don't ignore exit values: might have write error */
529 if (close(fd)) {
530 raise_priv(euid,egid); return -1;
531 }
532
533 /* reset euid/egid if it was (effective) root. Ignore exit value. */
534 raise_priv(euid,egid);
535 return 0;
536 }
537
538 /**
539 * Behaviour as mkdir -p: create parents where needed.
540 * Return values:
541 * 0: success
542 * -1: result is not a directory
543 * -3: absolutedir is not absolute (does not start with '/')
544 * -4: out of memory
545 */
546 int cgul_mkdir_with_parents(const char *absolutedir, mode_t mode) {
547 int rc,loop;
548 char *dir,*pos;
549 struct stat dir_stat;
550
551 if (absolutedir[0]!='/') /* need absolute path */
552 return -3;
553 /* make copy for local usage */
554 if ( (dir=strdup(absolutedir))==NULL )
555 return -4; /* out of memory */
556
557 /* pos will 'loop' over all the '/' */
558 pos=strchr(&(dir[1]),'/');
559 if (pos!=NULL)
560 pos[0]='\0';
561 loop=1;
562 do {
563 mkdir(dir,mode); /* just create, we will check later if it worked */
564 if (pos!=NULL) {
565 pos[0]='/';
566 pos=strchr(&(pos[1]),'/');
567 if (pos!=NULL) pos[0]='\0';
568 } else
569 loop=0;
570 } while ( loop );
571 /* Now check if dir exists */
572 if (lstat(dir,&dir_stat) || !S_ISDIR(dir_stat.st_mode))
573 rc=-1;
574 else
575 rc=0;
576 free(dir);
577 return rc;
578 }

grid.support@nikhef.nl
ViewVC Help
Powered by ViewVC 1.1.28