xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/safe_open.c (revision b1c86f5f087524e68db12794ee9c3e3da1ab17a0)
1 /*	$NetBSD: safe_open.c,v 1.1.1.1 2009/06/23 10:09:00 tron Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	safe_open 3
6 /* SUMMARY
7 /*	safely open or create regular file
8 /* SYNOPSIS
9 /*	#include <safe_open.h>
10 /*
11 /*	VSTREAM	*safe_open(path, flags, mode, st, user, group, why)
12 /*	const char *path;
13 /*	int	flags;
14 /*	mode_t	mode;
15 /*	struct stat *st;
16 /*	uid_t	user;
17 /*	gid_t	group;
18 /*	VSTRING	*why;
19 /* DESCRIPTION
20 /*	safe_open() carefully opens or creates a file in a directory
21 /*	that may be writable by untrusted users. If a file is created
22 /*	it is given the specified ownership and permission attributes.
23 /*	If an existing file is opened it must not be a symbolic link,
24 /*	it must not be a directory, and it must have only one hard link.
25 /*
26 /*	Arguments:
27 /* .IP "path, flags, mode"
28 /*	These arguments are the same as with open(2). The O_EXCL flag
29 /*	must appear either in combination with O_CREAT, or not at all.
30 /* .sp
31 /*	No change is made to the permissions of an existing file.
32 /* .IP st
33 /*	Null pointer, or pointer to storage for the attributes of the
34 /*	opened file.
35 /* .IP "user, group"
36 /*	File ownership for a file created by safe_open(). Specify -1
37 /*	in order to disable user and/or group ownership change.
38 /* .sp
39 /*	No change is made to the ownership of an existing file.
40 /* .IP why
41 /*	A VSTRING pointer for diagnostics.
42 /* DIAGNOSTICS
43 /*	Panic: interface violations.
44 /*
45 /*	A null result means there was a problem.  The nature of the
46 /*	problem is returned via the \fIwhy\fR buffer; when an error
47 /*	cannot be reported via \fIerrno\fR, the generic value EPERM
48 /*	(operation not permitted) is used instead.
49 /* HISTORY
50 /* .fi
51 /* .ad
52 /*	A safe open routine was discussed by Casper Dik in article
53 /*	<2rdb0s$568@mail.fwi.uva.nl>, posted to comp.security.unix
54 /*	(May 18, 1994).
55 /*
56 /*	Olaf Kirch discusses how the lstat()/open()+fstat() test can
57 /*	be fooled by delaying the open() until the inode found with
58 /*	lstat() has been re-used for a sensitive file (article
59 /*	<20000103212443.A5807@monad.swb.de> posted to bugtraq on
60 /*	Jan 3, 2000).  This can be a concern for a set-ugid process
61 /*	that runs under the control of a user and that can be
62 /*	manipulated with start/stop signals.
63 /* LICENSE
64 /* .ad
65 /* .fi
66 /*	The Secure Mailer license must be distributed with this software.
67 /* AUTHOR(S)
68 /*	Wietse Venema
69 /*	IBM T.J. Watson Research
70 /*	P.O. Box 704
71 /*	Yorktown Heights, NY 10598, USA
72 /*--*/
73 
74 /* System library. */
75 
76 #include <sys_defs.h>
77 #include <sys/stat.h>
78 #include <fcntl.h>
79 #include <stdlib.h>
80 #include <unistd.h>
81 #include <errno.h>
82 
83 /* Utility library. */
84 
85 #include <msg.h>
86 #include <vstream.h>
87 #include <vstring.h>
88 #include <stringops.h>
89 #include <safe_open.h>
90 
91 /* safe_open_exist - open existing file */
92 
93 static VSTREAM *safe_open_exist(const char *path, int flags,
94 				        struct stat * fstat_st, VSTRING *why)
95 {
96     struct stat local_statbuf;
97     struct stat lstat_st;
98     int     saved_errno;
99     VSTREAM *fp;
100 
101     /*
102      * Open an existing file.
103      */
104     if ((fp = vstream_fopen(path, flags & ~(O_CREAT | O_EXCL), 0)) == 0) {
105 	saved_errno = errno;
106 	vstring_sprintf(why, "cannot open file: %m");
107 	errno = saved_errno;
108 	return (0);
109     }
110 
111     /*
112      * Examine the modes from the open file: it must have exactly one hard
113      * link (so that someone can't lure us into clobbering a sensitive file
114      * by making a hard link to it), and it must be a non-symlink file.
115      */
116     if (fstat_st == 0)
117 	fstat_st = &local_statbuf;
118     if (fstat(vstream_fileno(fp), fstat_st) < 0) {
119 	msg_fatal("%s: bad open file status: %m", path);
120     } else if (fstat_st->st_nlink != 1) {
121 	vstring_sprintf(why, "file has %d hard links",
122 			(int) fstat_st->st_nlink);
123 	errno = EPERM;
124     } else if (S_ISDIR(fstat_st->st_mode)) {
125 	vstring_sprintf(why, "file is a directory");
126 	errno = EISDIR;
127     }
128 
129     /*
130      * Look up the file again, this time using lstat(). Compare the fstat()
131      * (open file) modes with the lstat() modes. If there is any difference,
132      * either we followed a symlink while opening an existing file, someone
133      * quickly changed the number of hard links, or someone replaced the file
134      * after the open() call. The link and mode tests aren't really necessary
135      * in daemon processes. Set-uid programs, on the other hand, can be
136      * slowed down by arbitrary amounts, and there it would make sense to
137      * compare even more file attributes, such as the inode generation number
138      * on systems that have one.
139      *
140      * Grr. Solaris /dev/whatever is a symlink. We'll have to make an exception
141      * for symlinks owned by root. NEVER, NEVER, make exceptions for symlinks
142      * owned by a non-root user. This would open a security hole when
143      * delivering mail to a world-writable mailbox directory.
144      *
145      * Sebastian Krahmer of SuSE brought to my attention that some systems have
146      * changed their semantics of link(symlink, newpath), such that the
147      * result is a hardlink to the symlink. For this reason, we now also
148      * require that the symlink's parent directory is writable only by root.
149      */
150     else if (lstat(path, &lstat_st) < 0) {
151 	vstring_sprintf(why, "file status changed unexpectedly: %m");
152 	errno = EPERM;
153     } else if (S_ISLNK(lstat_st.st_mode)) {
154 	if (lstat_st.st_uid == 0) {
155 	    VSTRING *parent_buf = vstring_alloc(100);
156 	    const char *parent_path = sane_dirname(parent_buf, path);
157 	    struct stat parent_st;
158 	    int     parent_ok;
159 
160 	    parent_ok = (stat(parent_path, &parent_st) == 0	/* not lstat */
161 			 && parent_st.st_uid == 0
162 			 && (parent_st.st_mode & (S_IWGRP | S_IWOTH)) == 0);
163 	    vstring_free(parent_buf);
164 	    if (parent_ok)
165 		return (fp);
166 	}
167 	vstring_sprintf(why, "file is a symbolic link");
168 	errno = EPERM;
169     } else if (fstat_st->st_dev != lstat_st.st_dev
170 	       || fstat_st->st_ino != lstat_st.st_ino
171 #ifdef HAS_ST_GEN
172 	       || fstat_st->st_gen != lstat_st.st_gen
173 #endif
174 	       || fstat_st->st_nlink != lstat_st.st_nlink
175 	       || fstat_st->st_mode != lstat_st.st_mode) {
176 	vstring_sprintf(why, "file status changed unexpectedly");
177 	errno = EPERM;
178     }
179 
180     /*
181      * We are almost there...
182      */
183     else {
184 	return (fp);
185     }
186 
187     /*
188      * End up here in case of fstat()/lstat() problems or inconsistencies.
189      */
190     vstream_fclose(fp);
191     return (0);
192 }
193 
194 /* safe_open_create - create new file */
195 
196 static VSTREAM *safe_open_create(const char *path, int flags, mode_t mode,
197 	            struct stat * st, uid_t user, gid_t group, VSTRING *why)
198 {
199     VSTREAM *fp;
200 
201     /*
202      * Create a non-existing file. This relies on O_CREAT | O_EXCL to not
203      * follow symbolic links.
204      */
205     if ((fp = vstream_fopen(path, flags | (O_CREAT | O_EXCL), mode)) == 0) {
206 	vstring_sprintf(why, "cannot create file exclusively: %m");
207 	return (0);
208     }
209 
210     /*
211      * Optionally look up the file attributes.
212      */
213     if (st != 0 && fstat(vstream_fileno(fp), st) < 0)
214 	msg_fatal("%s: bad open file status: %m", path);
215 
216     /*
217      * Optionally change ownership after creating a new file. If there is a
218      * problem we should not attempt to delete the file. Something else may
219      * have opened the file in the mean time.
220      */
221 #define CHANGE_OWNER(user, group) (user != (uid_t) -1 || group != (gid_t) -1)
222 
223     if (CHANGE_OWNER(user, group)
224 	&& fchown(vstream_fileno(fp), user, group) < 0) {
225 	msg_warn("%s: cannot change file ownership: %m", path);
226     }
227 
228     /*
229      * We are almost there...
230      */
231     else {
232 	return (fp);
233     }
234 
235     /*
236      * End up here in case of trouble.
237      */
238     vstream_fclose(fp);
239     return (0);
240 }
241 
242 /* safe_open - safely open or create file */
243 
244 VSTREAM *safe_open(const char *path, int flags, mode_t mode,
245 	            struct stat * st, uid_t user, gid_t group, VSTRING *why)
246 {
247     VSTREAM *fp;
248 
249     switch (flags & (O_CREAT | O_EXCL)) {
250 
251 	/*
252 	 * Open an existing file, carefully.
253 	 */
254     case 0:
255 	return (safe_open_exist(path, flags, st, why));
256 
257 	/*
258 	 * Create a new file, carefully.
259 	 */
260     case O_CREAT | O_EXCL:
261 	return (safe_open_create(path, flags, mode, st, user, group, why));
262 
263 	/*
264 	 * Open an existing file or create a new one, carefully. When opening
265 	 * an existing file, we are prepared to deal with "no file" errors
266 	 * only. When creating a file, we are prepared for "file exists"
267 	 * errors only. Any other error means we better give up trying.
268 	 */
269     case O_CREAT:
270 	fp = safe_open_exist(path, flags, st, why);
271 	if (fp == 0 && errno == ENOENT) {
272 	    fp = safe_open_create(path, flags, mode, st, user, group, why);
273 	    if (fp == 0 && errno == EEXIST)
274 		fp = safe_open_exist(path, flags, st, why);
275 	}
276 	return (fp);
277 
278 	/*
279 	 * Interface violation. Sorry, but we must be strict.
280 	 */
281     default:
282 	msg_panic("safe_open: O_EXCL flag without O_CREAT flag");
283     }
284 }
285