Synopsis: The fts(3) functions can be tricked by a rogue user NetBSD versions: 1.5, -current prior to 2001/07/09 Thanks to: Christos Zoulas Reported in NetBSD Security Advisory: NetBSD-SA2001-016 Index: basesrc/lib/libc/gen/__fts13.c =================================================================== RCS file: /cvsroot/basesrc/lib/libc/gen/__fts13.c,v retrieving revision 1.28.4.1 retrieving revision 1.28.4.2 diff -c -p -r1.28.4.1 -r1.28.4.2 *** __fts13.c 2001/06/10 18:33:48 1.28.4.1 --- __fts13.c 2001/08/22 18:56:06 1.28.4.2 *************** static int fts_palloc __P((FTS *, size_ *** 99,104 **** --- 99,106 ---- static void fts_padjust __P((FTS *, FTSENT *)); static FTSENT *fts_sort __P((FTS *, FTSENT *, size_t)); static u_short fts_stat __P((FTS *, FTSENT *, int)); + static int fts_safe_changedir __P((const FTS *, const FTSENT *, int, + const char *)); #define ISDOT(a) (a[0] == '.' && (!a[1] || (a[1] == '.' && !a[2]))) *************** fts_read(sp) *** 397,403 **** * FTS_STOP or the fts_info field of the node. */ if (sp->fts_child) { ! if (CHDIR(sp, p->fts_accpath)) { p->fts_errno = errno; p->fts_flags |= FTS_DONTCHDIR; for (p = sp->fts_child; p; p = p->fts_link) --- 399,405 ---- * FTS_STOP or the fts_info field of the node. */ if (sp->fts_child) { ! if (fts_safe_changedir(sp, p, -1, p->fts_accpath)) { p->fts_errno = errno; p->fts_flags |= FTS_DONTCHDIR; for (p = sp->fts_child; p; p = p->fts_link) *************** name: t = sp->fts_path + NAPPEND(p->fts *** 495,530 **** } (void)close(p->fts_symfd); } else if (!(p->fts_flags & FTS_DONTCHDIR) && ! !ISSET(FTS_NOCHDIR)) { ! int fd; ! struct STAT sb; ! ! if ((fd = open("..", O_RDONLY)) == -1) { ! SET(FTS_STOP); ! return (NULL); ! } ! if (fstat(fd, &sb) == -1) { ! saved_errno = errno; ! (void)close(fd); ! errno = saved_errno; ! SET(FTS_STOP); ! return (NULL); ! } ! if (sb.st_ino != p->fts_parent->fts_ino || ! sb.st_dev != p->fts_parent->fts_dev) { ! (void)close(fd); ! errno = ENOENT; ! SET(FTS_STOP); ! return (NULL); ! } ! if (fchdir(fd) == -1) { ! saved_errno = errno; ! (void)close(fd); ! errno = saved_errno; ! SET(FTS_STOP); ! return (NULL); ! } ! (void)close(fd); } p->fts_info = p->fts_errno ? FTS_ERR : FTS_DP; return (sp->fts_cur = p); --- 497,505 ---- } (void)close(p->fts_symfd); } else if (!(p->fts_flags & FTS_DONTCHDIR) && ! fts_safe_changedir(sp, p->fts_parent, -1, "..")) { ! SET(FTS_STOP); ! return (NULL); } p->fts_info = p->fts_errno ? FTS_ERR : FTS_DP; return (sp->fts_cur = p); *************** fts_build(sp, type) *** 722,728 **** */ cderrno = 0; if (nlinks || type == BREAD) { ! if (FCHDIR(sp, dirfd(dirp))) { if (nlinks && type == BREAD) cur->fts_errno = errno; cur->fts_flags |= FTS_DONTCHDIR; --- 697,703 ---- */ cderrno = 0; if (nlinks || type == BREAD) { ! if (fts_safe_changedir(sp, cur, dirfd(dirp), NULL)) { if (nlinks && type == BREAD) cur->fts_errno = errno; cur->fts_flags |= FTS_DONTCHDIR; *************** mem1: saved_errno = errno; *** 871,877 **** */ if (descend && (type == BCHILD || !nitems) && (cur->fts_level == FTS_ROOTLEVEL ? ! FCHDIR(sp, sp->fts_rfd) : CHDIR(sp, ".."))) { cur->fts_info = FTS_ERR; SET(FTS_STOP); return (NULL); --- 846,853 ---- */ if (descend && (type == BCHILD || !nitems) && (cur->fts_level == FTS_ROOTLEVEL ? ! FCHDIR(sp, sp->fts_rfd) : ! fts_safe_changedir(sp, cur->fts_parent, -1, ".."))) { cur->fts_info = FTS_ERR; SET(FTS_STOP); return (NULL); *************** fts_maxarglen(argv) *** 1168,1171 **** --- 1144,1187 ---- if ((len = strlen(*argv)) > max) max = len; return (max + 1); + } + + /* + * Change to dir specified by fd or p->fts_accpath without getting + * tricked by someone changing the world out from underneath us. + * Assumes p->fts_dev and p->fts_ino are filled in. + */ + static int + fts_safe_changedir(sp, p, fd, path) + const FTS *sp; + const FTSENT *p; + int fd; + const char *path; + { + int oldfd = fd, ret = -1; + struct STAT sb; + + if (ISSET(FTS_NOCHDIR)) + return 0; + + if (fd < 0 && (fd = open(path, O_RDONLY)) == -1) + return -1; + + if (fstat(fd, &sb) == -1) + goto bail; + + if (sb.st_ino != p->fts_ino || sb.st_dev != p->fts_dev) { + errno = ENOENT; + goto bail; + } + + ret = fchdir(fd); + + bail: + if (oldfd < 0) { + int save_errno = errno; + (void)close(fd); + errno = save_errno; + } + return ret; }