zsh-workers
 help / color / mirror / code / Atom feed
* files module improvements
@ 1996-12-31 12:51 Zefram
  1997-01-02  3:52 ` Zoltan Hidvegi
  0 siblings, 1 reply; 3+ messages in thread
From: Zefram @ 1996-12-31 12:51 UTC (permalink / raw)
  To: Z Shell workers mailing list

-----BEGIN PGP SIGNED MESSAGE-----

This patch makes some improvements to the files module.  The static
buffers are removed, to make it reentrant.  rm -r is made safer, and
can now handle arbitrarily long pathnames.

There are some remaining problems:

* Interrupt signals are not handled.  I'll do another patch for this.

* rm -r can run out of file descriptors in deep trees.  Fixing this would
  require going back up the tree with chdir("..") and then checking that
  the tree wasn't moved while we were deleting it; it makes it easier for
  rm to fail.  I think this needs to be done, but I don't like it.

* rm -r can now lose the current directory.  This is particularly easy
  in unreadable directories.  There's no avoiding this, as it's a design
  flaw in Unix.  There is a need for zsh to cope with unexpected changes
  of directory: some failure modes of cd can already do a chdir("/")
  and leave $PWD as something else.

This patch adds a function lchdir() that does a paranoid directory change,
not following symlinks (or at least not without noticing).  It would be
nice to make this functionality available directly to the user, via an
option to cd (cd -l?).

 -zefram

      *** Src/utils.c	1996/12/30 15:13:52	1.48
      --- Src/utils.c	1996/12/31 05:42:56
      ***************
      *** 3308,3313 ****
      --- 3308,3371 ----
            return r;
        }
        
      + /* Change directory, without following symlinks.  Returns 0 on success, -1 *
      +  * on failure.  Sets errno to ENOTDIR if any symlinks are encountered.  If *
      +  * fchdir() fails, or the current directory is unreadable, we might end up *
      +  * in an unwanted directory in case of failure.                            */
      + 
      + /**/
      + int
      + lchdir(char const *path)
      + {
      + #ifndef HAVE_LSTAT
      +     return zchdir(path);
      + #else /* HAVE_LSTAT */
      +     char buf[PATH_MAX + 1], *ptr;
      +     char const *pptr;
      +     int olddir = open(".", O_RDONLY), err;
      +     struct stat st1, st2;
      + 
      +     if(*path == '/')
      + 	chdir("/");
      +     for(;;) {
      + 	while(*path == '/')
      + 	    path++;
      + 	if(!*path) {
      + 	    close(olddir);
      + 	    return 0;
      + 	}
      + 	for(pptr = path; *++pptr && *pptr != '/'; ) ;
      + 	if(pptr - path > PATH_MAX) {
      + 	    err = ENAMETOOLONG;
      + 	    break;
      + 	}
      + 	for(ptr = buf; path != pptr; )
      + 	    *ptr++ = *path++;
      + 	*ptr = 0;
      + 	if(lstat(buf, &st1)) {
      + 	    err = errno;
      + 	    break;
      + 	}
      + 	if(!S_ISDIR(st1.st_mode)) {
      + 	    err = ENOTDIR;
      + 	    break;
      + 	}
      + 	if(chdir(buf) || lstat(".", &st2)) {
      + 	    err = errno;
      + 	    break;
      + 	}
      + 	if(st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) {
      + 	    err = ENOTDIR;
      + 	    break;
      + 	}
      +     }
      +     fchdir(olddir);
      +     close(olddir);
      +     errno = err;
      +     return -1;
      + #endif /* HAVE_LSTAT */
      + }
      + 
        #ifdef DEBUG
        
        /**/
      *** Src/Modules/files.c	1996/12/24 17:07:33	1.6
      --- Src/Modules/files.c	1996/12/31 05:42:09
      ***************
      *** 35,43 ****
        
        #include "files.pro"
        
      - static char buf[PATH_MAX * 2];
      - static char rbuf[PATH_MAX];
      - 
        /**/
        static int
        ask(void)
      --- 35,40 ----
      ***************
      *** 132,138 ****
            if(p) {
        	struct stat st;
        
      ! 	if(!stat(rpath, &st) && S_ISDIR(st.st_mode))
        	    return 0;
            }
            oumask = umask(0);
      --- 129,135 ----
            if(p) {
        	struct stat st;
        
      ! 	if(!lstat(rpath, &st) && S_ISDIR(st.st_mode))
        	    return 0;
            }
            oumask = umask(0);
      ***************
      *** 189,194 ****
      --- 186,193 ----
            int flags, space, err = 0;
            char **a, *ptr, *rp;
            struct stat st;
      +     char buf[PATH_MAX * 2 + 1];
      + 
        
            if(func == BIN_MV) {
        	move = rename;
      ***************
      *** 229,239 ****
            havedir:
            strcpy(buf, *a);
            *a = NULL;
      !     space = PATH_MAX - 2 - ztrlen(buf);
            rp = strchr(buf, 0);
            *rp++ = '/';
            for(; *args; args++) {
      ! 	if(ztrlen(*args) > PATH_MAX - 1) {
        	    zwarnnam(nam, "%s: %e", *args, ENAMETOOLONG);
        	    err = 1;
        	    continue;
      --- 228,238 ----
            havedir:
            strcpy(buf, *a);
            *a = NULL;
      !     space = PATH_MAX - 1 - ztrlen(buf);
            rp = strchr(buf, 0);
            *rp++ = '/';
            for(; *args; args++) {
      ! 	if(ztrlen(*args) > PATH_MAX) {
        	    zwarnnam(nam, "%s: %e", *args, ENAMETOOLONG);
        	    err = 1;
        	    continue;
      ***************
      *** 260,270 ****
        {
            struct stat st;
            char *qbuf;
      !     strcpy(rbuf, unmeta(p));
            qbuf = unmeta(q);
            if(flags & MV_NODIRS) {
        	errno = EISDIR;
      ! 	if(lstat(rbuf, &st) || S_ISDIR(st.st_mode)) {
        	    zwarnnam(nam, "%s: %e", p, errno);
        	    return 1;
        	}
      --- 259,270 ----
        {
            struct stat st;
            char *qbuf;
      !     char pbuf[PATH_MAX + 1];
      !     strcpy(pbuf, unmeta(p));
            qbuf = unmeta(q);
            if(flags & MV_NODIRS) {
        	errno = EISDIR;
      ! 	if(lstat(pbuf, &st) || S_ISDIR(st.st_mode)) {
        	    zwarnnam(nam, "%s: %e", p, errno);
        	    return 1;
        	}
      ***************
      *** 293,299 ****
        	if(flags & MV_FORCE)
        	    unlink(qbuf);
            }
      !     if(move(rbuf, qbuf)) {
        	zwarnnam(nam, "%s: %e", p, errno);
        	return 1;
            }
      --- 293,299 ----
        	if(flags & MV_FORCE)
        	    unlink(qbuf);
            }
      !     if(move(pbuf, qbuf)) {
        	zwarnnam(nam, "%s: %e", p, errno);
        	return 1;
            }
      ***************
      *** 306,374 ****
        static int
        bin_rm(char *nam, char **args, char *ops, int func)
        {
      !     int err = 0;
        
      !     for(; *args; args++)
      ! 	err |= dorm(nam, *args, ops);
      !     return ops['f'] ? 0 : err;
        }
        
        /**/
        static int
      ! dorm(char *nam, char *arg, char *ops)
        {
            struct stat st;
      -     char *rp = unmeta(arg);
        
      !     if(!rp) {
      ! 	if(!ops['f'])
      ! 	    zwarnnam(nam, "%s: %e", arg, ENAMETOOLONG);
      ! 	return 1;
      !     }
      !     if((!ops['d'] || !ops['f']) && !stat(rp, &st)) {
        	if(!ops['d'] && S_ISDIR(st.st_mode)) {
      ! 	    if(ops['r']) {
      ! 		DIR *d;
      ! 		char *pos, *fn;
      ! 		int err = 0, space;
      ! 		d = opendir(rp);
      ! 		if(!d) {
      ! 		    if(!ops['f'])
      ! 			zwarnnam(nam, "%s: %e", arg, errno);
      ! 		    return 1;
      ! 		}
      ! 		if(arg != buf)
      ! 		    strcpy(buf, arg);
      ! 		space = PATH_MAX - 2 - ztrlen(buf);
      ! 		pos = strchr(buf, 0);
      ! 		*pos++ = '/';
      ! 		while((fn = zreaddir(d, 1))) {
      ! 		    if(ztrlen(fn) > space) {
      ! 			pos[-1] = 0;
      ! 			zwarnnam(nam, "%s: %e", buf, ENAMETOOLONG);
      ! 			err = 1;
      ! 			continue;
      ! 		    }
      ! 		    strcpy(pos, fn);
      ! 		    err |= dorm(nam, buf, ops);
      ! 		}
      ! 		closedir(d);
      ! 		pos[-1] = 0;
      ! 		if(!ops['f'] && ops['i']) {
      ! 		    nicezputs(nam, stderr);
      ! 		    fputs(": remove `", stderr);
      ! 		    nicezputs(buf, stderr);
      ! 		    fputs("'? ", stderr);
      ! 		    fflush(stderr);
      ! 		    if(!ask())
      ! 			return err;
      ! 		}
      ! 		if(!rmdir(unmeta(buf)))
      ! 		    return err;
      ! 		if(!ops['f'])
      ! 		    zwarnnam(nam, "%s: %e", buf, errno);
      ! 		return 1;
      ! 	    }
        	    if(!ops['f'])
        		zwarnnam(nam, "%s: %e", arg, EISDIR);
        	    return 1;
      --- 306,334 ----
        static int
        bin_rm(char *nam, char **args, char *ops, int func)
        {
      !     int err = 0, len;
      !     char *rp;
        
      !     for(; !(err & 2) && *args; args++) {
      ! 	rp = ztrdup(*args);
      ! 	len = strlen(rp + 1);
      ! 	unmetafy(rp, NULL);
      ! 	err |= dorm(nam, *args, rp, ops, 1);
      ! 	zfree(rp, len);
      !     }
      !     return ops['f'] ? 0 : !!err;
        }
        
        /**/
        static int
      ! dorm(char *nam, char *arg, char *rp, char *ops, int first)
        {
            struct stat st;
        
      !     if((!ops['d'] || !ops['f']) && !lstat(rp, &st)) {
        	if(!ops['d'] && S_ISDIR(st.st_mode)) {
      ! 	    if(ops['r'])
      ! 		return dormr(nam, arg, rp, ops, first);
        	    if(!ops['f'])
        		zwarnnam(nam, "%s: %e", arg, EISDIR);
        	    return 1;
      ***************
      *** 393,398 ****
      --- 353,415 ----
            }
            if(!unlink(rp))
        	return 0;
      +     if(!ops['f'])
      + 	zwarnnam(nam, "%s: %e", arg, errno);
      +     return 1;
      + }
      + 
      + /**/
      + static int
      + dormr(char *nam, char *arg, char *rp, char *ops, int first)
      + {
      +     char *fn;
      +     DIR *d;
      +     int err = 0;
      +     int pwd = open(".", O_RDONLY);
      + 
      +     if(first ? zchdir(rp) : lchdir(rp)) {
      + 	if(!ops['f'])
      + 	    zwarnnam(nam, "%s: %e", arg, errno);
      + 	return 1;
      +     }
      + 
      +     d = opendir(".");
      +     if(!d) {
      + 	if(!ops['f'])
      + 	    zwarnnam(nam, "%s: %e", arg, errno);
      + 	return 1 - fchdir(pwd);
      +     }
      +     while((fn = zreaddir(d, 1))) {
      + 	char *narg = tricat(arg, "/", fn);
      + 
      + 	err |= dorm(nam, narg, unmeta(fn), ops, 0);
      + 	zsfree(narg);
      + 	if(err & 2) {
      + 	    closedir(d);
      + 	    close(pwd);
      + 	    return 2;
      + 	}
      +     }
      +     closedir(d);
      +     if(fchdir(pwd)) {
      + 	close(pwd);
      + 	if(!ops['f'])
      + 	    zwarnnam(nam, "failed to return to previous directory: %e",
      + 		NULL, errno);
      + 	return 2;
      +     }
      +     close(pwd);
      +     if(!ops['f'] && ops['i']) {
      + 	nicezputs(nam, stderr);
      + 	fputs(": remove `", stderr);
      + 	nicezputs(arg, stderr);
      + 	fputs("'? ", stderr);
      + 	fflush(stderr);
      + 	if(!ask())
      + 	    return err;
      +     }
      +     if(!rmdir(rp))
      + 	return err;
            if(!ops['f'])
        	zwarnnam(nam, "%s: %e", arg, errno);
            return 1;

-----BEGIN PGP SIGNATURE-----
Version: 2.6.2

iQCVAwUBMsit9XD/+HJTpU/hAQE3ZQQAoa+URUNR6PouSElz1yJT4Ll2ggUnbqP1
Gb1AoKNmTtMwVyLCC0BpmyYFx8T2H8gEHoP6K+/hQQFoay2Vwe7/yu9aeLeaCeMK
f2QBrQ5kl2ZlFDuahRTyvrD3DsAmPuSbfCbbCjLAFZ0vbelPrZ/mftZfsE6Qe9sg
bjj348W3dzY=
=IKT+
-----END PGP SIGNATURE-----


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: files module improvements
  1996-12-31 12:51 files module improvements Zefram
@ 1997-01-02  3:52 ` Zoltan Hidvegi
  1997-01-02 11:16   ` Zefram
  0 siblings, 1 reply; 3+ messages in thread
From: Zoltan Hidvegi @ 1997-01-02  3:52 UTC (permalink / raw)
  To: Zefram; +Cc: zsh-workers

Zefram wrote:
> * rm -r can run out of file descriptors in deep trees.  Fixing this would
>   require going back up the tree with chdir("..") and then checking that
>   the tree wasn't moved while we were deleting it; it makes it easier for
>   rm to fail.  I think this needs to be done, but I don't like it.

If chdir("..") succeeds it always go back where you started so it is as
safe to use as fchdir.  chdir(..) fails only if the current directory has
been unlinked or if there is a non-executable directory in the chain of
parents.  The patch below uses chdir("..") if possible.

It fixes an other more serious bug: the output of zreaddir was passed to
dorm (calling unmeta first).  But readdir (and zreaddir) uses a static
buffer which may be overwritten by subsequent calls.

And a less serious bug: rm -r non_readable_dir did not remove that
directory.

Zoltan


*** Src/utils.c	1997/01/02 01:48:28	3.1.1.9
--- Src/utils.c	1997/01/02 03:49:21
***************
*** 3306,3311 ****
--- 3306,3328 ----
      return r;
  }
  
+ /**/
+ int
+ upchdir(int n)
+ {
+     char buf[PATH_MAX];
+     char *s;
+ 
+     while (n > 0) {
+ 	for (s = buf; s < buf + PATH_MAX - 4 && n--; )
+ 	    *s++ = '.', *s++ = '.', *s++ = '/';
+ 	s[-1] = '\0';
+ 	if (chdir(buf))
+ 	    return -1;
+     }
+     return 0;
+ }
+ 
  /* Change directory, without following symlinks.  Returns 0 on success, -1 *
   * on failure.  Sets errno to ENOTDIR if any symlinks are encountered.  If *
   * fchdir() fails, or the current directory is unreadable, we might end up *
***************
*** 3320,3330 ****
  #else /* HAVE_LSTAT */
      char buf[PATH_MAX + 1], *ptr;
      char const *pptr;
!     int olddir = open(".", O_RDONLY), err;
      struct stat st1, st2;
  
!     if(*path == '/')
  	chdir("/");
      for(;;) {
  	while(*path == '/')
  	    path++;
--- 3337,3350 ----
  #else /* HAVE_LSTAT */
      char buf[PATH_MAX + 1], *ptr;
      char const *pptr;
!     int olddir = open(".", O_RDONLY), level, err;
      struct stat st1, st2;
  
!     if(*path == '/') {
  	chdir("/");
+ 	level = -1;
+     } else
+ 	level = 0;
      for(;;) {
  	while(*path == '/')
  	    path++;
***************
*** 3348,3354 ****
  	    err = ENOTDIR;
  	    break;
  	}
! 	if(chdir(buf) || lstat(".", &st2)) {
  	    err = errno;
  	    break;
  	}
--- 3368,3380 ----
  	    err = ENOTDIR;
  	    break;
  	}
! 	if(chdir(buf)) {
! 	    err = errno;
! 	    break;
! 	}
! 	if (level >= 0)
! 	    level++;
! 	if(lstat(".", &st2)) {
  	    err = errno;
  	    break;
  	}
***************
*** 3357,3364 ****
  	    break;
  	}
      }
