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.34
retrieving revision 1.35
diff -c -p -r1.34 -r1.35
*** basesrc/lib/libc/gen/__fts13.c	2001/06/06 14:10:46	1.34
--- basesrc/lib/libc/gen/__fts13.c	2001/07/09 21:33:03	1.35
*************** 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;
  }