1 /*        $NetBSD: safe_open.c,v 1.1.1.2 2013/01/02 18:59:14 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 #include <warn_stat.h>
91 
92 /* safe_open_exist - open existing file */
93 
safe_open_exist(const char * path,int flags,struct stat * fstat_st,VSTRING * why)94 static VSTREAM *safe_open_exist(const char *path, int flags,
95                                                 struct stat * fstat_st, VSTRING *why)
96 {
97     struct stat local_statbuf;
98     struct stat lstat_st;
99     int     saved_errno;
100     VSTREAM *fp;
101 
102     /*
103      * Open an existing file.
104      */
105     if ((fp = vstream_fopen(path, flags & ~(O_CREAT | O_EXCL), 0)) == 0) {
106           saved_errno = errno;
107           vstring_sprintf(why, "cannot open file: %m");
108           errno = saved_errno;
109           return (0);
110     }
111 
112     /*
113      * Examine the modes from the open file: it must have exactly one hard
114      * link (so that someone can't lure us into clobbering a sensitive file
115      * by making a hard link to it), and it must be a non-symlink file.
116      */
117     if (fstat_st == 0)
118           fstat_st = &local_statbuf;
119     if (fstat(vstream_fileno(fp), fstat_st) < 0) {
120           msg_fatal("%s: bad open file status: %m", path);
121     } else if (fstat_st->st_nlink != 1) {
122           vstring_sprintf(why, "file has %d hard links",
123                               (int) fstat_st->st_nlink);
124           errno = EPERM;
125     } else if (S_ISDIR(fstat_st->st_mode)) {
126           vstring_sprintf(why, "file is a directory");
127           errno = EISDIR;
128     }
129 
130     /*
131      * Look up the file again, this time using lstat(). Compare the fstat()
132      * (open file) modes with the lstat() modes. If there is any difference,
133      * either we followed a symlink while opening an existing file, someone
134      * quickly changed the number of hard links, or someone replaced the file
135      * after the open() call. The link and mode tests aren't really necessary
136      * in daemon processes. Set-uid programs, on the other hand, can be
137      * slowed down by arbitrary amounts, and there it would make sense to
138      * compare even more file attributes, such as the inode generation number
139      * on systems that have one.
140      *
141      * Grr. Solaris /dev/whatever is a symlink. We'll have to make an exception
142      * for symlinks owned by root. NEVER, NEVER, make exceptions for symlinks
143      * owned by a non-root user. This would open a security hole when
144      * delivering mail to a world-writable mailbox directory.
145      *
146      * Sebastian Krahmer of SuSE brought to my attention that some systems have
147      * changed their semantics of link(symlink, newpath), such that the
148      * result is a hardlink to the symlink. For this reason, we now also
149      * require that the symlink's parent directory is writable only by root.
150      */
151     else if (lstat(path, &lstat_st) < 0) {
152           vstring_sprintf(why, "file status changed unexpectedly: %m");
153           errno = EPERM;
154     } else if (S_ISLNK(lstat_st.st_mode)) {
155           if (lstat_st.st_uid == 0) {
156               VSTRING *parent_buf = vstring_alloc(100);
157               const char *parent_path = sane_dirname(parent_buf, path);
158               struct stat parent_st;
159               int     parent_ok;
160 
161               parent_ok = (stat(parent_path, &parent_st) == 0         /* not lstat */
162                                && parent_st.st_uid == 0
163                                && (parent_st.st_mode & (S_IWGRP | S_IWOTH)) == 0);
164               vstring_free(parent_buf);
165               if (parent_ok)
166                     return (fp);
167           }
168           vstring_sprintf(why, "file is a symbolic link");
169           errno = EPERM;
170     } else if (fstat_st->st_dev != lstat_st.st_dev
171                  || fstat_st->st_ino != lstat_st.st_ino
172 #ifdef HAS_ST_GEN
173                  || fstat_st->st_gen != lstat_st.st_gen
174 #endif
175                  || fstat_st->st_nlink != lstat_st.st_nlink
176                  || fstat_st->st_mode != lstat_st.st_mode) {
177           vstring_sprintf(why, "file status changed unexpectedly");
178           errno = EPERM;
179     }
180 
181     /*
182      * We are almost there...
183      */
184     else {
185           return (fp);
186     }
187 
188     /*
189      * End up here in case of fstat()/lstat() problems or inconsistencies.
190      */
191     vstream_fclose(fp);
192     return (0);
193 }
194 
195 /* safe_open_create - create new file */
196 
safe_open_create(const char * path,int flags,mode_t mode,struct stat * st,uid_t user,gid_t group,VSTRING * why)197 static VSTREAM *safe_open_create(const char *path, int flags, mode_t mode,
198                       struct stat * st, uid_t user, gid_t group, VSTRING *why)
199 {
200     VSTREAM *fp;
201 
202     /*
203      * Create a non-existing file. This relies on O_CREAT | O_EXCL to not
204      * follow symbolic links.
205      */
206     if ((fp = vstream_fopen(path, flags | (O_CREAT | O_EXCL), mode)) == 0) {
207           vstring_sprintf(why, "cannot create file exclusively: %m");
208           return (0);
209     }
210 
211     /*
212      * Optionally look up the file attributes.
213      */
214     if (st != 0 && fstat(vstream_fileno(fp), st) < 0)
215           msg_fatal("%s: bad open file status: %m", path);
216 
217     /*
218      * Optionally change ownership after creating a new file. If there is a
219      * problem we should not attempt to delete the file. Something else may
220      * have opened the file in the mean time.
221      */
222 #define CHANGE_OWNER(user, group) (user != (uid_t) -1 || group != (gid_t) -1)
223 
224     if (CHANGE_OWNER(user, group)
225           && fchown(vstream_fileno(fp), user, group) < 0) {
226           msg_warn("%s: cannot change file ownership: %m", path);
227     }
228 
229     /*
230      * We are almost there...
231      */
232     else {
233           return (fp);
234     }
235 
236     /*
237      * End up here in case of trouble.
238      */
239     vstream_fclose(fp);
240     return (0);
241 }
242 
243 /* safe_open - safely open or create file */
244 
safe_open(const char * path,int flags,mode_t mode,struct stat * st,uid_t user,gid_t group,VSTRING * why)245 VSTREAM *safe_open(const char *path, int flags, mode_t mode,
246                       struct stat * st, uid_t user, gid_t group, VSTRING *why)
247 {
248     VSTREAM *fp;
249 
250     switch (flags & (O_CREAT | O_EXCL)) {
251 
252           /*
253            * Open an existing file, carefully.
254            */
255     case 0:
256           return (safe_open_exist(path, flags, st, why));
257 
258           /*
259            * Create a new file, carefully.
260            */
261     case O_CREAT | O_EXCL:
262           return (safe_open_create(path, flags, mode, st, user, group, why));
263 
264           /*
265            * Open an existing file or create a new one, carefully. When opening
266            * an existing file, we are prepared to deal with "no file" errors
267            * only. When creating a file, we are prepared for "file exists"
268            * errors only. Any other error means we better give up trying.
269            */
270     case O_CREAT:
271           fp = safe_open_exist(path, flags, st, why);
272           if (fp == 0 && errno == ENOENT) {
273               fp = safe_open_create(path, flags, mode, st, user, group, why);
274               if (fp == 0 && errno == EEXIST)
275                     fp = safe_open_exist(path, flags, st, why);
276           }
277           return (fp);
278 
279           /*
280            * Interface violation. Sorry, but we must be strict.
281            */
282     default:
283           msg_panic("safe_open: O_EXCL flag without O_CREAT flag");
284     }
285 }
286