!     fchdir(olddir);
!     close(olddir);
      errno = err;
      return -1;
  #endif /* HAVE_LSTAT */
--- 3383,3393 ----
  	    break;
  	}
      }
!     if (olddir >= 0) {
! 	fchdir(olddir);
! 	close(olddir);
!     } else
! 	upchdir(level);
      errno = err;
      return -1;
  #endif /* HAVE_LSTAT */
*** Src/Modules/files.c	1997/01/02 01:49:56	3.1.1.2
--- Src/Modules/files.c	1997/01/02 03:15:42
***************
*** 311,320 ****
  
      for(; !(err & 2) && *args; args++) {
  	rp = ztrdup(*args);
! 	len = strlen(rp + 1);
! 	unmetafy(rp, NULL);
  	err |= dorm(nam, *args, rp, ops, 1);
! 	zfree(rp, len);
      }
      return ops['f'] ? 0 : !!err;
  }
--- 311,319 ----
  
      for(; !(err & 2) && *args; args++) {
  	rp = ztrdup(*args);
! 	unmetafy(rp, &len);
  	err |= dorm(nam, *args, rp, ops, 1);
! 	zfree(rp, len + 1);
      }
      return ops['f'] ? 0 : !!err;
  }
***************
*** 365,371 ****
      char *fn;
      DIR *d;
      int err = 0;
