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