/[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 1829 - (show annotations) (download) (as text)
Wed Jun 30 12:58:45 2010 UTC (11 years, 6 months ago) by msalle
File MIME type: text/x-chdr
File size: 20318 byte(s)
- Fix wrong return code for read_config: missing file should NOT be a privilege
  drop error but a I/O error.


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

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