!     int pwd = open(".", O_RDONLY);
  
      if(first ? zchdir(rp) : lchdir(rp)) {
  	if(!ops['f'])
--- 364,370 ----
      char *fn;
      DIR *d;
      int err = 0;
!     int pwd = *rp == '/' ? open(".", O_RDONLY) : -1;
  
      if(first ? zchdir(rp) : lchdir(rp)) {
  	if(!ops['f'])
***************
*** 377,404 ****
      if(!d) {
  	if(!ops['f'])
  	    zwarnnam(nam, "%s: %e", arg, errno);
! 	return 1 - fchdir(pwd);
      }
!     while((fn = zreaddir(d, 1))) {
! 	char *narg = tricat(arg, "/", fn);
  
! 	err |= dorm(nam, narg, unmeta(fn), ops, 0);
! 	zsfree(narg);
! 	if(err & 2) {
! 	    closedir(d);
  	    close(pwd);
  	    return 2;
  	}
!     }
!     closedir(d);
!     if(fchdir(pwd)) {
  	close(pwd);
- 	if(!ops['f'])
- 	    zwarnnam(nam, "failed to return to previous directory: %e",
- 		NULL, errno);
- 	return 2;
-     }
-     close(pwd);
      if(!ops['f'] && ops['i']) {
  	nicezputs(nam, stderr);
  	fputs(": remove `", stderr);
--- 376,420 ----
      if(!d) {
  	if(!ops['f'])
  	    zwarnnam(nam, "%s: %e", arg, errno);
! 	err = 1;
!     } else {
! 	while((fn = zreaddir(d, 1))) {
! 	    char *narg = tricat(arg, "/", fn);
! 	    int len;
! 
! 	    fn = ztrdup(unmetafy(fn, &len));
! 	    err |= dorm(nam, narg, fn, ops, 0);
! 	    zsfree(narg);
! 	    zfree(fn, len + 1);
! 	    if(err & 2) {
! 		closedir(d);
! 		close(pwd);
! 		return 2;
! 	    }
! 	}
! 	closedir(d);
      }
!     if (pwd < 0 || fchdir(pwd)) {
! 	int level = 0;
  
! 	if (pwd >= 0)
  	    close(pwd);
+ 	if (*rp != '/') {
+ 	    for (fn = rp; *fn; level++) {
+ 		while (*fn && *fn++ != '/');
+ 		while (*fn == '/')
+ 		    fn++;
+ 	    }
+ 	    level = !upchdir(level);
+ 	}
+ 	if (!level) {
+ 	    if(!ops['f'])
+ 		zwarnnam(nam, "failed to return to previous directory: %e",
+ 			 NULL, errno);
  	    return 2;
  	}
!     } else
  	close(pwd);
      if(!ops['f'] && ops['i']) {
  	nicezputs(nam, stderr);
  	fputs(": remove `", stderr);


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: files module improvements
  1997-01-02  3:52 ` Zoltan Hidvegi
@ 1997-01-02 11:16   ` Zefram
  0 siblings, 0 replies; 3+ messages in thread
From: Zefram @ 1997-01-02 11:16 UTC (permalink / raw)
  To: Zoltan Hidvegi; +Cc: Z Shell workers mailing list

Zoltan Hidvegi wrote:
>If chdir("..") succeeds it always go back where you started so it is as
>safe to use as fchdir.

Not true.  If a directory is moved, its .. changes.  One of the attacks
on /tmp-clearing cron jobs that has been proposed goes like

mkdir -p /tmp/a/b
# make large directory tree under /tmp/a/b
# wait for the cron job to start deleting under /tmp/a/b
mv /tmp/a/b /tmp/b

and when the rm does chdir("..") from b, thinking it is going back to
/tmp/a, it is actually entering /tmp.  It then does another
chdir(".."), trying to get back to /tmp, and actually enters /.

This attack can be detected (but not prevented entirely) by doing
stat(".") before entering a subdirectory, and after leaving it.  If the
two do not match, then the directory was moved while being cleared, and
the current directory could be pretty much anything -- rm would have to
abort.

-zefram


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~1997-01-02 11:12 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
1996-12-31 12:51 files module improvements Zefram
1997-01-02  3:52 ` Zoltan Hidvegi
1997-01-02 11:16   ` Zefram

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/zsh/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).