/[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 1476 - (show annotations) (download) (as text)
Mon Feb 8 16:11:46 2010 UTC (12 years, 4 months ago) by msalle
File MIME type: text/x-chdr
File size: 13195 byte(s)
- new function cgul_read_config that reads a config file into a memory buffer
  using J. Kupsch' safefile (only the safe_is_path_trusted_r() )

- hopefully raise all privileges also when failure.

- remove dead code


1 #include <sys/file.h>
2 #include <sys/stat.h>
3 #include <fcntl.h>
4 #include <unistd.h>
5 #include <stdlib.h>
6 #include <string.h>
7
8 #include "fileutil.h"
9 #include "../safefile-1.0/safe_id_range_list.h"
10 #include "../safefile-1.0/safe_is_path_trusted.h"
11
12 static int priv_drop(uid_t unpriv_uid,gid_t unpriv_gid);
13 static int raise_priv(uid_t euid, gid_t egid);
14
15 int cgul_filelock(int fd, int lock_type, int action) {
16 struct flock lck_struct;
17 int rc1,rc2,lck;
18
19 /* Can have multiple lock_types */
20
21 if (lock_type & LCK_NOLOCK)
22 return 0;
23
24 /* FLOCK */
25 if (lock_type & LCK_FLOCK) {
26 switch (action) { /* Only one action at the time */
27 case LCK_READ: lck=LOCK_SH; break;
28 case LCK_WRITE: lck=LOCK_EX; break;
29 case LCK_UNLOCK: lck=LOCK_UN; break;
30 default: return -1;
31 }
32 rc1=flock(fd, lck);
33 }
34 /* FCNTL */
35 if (lock_type & LCK_FCNTL) {
36 switch (action) { /* Only one action at the time */
37 case LCK_READ: lck_struct.l_type=F_RDLCK; break;
38 case LCK_WRITE: lck_struct.l_type=F_WRLCK; break;
39 case LCK_UNLOCK: lck_struct.l_type=F_UNLCK; break;
40 default: return -1;
41 }
42 lck_struct.l_whence=SEEK_SET;
43 lck_struct.l_start=0;
44 lck_struct.l_len=0;
45 rc2=fcntl(fd,F_SETLK,&lck_struct); /* -1 error */
46 }
47 return (rc1 || rc2 ? -1 : 0);
48 }
49
50 /**
51 * drops privilege to an unprivileged account. When unpriv_uid and/or unpriv_gid
52 * are negative, they will be ignored and the information is taken from the real
53 * uid/gid (primary) combination
54 * Returns 0 when successful.
55 * */
56 int priv_drop(uid_t unpriv_uid,gid_t unpriv_gid) {
57 /* drop priv when needed: euid==0, uid!=0 */
58 uid_t euid=geteuid(),uid;
59 gid_t egid=getegid(),gid;
60 int rc;
61
62 if (euid!=0) /* Cannot do anything */
63 return 0;
64
65 /* Drop group */
66 if (unpriv_gid>=0)
67 rc=setegid(unpriv_gid);
68 else {
69 gid=getgid();
70 rc=(gid==0 || gid==egid ? 0 : setegid(gid) );
71 }
72 /* If error: don't continue */
73 if (rc!=0) return rc;
74
75 /* Drop user */
76 if (unpriv_uid>=0)
77 rc=seteuid(unpriv_uid);
78 else {
79 uid=getuid();
80 rc=(uid==0 || uid==euid ? 0 : seteuid(uid) );
81 }
82 if (rc!=0) {
83 /* try restoring the euid */
84 setegid(egid); /* ignore rc of THIS process, it's damage control */
85 return rc;
86 }
87 return 0;
88 }
89
90 /**
91 * Tries to raise privilege level back to euid/egid.
92 * Return -1 when fails or impossible (neither euid or real uid is root), 0
93 * upon success.
94 * */
95 int raise_priv(uid_t euid, gid_t egid) {
96 uid_t uid=getuid();
97
98 /* reset euid/egid if: it was (effective) root or real user is root */
99 if (euid==0 || uid==0) {
100 if (setegid(egid) || seteuid(euid))
101 return -1;
102 else
103 return 0;
104 }
105 return -1;
106 }
107
108 /**
109 * Reads proxy from *path using given lock_type (see cgul_filelock). It tries to
110 * drop privilege to real-uid/read_gid. Space needed will be malloc-ed.
111 * Upon successful completion config contains the contents of path.
112 * Return values:
113 * 0: success
114 * -1: I/O error
115 * -2: privilege-drop error
116 * -3: permissions error
117 * -4: memory error
118 * -5: too many retries needed during reading
119 */
120 int cgul_read_proxy(const char *path, int lock_type, char **proxy, gid_t read_gid) {
121 const int tries=10; /* max number of retries for reading a changing file */
122 int i,fd,rc=0;
123 struct stat st1,st2,*sptr1,*sptr2,*sptr3;
124 uid_t uid=getuid(),euid=geteuid();
125 gid_t egid=getegid();
126 char *buf,*buf_new; /* *proxy will be updated when we know everything is ok */
127 ssize_t size;
128
129 /* Drop privilege to real uid and special read group */
130 if (priv_drop(uid,read_gid))
131 return -2;
132 /* Open file */
133 if ((fd=open(path,O_RDONLY))==-1) {
134 raise_priv(euid,egid); return -1;
135 }
136 /* Lock file */
137 if (cgul_filelock(fd,lock_type,LCK_READ)) {
138 close(fd); raise_priv(euid,egid); return -1;
139 }
140 /* Stat the file before reading:
141 * Need ownership and mode for allowed values, size for malloc */
142 if (fstat(fd,&st1)) {
143 close(fd); raise_priv(euid,egid); return -1;
144 }
145 /* Check we own it (only uid) and is unreadable/unwriteable for anyone else
146 * */
147 if ( st1.st_uid!=uid ||
148 st1.st_mode & S_IRGRP || st1.st_mode & S_IWGRP ||
149 st1.st_mode & S_IROTH || st1.st_mode & S_IWOTH ) {
150 close(fd); raise_priv(euid,egid); return -3;
151 }
152 /* Get expected space */
153 if ( (buf=(char *)malloc(st1.st_size))==NULL) {
154 close(fd); raise_priv(euid,egid); return -4;
155 }
156 /* use pointers to the two so that we can swap them easily */
157 sptr1=&st1; sptr2=&st2;
158 /* reading retry loop */
159 for (i=0; i<tries; i++) {
160 /* Read file: if statted size changes, we will try again */
161 size=read(fd,buf,sptr1->st_size);
162 /* Stat the file */
163 if (fstat(fd,sptr2)==-1) { /* cannot even stat: I/O error */
164 rc=-1; break;
165 }
166 /* Size, mtime and ctime should have stayed the same, especially ctime
167 * is good as we can't change it with touch ! */
168 if ( sptr2->st_size == sptr1->st_size && /* size equal */
169 sptr2->st_mtime== sptr1->st_mtime && /* mtime equal */
170 sptr2->st_ctime== sptr1->st_ctime) { /* ctime equal */
171 /* Just check the return of the read, we might have an I/O error */
172 rc= (size==sptr1->st_size ? 0 : -1);
173 break;
174 }
175
176 /* File has changed during reading: retry */
177 if (i<tries-1) { /* will be doing a retry */
178 if ( (buf_new=(char *)realloc(buf,sptr2->st_size))==NULL ) {
179 rc=-4; break;
180 }
181 buf=buf_new;
182 /* swap struct pointers */
183 sptr3=sptr2; sptr2=sptr1; sptr1=sptr3;
184 /* wait */
185 usleep(500);
186 /* About to read again, make sure we're (again) at the start */
187 if (lseek(fd,0,SEEK_SET)!=0) { /* I/O error */
188 rc=-1; break;
189 }
190 } else /* failed too many times */
191 rc=-5;
192 }
193
194 /* unlock and close the file, ignore exitval: we have read already */
195 cgul_filelock(fd,lock_type,LCK_UNLOCK);
196 close(fd);
197 /* reset euid/egid if it was (effective) root. Ignore exit value. */
198 raise_priv(euid,egid);
199 /* finalize */
200 if (rc!=0) {
201 free(buf); return rc;
202 }
203 /* Only now put buf in *proxy */
204 *proxy=buf;
205 return 0;
206 }
207
208 /**
209 * Used to read in a config file, the path is checked to be trusted using
210 * safe_is_path_trusted_r() from the safefile library of J. Kupsch.
211 * Upon successful completion config contains the contents of path
212 * Return values:
213 * 0: succes
214 * -1: I/O error
215 * -2: privilege-drop error
216 * -3: permission error (untrusted path)
217 * -4: memory error
218 * -5: unknown or safefile error
219 */
220 int cgul_read_config(const char *path, char **config, gid_t read_gid) {
221 int fd,rc,trust;
222 uid_t uid=getuid(),euid=geteuid();
223 gid_t gid=getgid(),egid=getegid(),target_gid=read_gid<0 ? gid : read_gid;
224 struct safe_id_range_list ulist,glist;
225 struct stat st;
226 char *buf;
227
228 /* Drop privilege to real uid and special read group */
229 if (priv_drop(uid,read_gid))
230 return -2;
231 /* initialize the lists of trusted uid/gid, can basically only fail when
232 * out of memory */
233 if ( safe_init_id_range_list(&ulist) ||
234 safe_init_id_range_list(&glist) ||
235 safe_add_id_to_list(&ulist,uid) ||
236 safe_add_id_to_list(&glist,target_gid) ) {
237 raise_priv(euid,egid); return -4;
238 }
239 /* Check trust */
240 trust=safe_is_path_trusted_r(path,&ulist,&glist);
241 /* free the range lists */
242 safe_destroy_id_range_list(&ulist);
243 safe_destroy_id_range_list(&glist);
244 /* Check the level of trust */
245 switch (trust) {
246 case SAFE_PATH_ERROR:
247 raise_priv(euid,egid); return -5; break; /* */
248 case SAFE_PATH_UNTRUSTED:
249 case SAFE_PATH_TRUSTED_STICKY_DIR:
250 raise_priv(euid,egid); return -3; break; /* Perms are somehow wrong */
251 case SAFE_PATH_TRUSTED:
252 case SAFE_PATH_TRUSTED_CONFIDENTIAL:
253 break; /* trusted */
254 default:
255 raise_priv(euid,egid); return -5; break; /* unknown error */
256 }
257 /* Open file and stat the file (latter for size) */
258 if ((fd=open(path,O_RDONLY))==-1 || fstat(fd,&st)) {
259 raise_priv(euid,egid); return -1;
260 }
261 /* Get expected space */
262 if ( (buf=(char *)malloc(st.st_size))==NULL) {
263 close(fd); raise_priv(euid,egid); return -4;
264 }
265 /* Read the file, check we get right size */
266 if (read(fd,buf,st.st_size)!=st.st_size)
267 /* read error, but don't return yet, we want to free the memory centrally */
268 rc=-1;
269 else
270 rc=0;
271 /* Close file */
272 close(fd);
273 /* reset euid/egid if it was (effective) root. Ignore exit value. */
274 raise_priv(euid,egid);
275 /* finalize */
276 if (rc!=0)
277 free(buf);
278 else /* Only now put buf in *proxy */
279 *config=buf;
280 return rc;
281 }
282
283 /**
284 * Writes proxy from *proxy to *path using given lock_type (see cgul_filelock).
285 * It tries to drop privilege to given write_uid, gid_t write_gid. When either
286 * of them is -1, that one is ignored.
287 * Return values:
288 * 0: success
289 * -1: I/O error
290 * -2: privilege-drop error
291 * -3: permissions error
292 */
293 int cgul_write_proxy(const char *path, int lock_type, const char *proxy,
294 uid_t write_uid, gid_t write_gid) {
295 int fd,rc;
296 uid_t uid=getuid(),euid=geteuid(),target_uid=(write_uid>=0 ? write_uid : uid);
297 gid_t gid=getgid(),egid=getegid(),target_gid=(write_gid>=0 ? write_gid : gid);
298 mode_t mode=S_IRUSR | S_IWUSR;
299 size_t size,expsize=strlen(proxy)/sizeof(char);
300
301 /* Drop privilege, will be ignored when euid != 0 */
302 if (priv_drop(write_uid,write_gid))
303 return -2;
304 /* Open the file */
305 if ( (fd=open(path,O_WRONLY | O_CREAT,mode))==-1 ) {
306 raise_priv(euid,egid); return -1;
307 }
308 /* Lock the file */
309 if ( cgul_filelock(fd,lock_type,LCK_WRITE) ) {
310 close(fd); raise_priv(euid,egid); return -1;
311 }
312 /* Do a chmod and chown, in case it already existed. If this fails, the file has
313 * the wrong permissions. Chowning is in principal only for the group. */
314 if ( fchmod(fd,mode) || fchown(fd,target_uid,target_gid) ) {
315 close(fd); raise_priv(euid,egid); return -3;
316 }
317 /* Truncate and write file */
318 if ( ftruncate(fd,0)!=0 ||
319 (size=write(fd,proxy,expsize))!=expsize) {
320 close(fd); raise_priv(euid,egid); return -1; /* write error */
321 }
322 /* unlock */
323 cgul_filelock(fd,lock_type,LCK_UNLOCK);
324 /* close the file, don't ignore exit values: might have write error */
325 if (close(fd))
326 rc=-1;
327 else
328 rc=0;
329
330 /* reset euid/egid if it was (effective) root. Ignore exit value. */
331 raise_priv(euid,egid);
332 return 0;
333 }
334
335 /**
336 * Writes proxy to random unique filename created from path_template using
337 * mkstemp(). It drops privilege (if possible) to write_uid/write_gid
338 * Any directory in path_template will be attempted to be created if it doesn't
339 * exist, with mode 0600.
340 * Return values:
341 * 0: success
342 * -1: I/O error
343 * -2: privilege-drop error
344 * -3: path_template is not absolute (does not start with '/')
345 * -4: memory error
346 */
347 int cgul_write_uniq_proxy(const char *path_template, const char *proxy,
348 uid_t write_uid, gid_t write_gid) {
349 int fd,rc;
350 char *path,*pos;
351 uid_t euid=geteuid();
352 gid_t egid=getegid();
353 mode_t mode=S_IRUSR | S_IWUSR;
354 size_t size,expsize=strlen(proxy)/sizeof(char);
355
356 /* Drop privilege, will be ignored when euid != 0 */
357 if (priv_drop(write_uid,write_gid))
358 return -2; /* priv problem */
359 /* make copy of the template, since it will be overwritten by mkstemp() */
360 if ( (path=strdup(path_template))==NULL ) {
361 /* out of mem */
362 raise_priv(euid,egid); return -4;
363 }
364 /* Now create the path */
365 if ( (pos=strrchr(path,'/'))==NULL) {
366 /* illegal pathname */
367 free(path); raise_priv(euid,egid); return -3;
368 }
369 pos[0]='\0';
370 if ((rc=cgul_mkdir_with_parents(path,mode))!=0) {
371 free(path); raise_priv(euid,egid); return rc;
372 }
373 pos[0]='/';
374
375 /* Open unique filename */
376 if ( (fd=mkstemp(path))==-1) {
377 free(path); raise_priv(euid,egid); return -1;
378 }
379 /* chmod and write the file */
380 if (fchmod(fd,mode) ||
381 (size=write(fd,proxy,expsize))!=expsize ) {
382 close(fd); free(path); raise_priv(euid,egid); return -1;
383 }
384 /* close the file, don't ignore exit values: might have write error */
385 if (close(fd)) {
386 free(path); raise_priv(euid,egid); return -1;
387 }
388
389 /* reset euid/egid if it was (effective) root. Ignore exit value. */
390 free(path);
391 raise_priv(euid,egid);
392 return 0;
393 }
394
395 /**
396 * Behaviour as mkdir -p: create parents where needed.
397 * Return values:
398 * 0: success
399 * -1: result is not a directory
400 * -3: absolutedir is not absolute (does not start with '/')
401 * -4: out of memory
402 */
403 int cgul_mkdir_with_parents(const char *absolutedir, mode_t mode) {
404 int rc;
405 char *dir,*pos;
406 struct stat dir_stat;
407
408 if (absolutedir[0]!='/') /* need absolute path */
409 return -3;
410 /* make copy */
411 if ( (dir=strdup(absolutedir))==NULL )
412 return -4; /* out of memory */
413
414 /* pos will 'loop' over all the '/' */
415 pos=&(dir[1]); /* start with 2nd position, it could be '\0' but not NULL */
416 while ( (pos=strchr(pos,'/')) ) {
417 pos[0]='\0';
418 mkdir(dir,mode); /* just create, we will check later if it worked */
419 pos[0]='/';
420 pos=&(pos[1]);
421 }
422 /* Now check if dir exists */
423 if (lstat(dir,&dir_stat) || !S_ISDIR(dir_stat.st_mode))
424 rc=-1;
425 else
426 rc=0;
427 free(dir);
428 return rc;
429 }

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