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