sys_chroot+sys_fchdir Fix

2007-09-19 Thread majkls

Hello,
here is an fix to an exploit (obtained somewhere in internet). This 
exploit can workaround chroot with CAP_SYS_CHROOT. It is also possible 
(with sufficient filedescriptor (if there is na directory fd opened in 
root) workaround chroot with sys_fchdir. This patch fixes it.


Miloslav Semler
diff -Nrp linux-2.6.16.53/fs/namei.c linux-2.6.16.53-new/fs/namei.c
*** linux-2.6.16.53/fs/namei.c	2007-07-25 23:05:45.0 +0200
--- linux-2.6.16.53-new/fs/namei.c	2007-09-15 16:13:50.0 +0200
*** static __always_inline void follow_dotdo
*** 728,733 
--- 728,772 
  	}
  	follow_mount(&nd->mnt, &nd->dentry);
  }
+ long directory_is_out(struct vfsmount *wdmnt, struct dentry *wdentry,
+ 		struct vfsmount *rootmnt, struct dentry *root)
+ {
+ 	struct nameidata oldentry, newentry;
+ 	long ret = 1;
+ 	
+ read_lock(¤t->fs->lock);
+ 	oldentry.dentry = dget(wdentry);
+ 	oldentry.mnt = mntget(wdmnt);
+ read_unlock(¤t->fs->lock);
+ 	newentry.dentry = oldentry.dentry;
+ 	newentry.mnt = oldentry.mnt;
+ 	
+ 	follow_dotdot(&newentry);
+ 	/* check it */
+ 	if(newentry.dentry == root && 
+ 		newentry.mnt == rootmnt){
+ 		ret = 0;
+ 		goto out;
+ 	}
+ 	
+ 	while(oldentry.mnt != newentry.mnt ||
+ 		oldentry.dentry != newentry.dentry){
+ 		
+ 		memcpy(&oldentry, &newentry, sizeof(struct nameidata));
+ 		follow_dotdot(&newentry);
+ 		
+ 		/* check it */
+ 		if(newentry.dentry == root && 
+ 			newentry.mnt == rootmnt){
+ 			ret = 0;
+ 			goto out;
+ 		}
+ 	}
+ out:
+ 	dput(newentry.dentry);
+ 	mntput(newentry.mnt);
+ 	return ret;
+ }
  
  /*
   *  It's more convoluted than I'd like it to be, but... it's still fairly
diff -Nrp linux-2.6.16.53/fs/open.c linux-2.6.16.53-new/fs/open.c
*** linux-2.6.16.53/fs/open.c	2007-07-25 23:05:45.0 +0200
--- linux-2.6.16.53-new/fs/open.c	2007-09-15 16:14:52.0 +0200
*** dput_and_out:
*** 560,565 
--- 560,567 
  out:
  	return error;
  }
+ long directory_is_out(struct vfsmount *, struct dentry*, 
+ 		struct vfsmount *, struct dentry *);
  
  asmlinkage long sys_fchdir(unsigned int fd)
  {
*** asmlinkage long sys_fchdir(unsigned int 
*** 581,586 
--- 583,591 
  	error = -ENOTDIR;
  	if (!S_ISDIR(inode->i_mode))
  		goto out_putf;
+ 	if (directory_is_out(mnt, dentry, current->fs->rootmnt, 
+ current->fs->root))
+ 		goto out_putf;
  
  	error = file_permission(file, MAY_EXEC);
  	if (!error)
*** out:
*** 594,600 
  asmlinkage long sys_chroot(const char __user * filename)
  {
  	struct nameidata nd;
! 	int error;
  
  	error = __user_walk(filename, LOOKUP_FOLLOW | LOOKUP_DIRECTORY | LOOKUP_NOALT, &nd);
  	if (error)
--- 599,605 
  asmlinkage long sys_chroot(const char __user * filename)
  {
  	struct nameidata nd;
! 	int error, set_wd = 0;
  
  	error = __user_walk(filename, LOOKUP_FOLLOW | LOOKUP_DIRECTORY | LOOKUP_NOALT, &nd);
  	if (error)
*** asmlinkage long sys_chroot(const char __
*** 607,615 
--- 612,631 
  	error = -EPERM;
  	if (!capable(CAP_SYS_CHROOT))
  		goto dput_and_out;
+ 	error = -ENOTDIR;
+ 	/*
+ 	if (directory_is_out(nd.mnt, nd.dentry, current->fs->rootmnt,
+ current->fs->root))
+ 		goto dput_and_out;
+ 		*/
+ 	set_wd = directory_is_out(current->fs->pwdmnt, current->fs->pwd, 
+ nd.mnt, nd.dentry);
  
  	set_fs_root(current->fs, nd.mnt, nd.dentry);
  	set_fs_altroot();
