7 /* safely open or create regular file
9 /* #include <safe_open.h>
11 /* VSTREAM *safe_open(path, flags, mode, st, user, group, why)
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.
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.
31 /* No change is made to the permissions of an existing file.
33 /* Null pointer, or pointer to storage for the attributes of the
36 /* File ownership for a file created by safe_open(). Specify -1
37 /* in order to disable user and/or group ownership change.
39 /* No change is made to the ownership of an existing file.
41 /* A VSTRING pointer for diagnostics.
43 /* Panic: interface violations.
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.
52 /* A safe open routine was discussed by Casper Dik in article
53 /* <2rdb0s$568@mail.fwi.uva.nl>, posted to comp.security.unix
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.
66 /* The Secure Mailer license must be distributed with this software.
69 /* IBM T.J. Watson Research
71 /* Yorktown Heights, NY 10598, USA
83 /* Utility library. */
88 #include <stringops.h>
89 #include <safe_open.h>
91 /* safe_open_exist - open existing file */
93 static VSTREAM
*safe_open_exist(const char *path
, int flags
,
94 struct stat
* fstat_st
, VSTRING
*why
)
96 struct stat local_statbuf
;
102 * Open an existing file.
104 if ((fp
= vstream_fopen(path
, flags
& ~(O_CREAT
| O_EXCL
), 0)) == 0) {
106 vstring_sprintf(why
, "cannot open file: %m");
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.
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
);
124 } else if (S_ISDIR(fstat_st
->st_mode
)) {
125 vstring_sprintf(why
, "file is a directory");
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.
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.
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.
150 else if (lstat(path
, &lstat_st
) < 0) {
151 vstring_sprintf(why
, "file status changed unexpectedly: %m");
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
;
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
);
167 vstring_sprintf(why
, "file is a symbolic link");
169 } else if (fstat_st
->st_dev
!= lstat_st
.st_dev
170 || fstat_st
->st_ino
!= lstat_st
.st_ino
172 || fstat_st
->st_gen
!= lstat_st
.st_gen
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");
181 * We are almost there...
188 * End up here in case of fstat()/lstat() problems or inconsistencies.
194 /* safe_open_create - create new file */
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
)
202 * Create a non-existing file. This relies on O_CREAT | O_EXCL to not
203 * follow symbolic links.
205 if ((fp
= vstream_fopen(path
, flags
| (O_CREAT
| O_EXCL
), mode
)) == 0) {
206 vstring_sprintf(why
, "cannot create file exclusively: %m");
211 * Optionally look up the file attributes.
213 if (st
!= 0 && fstat(vstream_fileno(fp
), st
) < 0)
214 msg_fatal("%s: bad open file status: %m", path
);
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.
221 #define CHANGE_OWNER(user, group) (user != (uid_t) -1 || group != (gid_t) -1)
223 if (CHANGE_OWNER(user
, group
)
224 && fchown(vstream_fileno(fp
), user
, group
) < 0) {
225 msg_warn("%s: cannot change file ownership: %m", path
);
229 * We are almost there...
236 * End up here in case of trouble.
242 /* safe_open - safely open or create file */
244 VSTREAM
*safe_open(const char *path
, int flags
, mode_t mode
,
245 struct stat
* st
, uid_t user
, gid_t group
, VSTRING
*why
)
249 switch (flags
& (O_CREAT
| O_EXCL
)) {
252 * Open an existing file, carefully.
255 return (safe_open_exist(path
, flags
, st
, why
));
258 * Create a new file, carefully.
260 case O_CREAT
| O_EXCL
:
261 return (safe_open_create(path
, flags
, mode
, st
, user
, group
, why
));
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.
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
);
279 * Interface violation. Sorry, but we must be strict.
282 msg_panic("safe_open: O_EXCL flag without O_CREAT flag");