Postfix3.3.1
全て データ構造 ファイル 関数 変数 型定義 マクロ定義
safe_open.c
[詳解]
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 
92 static 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 
195 static 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 
243 VSTREAM *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 }
NORETURN msg_panic(const char *fmt,...)
Definition: msg.c:295
#define lstat(p, s)
Definition: warn_stat.h:19
#define stat(p, s)
Definition: warn_stat.h:18
char * sane_dirname(VSTRING *bp, const char *path)
VSTREAM * vstream_fopen(const char *path, int flags, mode_t mode)
Definition: vstream.c:1241
int vstream_fclose(VSTREAM *stream)
Definition: vstream.c:1268
void msg_warn(const char *fmt,...)
Definition: msg.c:215
VSTRING * vstring_alloc(ssize_t len)
Definition: vstring.c:353
#define CHANGE_OWNER(user, group)
VSTRING * vstring_sprintf(VSTRING *vp, const char *format,...)
Definition: vstring.c:602
NORETURN msg_fatal(const char *fmt,...)
Definition: msg.c:249
VSTREAM * safe_open(const char *path, int flags, mode_t mode, struct stat *st, uid_t user, gid_t group, VSTRING *why)
Definition: safe_open.c:243
VSTRING * vstring_free(VSTRING *vp)
Definition: vstring.c:380
#define vstream_fileno(vp)
Definition: vstream.h:115
#define fstat(f, s)
Definition: warn_stat.h:20