+ 	/* if wd is out, reset it to . */
+ 	if(set_wd)
+ 		set_fs_pwd(current->fs, nd.mnt, nd.dentry);
  	error = 0;
  dput_and_out:
  	path_release(&nd);
#include 
#include 
#include   
#include   
#include   
#include   
#include   
#include   
  	
  	 /*  
  	 ** You should set NEED_FCHDIR to 1 if the chroot() on your  
  	 ** system changes the working directory of the calling  
  	 ** process to the same directory as the process was chroot()ed  
  	 ** to.  
  	 **  
  	 ** It is known that you do not need to set this value if you  
  	 ** running on Solaris 2.7 and below.  
  	 **  
  	 */  
  	 /*#define NEED_FCHDIR 0*/
  	
  	 #define TEMP_DIR "waterbuffalo"  
  	
  	 /* Break out of a chroot() environment in C */  
  	
  	 int main() {  
  	   int x;/* Used to move up a directory tree */  
  	   int done=0;   /* Are we done yet ? */  
  	 #ifdef NEED_FCHDIR  
  	   int dir_fd;   /* File descriptor to directory */  
  	 #endif  
  	   struct stat sbuf; /* The stat() buffer */  
  	   puts("chroot()");
	   chroot(".");  
  	  /* chdir("/");
	  setuid(65534); */
  	 /*  
  	 ** First we create the temporary directory if it doesn't exist  
  	 */  
  	 
	   if (stat(TEMP_DIR,&sbuf)<0) {  
  	 if (errno==ENOENT) {  
  	   if (mkdir(TEMP_DIR,0755)<0) {  
  	 fprintf(stderr,"Failed to create %s - %s\n", TEMP_DIR,  
  	 strerror(errno));  
  	 exit(1);  
  	   }  
  	 } else {  
  	   fprintf(stderr,"Failed to stat %s - %s\n", TEMP_DIR,  
  	   strerror(errno));  
  	   

Re: sys_chroot+sys_fchdir Fix

2007-09-20 Thread majkls

Philipp Marek napsal(a):

Please, everybody,

don't change that.

I'm currently using that *feature* (yes, I see it as that) in my
fsvs-chrooter-utility (see
http://fsvs.tigris.org/source/browse/*checkout*/fsvs/trunk/www/doxygen/html/group__howto__chroot.html)
for easier usage of fsvs on older systems.

- User starts a small wrapper,
- that opens "/",
- chroot()s into a directory and starts fsvs.
- fsvs gets its libraries loaded
- and chroot()s back to the original system.

Voila! fsvs can use the newest available libraries for that architecture,
without having to change the installed system.

  
So I thing this is an example how chroot would not be really used. For 
DSO loading there is many better ways to load own DSO. Though is this 
feature described in chroot() manpage, I have not noticed that any 
serious project uses it.  But ok, this is a ferature of chroot(). Also 
FreeBSD does not support escaping chroot AFAIK. So this feature is very 
badly portable.



Miloslav
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/