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