1/*
2 * Copyright (c) 2007 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23/*
24 * FILE: safecalls.c
25 * AUTH: Soren Spies (sspies)
26 * DATE: 16 June 2006 (Copyright Apple Computer, Inc)
27 * DESC: picky/safe syscalls
28 *
29 * Security functions
30 * the first argument limits the scope of the operation
31 *
32 * Pretty much every function is implemented as
33 * savedir = open(".", O_RDONLY);
34 * schdirparent()->sopen()->spolicy()
35 * <operation>(child)
36 * fchdir(savedir)
37 *
38 */
39
40#include <errno.h>
41#include <dirent.h>
42#include <fcntl.h>
43#include <fts.h>
44#include <libgen.h>
45#include <limits.h>
46#include <sys/mount.h>
47#include <sys/param.h>  // MAXBSIZE, MIN
48#include <sys/stat.h>
49#include <string.h>
50#include <stdio.h>      // rename(2)?
51#include <stdlib.h>     // malloc(3)
52#include <sys/types.h>
53#include <unistd.h>
54#include <sys/ucred.h>
55
56#include <IOKit/kext/kextmanager_types.h>
57
58#include <IOKit/kext/OSKextPrivate.h>
59#ifndef kOSKextLogCacheFlag
60#define kOSKextLogCacheFlag kOSKextLogArchiveFlag
61#endif  // no kOSKextLogCacheFlag
62
63#define STRICT_SAFETY 0     // since our wrappers need to call the real calls
64#include "safecalls.h"      // w/o STRICT_SAFETY, will #define mkdir, etc
65#include "kext_tools_util.h"
66
67#define RESTOREDIR(savedir) do { if (savedir != -1 && restoredir(savedir))  \
68                 OSKextLog(/* kext */ NULL, \
69                     kOSKextLogErrorLevel | kOSKextLogCacheFlag, \
70                     "%s: ALERT: couldn't restore CWD", __func__); \
71    } while(0)
72
73// Seed errno since strlXXX routines do not set it.  This will make
74// downstream error messages more meaningful (since we're often logging the
75// errno value and message).  COMPILE_TIME_ASSERT() break schdirparent().
76#define PATHCPY(dst, src) do { \
77            /* COMPILE_TIME_ASSERT(sizeof(dst) == PATH_MAX); */ \
78            Boolean useErrno = (errno == 0); \
79            if (useErrno)       errno = ENAMETOOLONG; \
80            if (strlcpy(dst, src, PATH_MAX) >= PATH_MAX)  goto finish; \
81            if (useErrno)       errno = 0; \
82} while(0)
83#define PATHCAT(dst, src) do { \
84            COMPILE_TIME_ASSERT(sizeof(dst) == PATH_MAX); \
85            Boolean useErrno = (errno == 0); \
86            if (useErrno)       errno = ENAMETOOLONG; \
87            if (strlcat(dst, src, PATH_MAX) >= PATH_MAX)  goto finish; \
88            if (useErrno)       errno = 0; \
89} while(0)
90
91// given that we call this function twice on an error path, it is tempting
92// to use getmntinfo(3) but it's not threadsafe ... :P
93// called on error paths; shouldn't use PATH*()
94static int findmnt(dev_t devid, char mntpt[MNAMELEN])
95{
96    int rval = ELAST + 1;
97    int i, nmnts = getfsstat(NULL, 0, MNT_NOWAIT);
98    int bufsz;
99    struct statfs *mounts = NULL;
100
101    if (nmnts <= 0)     goto finish;
102
103    bufsz = nmnts * sizeof(struct statfs);
104    if (!(mounts = malloc(bufsz)))                  goto finish;
105    if (-1 == getfsstat(mounts, bufsz, MNT_NOWAIT)) goto finish;
106
107    // loop looking for dev_t in the statfs structs
108    for (i = 0; i < nmnts; i++) {
109        struct statfs *sfs = &mounts[i];
110
111        if (sfs->f_fsid.val[0] == devid) {
112            strlcpy(mntpt, sfs->f_mntonname, PATH_MAX);
113            rval = 0;
114            break;
115        }
116    }
117
118finish:
119    if (mounts)     free(mounts);
120    return rval;
121}
122
123// currently checks to make sure on same volume
124// other checks could include:
125// - "really owned by <foo> on root/<foo>-mounted volume"
126static int spolicy(int scopefd, int candfd)
127{
128    int bsderr = -1;
129    struct stat candsb, scopesb;
130    char path[PATH_MAX] = "<unknown>";
131
132    if ((bsderr = fstat(candfd, &candsb)))    goto finish;  // trusty fstat()
133    if ((bsderr = fstat(scopefd, &scopesb)))  goto finish;  // still there?
134
135    // make sure st_dev matches
136    if (candsb.st_dev != scopesb.st_dev ) {
137        bsderr = -1;
138        errno = EPERM;
139        char scopemnt[MNAMELEN];
140
141        if (findmnt(scopesb.st_dev, scopemnt) == 0) {
142            (void)fcntl(candfd, F_GETPATH, path);
143
144            OSKextLog(/* kext */ NULL,
145            kOSKextLogErrorLevel | kOSKextLogCacheFlag | kOSKextLogFileAccessFlag,
146            "ALERT: %s does not appear to be on %s.", path, scopemnt);
147        } else {
148            OSKextLog(NULL,
149                      kOSKextLogErrorLevel,
150                      "%s - find mount failed: errno %d %s.",
151                      __FUNCTION__, errno, strerror(errno));
152            OSKextLog(/* kext */ NULL,
153                kOSKextLogErrorLevel | kOSKextLogGeneralFlag | kOSKextLogFileAccessFlag,
154                "ALERT: dev_t mismatch (%d != %d).",
155            candsb.st_dev, scopesb.st_dev);
156        }
157        goto finish;
158    }
159
160    // warn about non-root owners
161    // (.disk_label can be written while owners are ignored :P)
162    if (candsb.st_uid != 0) {
163
164        // could try to trim pathname to basename?
165        (void)fcntl(candfd, F_GETPATH, path);
166
167        OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
168            "WARNING: %s: owner not root!", path);
169    }
170
171finish:
172    return bsderr;
173}
174
175
176int schdirparent(int fdvol, const char *path, int *olddir, char child[PATH_MAX])
177{
178    int bsderr = -1;
179    int dirfd = -1, savedir = -1;
180    char parent[PATH_MAX];
181
182    if (olddir)     *olddir = -1;
183    if (!path)      goto finish;
184
185    // make a copy of path in case our dirname() ever modifies the buffer
186    PATHCPY(parent, path);
187    PATHCPY(parent, dirname(parent));
188
189    // make sure parent is on specified volume
190    if (-1 == (dirfd = open(parent, O_RDONLY, 0)))  goto finish;
191    errno = 0;
192    if (spolicy(fdvol, dirfd)) {
193        if (errno == EPERM)
194            OSKextLog(/* kext */ NULL,
195            kOSKextLogErrorLevel | kOSKextLogGeneralFlag | kOSKextLogFileAccessFlag,
196            "Policy violation opening %s.", parent);
197        goto finish;
198    }
199
200    // save old directory if requested
201    if (olddir) {
202        if (-1 == (savedir = open(".", O_RDONLY)))  goto finish;
203    }
204
205    // attempt to switch to the directory
206    if ((bsderr = fchdir(dirfd)))               goto finish;
207
208    // set output parameters
209    if (olddir)             *olddir = savedir;
210    if (child) {
211        PATHCPY(child, path);
212        PATHCPY(child, basename(child));
213    }
214
215finish:
216    if (bsderr) {
217        if (savedir != -1)  close(savedir);
218	if (olddir)         *olddir = -1;
219    }
220    if (dirfd != -1)        close(dirfd);
221
222    return bsderr;
223}
224
225// have to rely on schdirparent so we don't accidentally O_CREAT
226int sopen(int fdvol, const char *path, int flags, mode_t mode /*'...' fancier*/)
227{
228    int rfd = -1;
229    int candfd = -1;
230    char child[PATH_MAX];
231    int savedir = -1;
232
233    // omitting O_NOFOLLOW except when creating gives better errors
234    // flags |= O_NOFOLLOW;
235
236    // if creating, make sure it doesn't exist (O_NOFOLLOW for good measure)
237    if (flags & O_CREAT)
238        flags |= O_EXCL | O_NOFOLLOW;
239
240    if (schdirparent(fdvol, path, &savedir, child))     goto finish;
241    if (-1 == (candfd = open(child, flags, mode)))      goto finish;
242
243    // schdirparent checked the parent; here we check the child (6393648)
244    if (spolicy(fdvol, candfd))                         goto finish;
245
246    rfd = candfd;
247
248finish:
249    if (candfd != -1 && rfd != candfd) {
250        close(candfd);
251    }
252    RESTOREDIR(savedir);
253
254    return rfd;
255}
256
257int schdir(int fdvol, const char *path, int *savedir)
258{
259    char cpath[PATH_MAX];
260
261    // X could switch to snprintf()
262    PATHCPY(cpath, path);
263    PATHCAT(cpath, "/.");
264
265    return schdirparent(fdvol, cpath, savedir, NULL);
266
267finish:
268    return -1;
269}
270
271int restoredir(int savedir)
272{
273    int cherr = -1, clerr = -1;
274
275    if (savedir != -1) {
276        cherr = fchdir(savedir);
277        clerr = close(savedir);
278    }
279
280    return cherr ? cherr : clerr;
281}
282
283int smkdir(int fdvol, const char *path, mode_t mode)
284{
285    int bsderr = -1;
286    int savedir = -1;
287    char child[PATH_MAX];
288
289    if (schdirparent(fdvol, path, &savedir, child))  goto finish;
290    if ((bsderr = mkdir(child, mode)))      goto finish;
291
292finish:
293    RESTOREDIR(savedir);
294    return bsderr;
295}
296
297int srmdir(int fdvol, const char *path)
298{
299    int bsderr = -1;
300    char child[PATH_MAX];
301    int savedir = -1;
302
303    if (schdirparent(fdvol, path, &savedir, child))  goto finish;
304
305    bsderr = rmdir(child);
306
307finish:
308    RESTOREDIR(savedir);
309    return bsderr;
310}
311
312int sunlink(int fdvol, const char *path)
313{
314    int bsderr = -1;
315    char child[PATH_MAX];
316    int savedir = -1;
317
318    if (schdirparent(fdvol, path, &savedir, child))  goto finish;
319
320    bsderr = unlink(child);
321
322finish:
323    RESTOREDIR(savedir);
324    return bsderr;
325}
326
327
328// taking a path and a filename is sort of annoying for clients
329// so we "auto-strip" newname if it happens to be a path
330int srename(int fdvol, const char *oldpath, const char *newpath)
331{
332    int bsderr = -1;
333    int savedir = -1;
334    char oldname[PATH_MAX];
335    char newname[PATH_MAX];
336
337    // calculate netname first since schdirparent uses basename
338    PATHCPY(newname, newpath);
339    PATHCPY(newname, basename(newname));
340    if (schdirparent(fdvol, oldpath, &savedir, oldname))        goto finish;
341
342    bsderr = rename(oldname, newname);
343
344finish:
345    RESTOREDIR(savedir);
346    return bsderr;
347}
348
349
350int szerofile(int fdvol, const char *toErase)
351{
352    int bsderr = -1;
353    int zfd = -1;
354    struct stat sb;
355    uint64_t bytesLeft;     // why is off_t signed?
356    size_t bufsize;
357    ssize_t thisTime;
358    void *buf = NULL;
359
360    zfd = sopen(fdvol, toErase, O_WRONLY, 0);
361    if (zfd == -1) {
362        if (errno == ENOENT)
363            bsderr = 0;
364        goto finish;
365    }
366
367    if (fstat(zfd, &sb))                    goto finish;
368    if (sb.st_size == 0) {
369        bsderr = 0;
370        goto finish;
371    }
372    bufsize = (size_t)MIN(sb.st_size, MAXBSIZE);
373    if (!(buf = calloc(1, bufsize)))        goto finish;
374
375    // and loop writing the zeros
376    for (bytesLeft = sb.st_size; bytesLeft > 0; bytesLeft -= thisTime) {
377        thisTime = (ssize_t)MIN(bytesLeft, bufsize);
378
379        if (write(zfd, buf, thisTime) != thisTime)    goto finish;
380    }
381
382    // our job is done, but the space is useless so attempt to truncate
383    (void)ftruncate(zfd, 0LL);
384
385    bsderr = 0;
386
387finish:
388    if (zfd != -1)      close(zfd);
389    if (buf)            free(buf);
390
391    return bsderr;
392}
393
394// stolen with gratitude from TAOcommon's TAOCFURLDelete
395int sdeepunlink(int fdvol, char *path)
396{
397    int             rval = ELAST + 1;
398    int             firstErrno = 0;     // FTS clears errno at the end :P
399
400    char        *   const pathv[2] = { path, NULL };
401    int             ftsoptions = 0;
402    FTS         *   fts;
403    FTSENT      *   fent;
404
405    // opting for security, of course
406    ftsoptions |= FTS_PHYSICAL;         // see symlinks
407    ftsoptions |= FTS_XDEV;             // don't cross devices
408    ftsoptions |= FTS_NOSTAT;           // fts_info tells us enough
409    ftsoptions |= FTS_NOCHDIR;          // only we should be using [f]chdir
410//  ftsoptions |= FTS_COMFOLLOW;        // if 'path' is symlink, remove link
411//  ftsoptions |= FTS_SEEDOT;           // we don't need "."
412
413    rval = -1;
414    if ((fts = fts_open(pathv, ftsoptions, NULL)) == NULL)  goto finish;
415    rval = 0;
416
417    // and here we go
418    while ((fent = fts_read(fts)) /* && !rval ?? */) {
419        switch (fent->fts_info) {
420            case FTS_DC:        // directory that causes a cycle in the tree
421            case FTS_D:         // directory being visited in pre-order
422            case FTS_DOT:       // file named '.' or '..' (not requested)
423                break;
424
425            case FTS_DNR:       // directory which cannot be read
426            case FTS_ERR:       // generic fcts_errno-borne error
427            case FTS_NS:        // file for which stat(s) failed (not requested)
428                // rval |= fent->fts_errno;
429                if (!firstErrno)        firstErrno = fent->fts_errno;
430                break;
431
432            case FTS_SL:        // symbolic link
433            case FTS_SLNONE:    // symbolic link with a non-existent target
434            case FTS_DEFAULT:   // good file of type unknown to FTS (block? ;)
435            case FTS_F:         // regular file
436            case FTS_NSOK:      // no stat(2) requested (but not a dir?)
437            default:            // in case FTS gets smarter in the future
438                // XX need to port RECERR() from update_boot.c
439                rval |= sunlink(fdvol, fent->fts_accpath);
440                if (!firstErrno)        firstErrno = errno;
441                break;
442
443            case FTS_DP:        // directory being visited in post-order
444                // XX need to port RECERR() from update_boot.c
445                rval |= srmdir(fdvol, fent->fts_accpath);
446                if (!firstErrno)        firstErrno = errno;
447                break;
448        } // switch
449    } // while (fts_read())
450
451    // close the iterator now
452    if (fts_close(fts) < 0) {
453        OSKextLog(/* kext */ NULL,
454            kOSKextLogErrorLevel | kOSKextLogGeneralFlag | kOSKextLogFileAccessFlag,
455            "fts_close failed - %s.", strerror(errno));
456    }
457
458    if (firstErrno) {
459        rval = -1;
460        errno = firstErrno;
461    }
462
463finish:
464    // fts_read() clears errno if it completed
465    if (rval == 0 && errno) {
466        rval = -1;
467    }
468
469    return rval;
470}
471
472int sdeepmkdir(int fdvol, const char *path, mode_t mode)
473{
474    int bsderr = -1;
475    struct stat sb;
476    char parent[PATH_MAX];
477
478    if (strlen(path) == 0)      goto finish;        // protection?
479
480    // trusting that stat(".") will always do the right thing
481    if (0 == stat(path, &sb)) {
482        if ((sb.st_mode & S_IFMT) != S_IFDIR) {
483            bsderr = ENOTDIR;
484            goto finish;
485        } else {
486            bsderr = 0;             // base case (dir exists)
487            goto finish;
488        }
489    } else if (errno != ENOENT) {
490        goto finish;                // bsderr = -1 -> errno
491    } else {
492        PATHCPY(parent, path);
493        PATHCPY(parent, dirname(parent));
494
495        // and recurse since it wasn't there
496        if ((bsderr = sdeepmkdir(fdvol, parent, mode)))     goto finish;
497    }
498
499    // all parents made; top-level still needed
500    bsderr = smkdir(fdvol, path, mode);
501
502finish:
503    return bsderr;
504}
505
506
507static int
508_copyfiledata(int srcfd, struct stat *srcsb, int dstfdvol, const char *dstpath)
509{
510    int bsderr = -1;
511    int dstfd = -1;
512    void *buf = NULL;       // up to MAXBSIZE on the stack is a bad idea
513    size_t bufsize;
514    ssize_t thisTime;
515    off_t bytesLeft;
516
517    // nuke/open the destination
518    (void)sunlink(dstfdvol, dstpath);
519    dstfd = sopen(dstfdvol, dstpath, O_CREAT|O_WRONLY, srcsb->st_mode|S_IWUSR);
520    if (dstfd == -1)        goto finish;
521
522    // and loop with our handy buffer
523    bufsize = (size_t)MIN(srcsb->st_size, MAXBSIZE);
524    if (!(buf = malloc(bufsize)))      goto finish;;
525    for (bytesLeft = srcsb->st_size; bytesLeft > 0; bytesLeft -= thisTime) {
526        thisTime = (ssize_t)MIN(bytesLeft, (unsigned int)bufsize);
527
528        if (read(srcfd, buf, thisTime) != thisTime)     goto finish;
529        if (write(dstfd, buf, thisTime) != thisTime)    goto finish;
530    }
531
532    // apply final permissions
533    if ((bsderr = fchmod(dstfd, srcsb->st_mode)))  goto finish;
534    // kextcache doesn't currently look into the Apple_Boot, so we'll skip times
535
536finish:
537    if (dstfd != -1)    close(dstfd);
538    if (buf)            free(buf);
539
540    return bsderr;
541}
542
543// for now, we only support a flat set of files; no recursion
544static int
545_copysubitems(int srcfdvol, const char *srcdir, int dstfdvol,const char *dstdir)
546{
547    int bsderr = -1;
548    DIR *dir = NULL;
549    struct dirent dentry, *dp;
550    char srcpath[PATH_MAX], dstpath[PATH_MAX];
551
552    // scopyitem() will also validate srcfdvol for each entry
553    if (!(dir = opendir(srcdir)))
554        goto finish;
555    if (spolicy(srcfdvol, dirfd(dir)))
556        goto finish;
557
558    while (0 == (bsderr = readdir_r(dir, &dentry, &dp)) && dp) {
559        char *fname = dp->d_name;
560
561        // skip "." and ".."
562        if ((fname[0] == '.' && fname[1] == '\0') ||
563            (fname[0] == '.' && fname[1] == '.' && fname[2] == '\0'))
564                continue;
565
566        // set up source path for child file
567        PATHCPY(srcpath, srcdir);
568        PATHCAT(srcpath, "/");
569        PATHCAT(srcpath, fname);
570
571        // and corresponding destination path
572        PATHCPY(dstpath, dstdir);
573        PATHCAT(dstpath, "/");
574        PATHCAT(dstpath, fname);
575
576        // recurse back to scopyitem()
577        bsderr = scopyitem(srcfdvol, srcpath, dstfdvol, dstpath);
578        if (bsderr)   goto finish;
579    }
580
581finish:
582    if (dir)            closedir(dir);
583
584    return bsderr;
585}
586
587int
588scopyitem(int srcfdvol, const char *srcpath, int dstfdvol, const char *dstpath)
589{
590    int bsderr = -1;
591    int srcfd = -1;
592    struct stat srcsb;
593    char dstparent[PATH_MAX];
594    mode_t dirmode;
595
596    // figure out parent directory mode
597    if (-1 == (srcfd = sopen(srcfdvol, srcpath, O_RDONLY, 0)))    goto finish;
598    if (fstat(srcfd, &srcsb))                       goto finish;
599    dirmode = ((srcsb.st_mode&~S_IFMT) | S_IWUSR | S_IXUSR /* u+wx */);
600    if (dirmode & S_IRGRP)      dirmode |= S_IXGRP;     // add conditional o+x
601    if (dirmode & S_IROTH)      dirmode |= S_IXOTH;
602
603    // and recursively create the parent directory
604    PATHCPY(dstparent, dstpath);
605    PATHCPY(dstparent, dirname(dstparent));
606
607    if ((bsderr = sdeepmkdir(dstfdvol, dstparent, dirmode)))        goto finish;
608
609    // should we let _copysubitems will call us back
610    switch ((srcsb.st_mode & S_IFMT)) {
611        case S_IFREG:
612            bsderr = _copyfiledata(srcfd, &srcsb, dstfdvol, dstpath);
613            break;
614
615        case S_IFDIR:
616            bsderr = _copysubitems(srcfdvol, srcpath, dstfdvol, dstpath);
617            break;
618
619        default:
620            bsderr = EFTYPE;
621            break;
622    }
623
624finish:
625    if (srcfd != -1)    close(srcfd);
626
627    return bsderr;
628}
629