--- orig/fs/file_table.c 2007-02-20 07:34:32.000000000 +0100 +++ new/fs/file_table.c 2007-05-09 20:49:52.000000000 +0200 @@ -152,8 +152,8 @@ EXPORT_SYMBOL(fput); */ void fastcall __fput(struct file *file) { - struct dentry *dentry = file->f_path.dentry; - struct vfsmount *mnt = file->f_path.mnt; + struct dentry *dentry = file->f_path.dentry, *s_dentry = file->f_shdw; + struct vfsmount *mnt = file->f_path.mnt, *s_mnt = file->f_shdwmnt; struct inode *inode = dentry->d_inode; might_sleep(); @@ -178,15 +178,21 @@ void fastcall __fput(struct file *file) file_kill(file); file->f_path.dentry = NULL; file->f_path.mnt = NULL; + file->f_shdw = NULL; + file->f_shdwmnt = NULL; file_free(file); dput(dentry); mntput(mnt); + if (s_dentry) { + /* NOTE: if s_dentry == NULL then s_mnt may be ERR_PTR */ + dput(s_dentry); + mntput(s_mnt); + } } -struct file fastcall *fget(unsigned int fd) +struct file fastcall *__fget(struct files_struct *files, unsigned int fd) { struct file *file; - struct files_struct *files = current->files; rcu_read_lock(); file = fcheck_files(files, fd); @@ -202,6 +208,11 @@ struct file fastcall *fget(unsigned int return file; } +struct file fastcall *fget(unsigned int fd) +{ + return __fget(current->files, fd); +} + EXPORT_SYMBOL(fget); /* --- orig/fs/open.c 2007-02-20 07:34:32.000000000 +0100 +++ new/fs/open.c 2007-05-09 21:01:24.000000000 +0200 @@ -413,13 +413,51 @@ asmlinkage long sys_access(const char __ return sys_faccessat(AT_FDCWD, filename, mode); } +static inline int read_fs_flags(void) +{ + int res; + read_lock(¤t->fs->lock); + res = current->fs->flags; + read_unlock(¤t->fs->lock); + return res; +} + +void set_fs_shdwpwd(struct fs_struct *fs, + struct vfsmount *mnt, struct dentry *dentry) +{ + struct dentry *old_dentry; + struct vfsmount *old_mnt; + + BUG_ON(dentry != NULL && mnt == NULL); + write_lock(&fs->lock); + /* set shadow pwd */ + old_dentry = fs->shdwpwd; + old_mnt = fs->shdwpwdmnt; + fs->shdwpwd = dget(dentry); + if (dentry) + fs->shdwpwdmnt = mntget(mnt); + else + /* PTR_ERR flag */ + fs->shdwpwdmnt = mnt; + write_unlock(&fs->lock); + + if (old_dentry) { + mntput(old_mnt); + dput(old_dentry); + } +} + asmlinkage long sys_chdir(const char __user * filename) { struct nameidata nd; - int error; + char *tmp = getname(filename); + int error = PTR_ERR(tmp);; + + if (IS_ERR(tmp)) + goto out_badname; - error = __user_walk(filename, - LOOKUP_FOLLOW|LOOKUP_DIRECTORY|LOOKUP_CHDIR, &nd); + error = path_lookup(tmp, LOOKUP_FOLLOW | LOOKUP_DIRECTORY + | LOOKUP_CHDIR, &nd); if (error) goto out; @@ -427,11 +465,23 @@ asmlinkage long sys_chdir(const char __u if (error) goto dput_and_out; - set_fs_pwd(current->fs, nd.mnt, nd.dentry); + if (!(read_fs_flags() & SHDW_ENABLED)) + goto set_std; + if (!(nd.flags & LOOKUP_INSHDW)) { + set_fs_shdwpwd(current->fs, NULL, NULL); + } else + /* shadow == std */ + set_fs_shdwpwd(current->fs, nd.mnt, nd.dentry); + +set_std: + /* set std cwd */ + set_fs_pwd(current->fs, nd.mnt, nd.dentry); dput_and_out: path_release(&nd); out: + putname(tmp); +out_badname: return error; } @@ -457,8 +507,25 @@ asmlinkage long sys_fchdir(unsigned int goto out_putf; error = file_permission(file, MAY_EXEC); - if (!error) - set_fs_pwd(current->fs, mnt, dentry); + if (error) + goto out_putf; + + set_fs_pwd(current->fs, mnt, dentry); + + if (!(read_fs_flags() & SHDW_ENABLED)) + /* shadow dirs aren't enabled */ + goto out_putf; + + if (get_file_shdwdir(file, &dentry, &mnt)) + /* some error ocured */ + set_fs_shdwpwd(current->fs, NULL, NULL); + else { + /* ok */ + set_fs_shdwpwd(current->fs, mnt, dentry); + mntput(mnt); + dput(dentry); + } + out_putf: fput(file); out: --- orig/fs/namei.c 2007-02-20 07:34:32.000000000 +0100 +++ new/fs/namei.c 2007-05-09 21:01:50.000000000 +0200 @@ -1,4 +1,4 @@ -/* + /* * linux/fs/namei.c * * Copyright (C) 1991, 1992 Linus Torvalds @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -514,6 +515,25 @@ static struct dentry * real_lookup(struc return result; } +static inline int use_shadow(struct fs_struct *fs, struct nameidata *nd) +{ + /* assert: fs->lock held */ + return (fs->flags & SHDW_ENABLED) && (nd->flags & LOOKUP_INSHDW); +} + +static inline struct dentry *fs_root(struct fs_struct *fs, struct nameidata *nd) +{ + /* assert: current->fs->lock held */ + return (use_shadow(fs, nd)) ? fs->shdwroot : fs->root; +} + +static inline struct vfsmount *fs_rootmnt(struct fs_struct *fs, + struct nameidata *nd) +{ + /* assert: current->fs->lock held */ + return (use_shadow(fs, nd)) ? fs->shdwrootmnt : fs->rootmnt; +} + static int __emul_lookup_dentry(const char *, struct nameidata *); /* SMP-safe */ @@ -531,8 +551,8 @@ walk_init_root(const char *name, struct return 0; read_lock(&fs->lock); } - nd->mnt = mntget(fs->rootmnt); - nd->dentry = dget(fs->root); + nd->mnt = mntget(fs_rootmnt(fs, nd)); + nd->dentry = dget(fs_root(fs, nd)); read_unlock(&fs->lock); return 1; } @@ -729,9 +749,9 @@ static __always_inline void follow_dotdo struct vfsmount *parent; struct dentry *old = nd->dentry; - read_lock(&fs->lock); - if (nd->dentry == fs->root && - nd->mnt == fs->rootmnt) { + read_lock(&fs->lock); + if (nd->dentry == fs_root(fs, nd) && + nd->mnt == fs_rootmnt(fs, nd)) { read_unlock(&fs->lock); break; } @@ -841,6 +861,11 @@ static fastcall int __link_path_walk(con hash = init_name_hash(); do { + if (unlikely((nd->flags & LOOKUP_FINDCHAR) && + (c == nd->find_char))) { + /* shadow control char found */ + nd->flags |= LOOKUP_CHARFOUND; + } name++; hash = partial_name_hash(c, hash); c = *(const unsigned char *)name; @@ -1099,8 +1124,8 @@ set_it: } } -/* Returns 0 and nd will be valid on success; Retuns error, otherwise. */ -static int fastcall do_path_lookup(int dfd, const char *name, +/* Lookup @name, starting at @dfd, use normal (non-shadow) root and pwd */ +static int fastcall path_lookup_norm(int dfd, const char *name, unsigned int flags, struct nameidata *nd) { int retval = 0; @@ -1159,7 +1184,7 @@ out: if (likely(retval == 0)) { if (unlikely(!audit_dummy_context() && nd && nd->dentry && nd->dentry->d_inode)) - audit_inode(name, nd->dentry->d_inode); + audit_inode(name, nd->dentry->d_inode); } out_fail: return retval; @@ -1169,6 +1194,313 @@ fput_fail: goto out_fail; } +/* + * Set @filp->f_shdw, @filp->f_shdwmnt to @mnt,@dentry. + * Takes @filp->f_owner->lock. + * Note: if @dentry == NULL then @mnt may be ERR_PTR(-EINVAL). + */ +static void set_fileshdw(struct file *filp, struct vfsmount *mnt, + struct dentry *dentry) +{ + struct dentry *old_dentry; + struct vfsmount *old_mnt; + + BUG_ON(dentry != NULL && mnt == NULL); + write_lock(&filp->f_owner.lock); + old_dentry = filp->f_shdw; + old_mnt = filp->f_shdwmnt; + filp->f_shdw = dget(dentry); + if (dentry) + filp->f_shdwmnt = mntget(mnt); + else + /* mnt is ERR_PTR */ + filp->f_shdwmnt = mnt; + write_unlock(&filp->f_owner.lock); + + if (old_dentry) { + dput(old_dentry); + mntput(old_mnt); + } +} + +/* + * Determine @filp->f_shdw,f_shdwmnt from @filp->dentry,mnt + * and current->fs->shdwroot. + * Also check whether it's a directory and we have permisson. + * Called only from get_file_shdwdir(). + */ +static int validate_shdwfile(struct file *filp) +{ + struct nameidata nd; + char *buf, *name; + int res = -ENOMEM; + + buf = (char*)__get_free_page(GFP_KERNEL); + if (!buf) + goto fail; + + /* doesn't need a lock for reading f_dentry, f_vfsmnt */ + name = d_path(filp->f_dentry, filp->f_vfsmnt, buf, PAGE_SIZE); + res = PTR_ERR(name); + if (IS_ERR(name)) + goto fail_free; + + BUG_ON(*name != '/'); + res = path_lookup_shdw(AT_FDCWD, name, + LOOKUP_FOLLOW|LOOKUP_DIRECTORY, &nd); + if (res) + goto fail_free; + + res = permission(nd.dentry->d_inode, MAY_EXEC, NULL); + if (res) + goto fail_put; + + /* ok -> valid */ + set_fileshdw(filp, nd.mnt, nd.dentry); + path_release(&nd); + free_page((unsigned long)buf); +out: + /* current->fs->lock is not held on exit */ + return res; + +fail_put: + path_release(&nd); +fail_free: + free_page((unsigned long)buf); +fail: + /* error -> invalid */ + set_fileshdw(filp, ERR_PTR(-EINVAL), NULL); + goto out; +} + +/* + * Set *@dentry,*@mnt to @file->f_shdw,f_shdwmnt, try to validate + * them if needed. + */ +int get_file_shdwdir(struct file *file, struct dentry **dentry, + struct vfsmount **mnt) +{ + int retval = -ENOENT; + + read_lock(&file->f_owner.lock); + while (!file->f_shdw) { + if (!file->f_shdwmnt) { + /* delayed, try to validate */ + read_unlock(&file->f_owner.lock); + if (validate_shdwfile(file)) + goto out; + /* ok but continue loop to avoid races */ + read_lock(&file->f_owner.lock); + } else + /* invalid */ + goto out_unlock; + /* continue loop to avoid races */ + } + /* get the shadow dir */ + *dentry = dget(file->f_shdw); + *mnt = mntget(file->f_shdwmnt); + retval = 0; +out_unlock: + read_unlock(&file->f_owner.lock); +out: + return retval; +} + +/* + * Determine current->fs->shdwpwd,shdwpwdmnt from current->fs->pwd,pwdmnt. + * Also check whether it's a directory and we have permisson. + */ +static int validate_shdwpwd(void) +{ + /* called with current->fs->lock held */ + struct dentry *pwd = dget(current->fs->pwd); + struct vfsmount *mnt = mntget(current->fs->pwdmnt); + struct nameidata nd; + char *buf, *name; + int res = -ENOMEM; + + read_unlock(¤t->fs->lock); + buf = (char*)__get_free_page(GFP_KERNEL); + if (!buf) + goto fail; + + name = d_path(pwd, mnt, buf, PAGE_SIZE); + res = PTR_ERR(name); + if (IS_ERR(name)) + goto fail_free; + + BUG_ON(*name != '/'); + /* won't recurse here because @name starts with '/' */ + res = path_lookup_shdw(AT_FDCWD, name, + LOOKUP_FOLLOW|LOOKUP_DIRECTORY, &nd); + if (res) + goto fail_free; + + res = permission(nd.dentry->d_inode, MAY_EXEC, NULL); + if (res) + goto fail_put; + + /* ok -> valid */ + set_fs_shdwpwd(current->fs, nd.mnt, nd.dentry); + path_release(&nd); + free_page((unsigned long)buf); +out: + dput(pwd); + mntput(mnt); + /* current->fs->lock is NOT held on exit */ + return res; + +fail_put: + path_release(&nd); +fail_free: + free_page((unsigned long)buf); +fail: + /* error -> invalidate */ + set_fs_shdwpwd(current->fs, ERR_PTR(-EINVAL), NULL); + goto out; +} + +/* + * Set *@dentry,*@mnt to current->fs->shdwpwd,shdwpwdmnt, try to validate + * them if needed. + */ +static int get_shdwpwd(struct dentry **dentry, struct vfsmount **mnt) +{ + int retval = -ENOENT; + /* assert: current->fs->lock is held */ + while (!current->fs->shdwpwd) { + if (current->fs->shdwpwdmnt) + /* ERR_PTR - invalid */ + goto out_unlock; + + /* it's delayed -> validate */ + if (validate_shdwpwd()) + /* (current->fs->lock is unlocked + * in validate_shdwpwd()) */ + goto out; + + read_lock(¤t->fs->lock); + /* continue loop to avoid races */ + } + + *mnt = mntget(current->fs->shdwpwdmnt); + *dentry = dget(current->fs->shdwpwd); + retval = 0; +out_unlock: + read_unlock(¤t->fs->lock); +out: + /* current->fs->lock is NOT held on exit */ + return retval; +} + +/* + * Lookup @name, starting at @dfd, use shadow root and pwd. + * Try to validate current->fs->shdwpwd/filp->f_shdwmnt if needed. + */ +int fastcall path_lookup_shdw(int dfd, const char *name, + unsigned int flags, struct nameidata *nd) +{ + int retval = -ENOENT; + + nd->last_type = LAST_ROOT; /* if there are only slashes... */ + nd->flags = flags | LOOKUP_INSHDW | LOOKUP_NOALT; + nd->depth = 0; + + read_lock(¤t->fs->lock); + if (!(current->fs->flags & SHDW_ENABLED)) + goto unlock_fail; + + if (*name == '/') { + /* start at the shadow root */ + if (!current->fs->shdwroot) + goto unlock_fail; + nd->mnt = mntget(current->fs->shdwrootmnt); + nd->dentry = dget(current->fs->shdwroot); + read_unlock(¤t->fs->lock); + } else if (dfd == AT_FDCWD) { + /* start at the shadow pwd */ + retval = get_shdwpwd(&nd->dentry, &nd->mnt); + /* current->fs->lock is not held here */ + if (retval) + goto out_fail; + } else { + int fput_needed; + struct file *file; + + read_unlock(¤t->fs->lock); + /* start at file's shadow dir */ + file = fget_light(dfd, &fput_needed); + retval = -EBADF; + if (!file) + goto out_fail; + + retval = get_file_shdwdir(file, &nd->dentry, &nd->mnt); + fput_light(file, fput_needed); + + if (retval) + goto out_fail; + } + + current->total_link_count = 0; + retval = link_path_walk(name, nd); + + if (likely(retval == 0)) { + if (unlikely(!audit_dummy_context() && nd && nd->dentry && + nd->dentry->d_inode)) + audit_inode(name, nd->dentry->d_inode); + } + +out_fail: + return retval; + +unlock_fail: + read_unlock(¤t->fs->lock); + goto out_fail; +} + +/* + * Perform full lookup of @name starting at @dfd. + * 1. do a normal lookup + * 2. if it fails try to lookup in shadow dir + * Returns 0 and nd will be valid on success; Retuns error, otherwise. + */ +static int fastcall do_path_lookup(int dfd, const char *name, + unsigned int flags, struct nameidata *nd) +{ + int retval; + + if (!(flags & LOOKUP_NOSHDW)) { + /* shadow dir isn't disabled in the current lookup session */ + read_lock(¤t->fs->lock); + if (current->fs->flags & SHDW_ENABLED) { + /* shadow is enabled */ + if (current->fs->flags & SHDW_USE_ESC) { + flags |= LOOKUP_FINDCHAR; + nd->find_char = current->fs->shdw_escch; + } + } else + /* shadow is disabled - disable it in lookup session */ + flags |= LOOKUP_NOSHDW; + read_unlock(¤t->fs->lock); + } + + retval = path_lookup_norm(dfd, name, flags, nd); + + /* + * Do another lookup in the shadow dir iff: + * normal lookup failed + * && shadow is enabled + * && the last lookup was not already going within shadows + * && user asked for the escape character and we found it + */ + if (unlikely(retval && !(nd->flags & (LOOKUP_NOSHDW|LOOKUP_INSHDW)) + && !((nd->flags & LOOKUP_FINDCHAR) + && !(nd->flags & LOOKUP_CHARFOUND)))) + retval = path_lookup_shdw(dfd, name, flags, nd); + + return retval; +} + int fastcall path_lookup(const char *name, unsigned int flags, struct nameidata *nd) { @@ -1195,6 +1527,16 @@ static int __path_lookup_intent_open(int } } else if (err != 0) release_open_intent(nd); + else if (!(nd->flags & LOOKUP_NOSHDW) && + S_ISDIR(nd->dentry->d_inode->i_mode)) { + /* setup file's shadow dir */ + /* default: filp->f_shdw = filp->f_shdwmnt = NULL */ + if (nd->flags & LOOKUP_INSHDW) { + filp->f_shdw = dget(nd->dentry); + filp->f_shdwmnt = mntget(nd->mnt); + } + } + return err; } @@ -2750,6 +3092,260 @@ struct inode_operations page_symlink_ino .put_link = page_put_link, }; + +/* + * Find task by @pid, check permissions. + * @pid == 0 -> current. + */ +static struct task_struct *tsk_by_pid(pid_t pid) +{ + struct task_struct *tsk = current; + + if (pid) { + read_lock(&tasklist_lock); + tsk = find_task_by_pid(pid); + if (tsk) + get_task_struct(tsk); + read_unlock(&tasklist_lock); + if (!tsk) + tsk = ERR_PTR(-ESRCH); + else if (!ptrace_may_attach(tsk)) { + put_task_struct(tsk); + tsk = ERR_PTR(-EPERM); + } + } + return tsk; +} + +asmlinkage long sys_getshdwinfo(pid_t pid, int func, int __user *data) +{ + struct task_struct *tsk = tsk_by_pid(pid); + long ret = PTR_ERR(tsk); + + if (IS_ERR(tsk)) + goto out_noput; + ret = -EINVAL; + + switch (func) { + case FSI_SHDW_ENABLE: + read_lock(&tsk->fs->lock); + ret = (tsk->fs->flags & SHDW_ENABLED) ? 1 : 0; + read_unlock(&tsk->fs->lock); + ret = put_user(ret, data); + break; + + case FSI_SHDW_ESC_EN: + read_lock(&tsk->fs->lock); + ret = (tsk->fs->flags & SHDW_USE_ESC) ? 1 : 0; + read_unlock(&tsk->fs->lock); + ret = put_user(ret, data); + break; + + case FSI_SHDW_ESC_CHAR: + read_lock(&tsk->fs->lock); + ret = tsk->fs->shdw_escch; + read_unlock(&tsk->fs->lock); + ret = put_user((char)ret, (char __user*)data); + break; + } + + if (pid) + put_task_struct(tsk); +out_noput: + /* avoid REGPARM breakage on x86: */ + prevent_tail_call(ret); + return ret; +} + +/* + * Set fs->shdwpwd,shdwpwdmnt according to @pathname. + * @pathname is NOT looked up in shadow dir. + */ +static int do_setshdwpwd(struct fs_struct *fs, const char __user *pathname) +{ + struct nameidata nd; + int error = __user_walk(pathname, + LOOKUP_FOLLOW|LOOKUP_DIRECTORY|LOOKUP_NOSHDW, &nd); + if (error) + goto out; + + error = vfs_permission(&nd, MAY_EXEC); + if (error) + goto dput_and_out; + + set_fs_shdwpwd(fs, nd.mnt, nd.dentry); + +dput_and_out: + path_release(&nd); +out: + return error; +} + +/* + * Set fs->shdwroot,shdwrootmnt according to @pathname. + * @pathname is NOT looked up in shadow dir. + * If @pathname == NULL then disable shadow dir. + */ +static int do_setshdwroot(struct fs_struct *fs, const char __user *pathname) +{ + struct dentry *old_dentry; + struct vfsmount *old_mnt; + struct nameidata nd; + int error = 0; + + if (pathname) { + error = __user_walk(pathname, + LOOKUP_FOLLOW|LOOKUP_DIRECTORY|LOOKUP_NOSHDW, &nd); + if (error) + goto out; + + error = vfs_permission(&nd, MAY_EXEC); + if (error) + goto dput_and_out; + } else { + /* remove shadow root */ + nd.dentry = NULL; + nd.mnt = NULL; + } + + write_lock(&fs->lock); + old_dentry = fs->shdwroot; + old_mnt = fs->shdwrootmnt; + fs->shdwroot = dget(nd.dentry); + fs->shdwrootmnt = mntget(nd.mnt); + if (!nd.dentry) + /* disable shadow dir */ + fs->flags &= ~SHDW_ENABLED; + write_unlock(&fs->lock); + + dput(old_dentry); + mntput(old_mnt); + +dput_and_out: + path_release(&nd); +out: + return error; +} + +/* + * Set file->f_shdw,f_shdwmnt according to @pathname. + * @pathname is NOT looked up in shadow dir. + * If @pathname == NULL then set file->f_shdw,f_shdwmnt as delayed. + */ +static int do_setshdwfd(struct task_struct *tsk, int fd, + const char __user *pathname) +{ + struct nameidata nd; + struct file *filp = __fget(tsk->files, fd); + int error = 0; + + if (!filp) + return -EBADF; + + if (pathname) { + error = __user_walk(pathname, + LOOKUP_FOLLOW|LOOKUP_DIRECTORY|LOOKUP_NOSHDW, &nd); + if (error) + goto out; + + error = vfs_permission(&nd, MAY_EXEC); + if (!error) { + set_fileshdw(filp, nd.mnt, nd.dentry); + path_release(&nd); + } + } else { + /* set delayed */ + set_fileshdw(filp, NULL, NULL); + } +out: + fput(filp); + return error; +} + +asmlinkage long sys_setshdwpath(pid_t pid, int fd, const char __user *path) +{ + struct task_struct *tsk = tsk_by_pid(pid); + long ret = PTR_ERR(tsk); + + if (IS_ERR(tsk)) + goto out_noput; + + ret = -EINVAL; + + if (fd >= 0) + /* a normal file's shadow */ + ret = do_setshdwfd(tsk, fd, path); + else if (fd == SHDW_FD_ROOT) + /* root shadow */ + ret = do_setshdwroot(tsk->fs, path); + else if (fd == SHDW_FD_PWD) { + /* pwd shadow */ + if (path) + ret = do_setshdwpwd(tsk->fs, path); + else { + /* set delayed */ + set_fs_shdwpwd(tsk->fs, NULL, NULL); + ret = 0; + } + } + + if (pid) + put_task_struct(tsk); +out_noput: + /* avoid REGPARM breakage on x86: */ + prevent_tail_call(ret); + return ret; +} + +asmlinkage long sys_setshdwinfo(pid_t pid, int func, int data) +{ + struct task_struct *tsk = tsk_by_pid(pid); + long ret = PTR_ERR(tsk); + + if (IS_ERR(tsk)) + goto out_noput; + + ret = -EINVAL; + switch (func) { + case FSI_SHDW_ENABLE: + ret = 0; + write_lock(&tsk->fs->lock); + tsk->fs->flags &= ~SHDW_ENABLED; + if (data) { + /* may enable shadow? */ + if (tsk->fs->shdwroot && tsk->fs->shdwrootmnt) + tsk->fs->flags |= SHDW_ENABLED; + else + ret = -EPERM; + } + write_unlock(&tsk->fs->lock); + break; + + case FSI_SHDW_ESC_EN: + ret = 0; + write_lock(&tsk->fs->lock); + tsk->fs->flags &= ~SHDW_USE_ESC; + if (data) + tsk->fs->flags |= SHDW_USE_ESC; + write_unlock(&tsk->fs->lock); + break; + + case FSI_SHDW_ESC_CHAR: + ret = 0; + write_lock(&tsk->fs->lock); + tsk->fs->shdw_escch = (unsigned char)data; + write_unlock(&tsk->fs->lock); + break; + } + + if (pid) + put_task_struct(tsk); +out_noput: + /* avoid REGPARM breakage on x86: */ + prevent_tail_call(ret); + return ret; +} + EXPORT_SYMBOL(__user_walk); EXPORT_SYMBOL(__user_walk_fd); EXPORT_SYMBOL(follow_down); --- orig/fs/namespace.c 2007-02-20 07:34:32.000000000 +0100 +++ new/fs/namespace.c 2007-03-02 20:05:03.000000000 +0100 @@ -1448,6 +1448,7 @@ struct mnt_namespace *dup_mnt_ns(struct struct mnt_namespace *mnt_ns = tsk->nsproxy->mnt_ns; struct mnt_namespace *new_ns; struct vfsmount *rootmnt = NULL, *pwdmnt = NULL, *altrootmnt = NULL; + struct vfsmount *shdwrootmnt = NULL, *shdwpwdmnt = NULL; struct vfsmount *p, *q; new_ns = kmalloc(sizeof(struct mnt_namespace), GFP_KERNEL); @@ -1494,6 +1495,14 @@ struct mnt_namespace *dup_mnt_ns(struct altrootmnt = p; fs->altrootmnt = mntget(q); } + if (p == fs->shdwrootmnt) { + shdwrootmnt = p; + fs->shdwrootmnt = mntget(q); + } + if (p == fs->shdwpwdmnt) { + shdwpwdmnt = p; + fs->shdwpwdmnt = mntget(q); + } } p = next_mnt(p, mnt_ns->root); q = next_mnt(q, new_ns->root); @@ -1506,6 +1515,10 @@ struct mnt_namespace *dup_mnt_ns(struct mntput(pwdmnt); if (altrootmnt) mntput(altrootmnt); + if (shdwrootmnt) + mntput(shdwrootmnt); + if (shdwpwdmnt) + mntput(shdwpwdmnt); return new_ns; } --- orig/fs/exec.c 2007-02-20 07:34:32.000000000 +0100 +++ new/fs/exec.c 2007-04-15 20:10:01.000000000 +0200 @@ -856,8 +856,11 @@ int flush_old_exec(struct linux_binprm * if (current->euid == current->uid && current->egid == current->gid) current->mm->dumpable = 1; - else + else { current->mm->dumpable = suid_dumpable; + /* switch off the shadow directories for a suid exec */ + current->fs->flags &= ~SHDW_ENABLED; + } name = bprm->filename; --- orig/fs/proc/base.c 2007-02-20 07:34:32.000000000 +0100 +++ new/fs/proc/base.c 2007-05-09 20:57:37.000000000 +0200 @@ -168,6 +168,32 @@ static int proc_cwd_link(struct inode *i return result; } +static int proc_shdwcwd_link(struct inode *inode, struct dentry **dentry, + struct vfsmount **mnt) +{ + struct task_struct *task = get_proc_task(inode); + struct fs_struct *fs = NULL; + int result = -ENOENT; + + if (task) { + fs = get_fs_struct(task); + put_task_struct(task); + } + if (fs) { + read_lock(&fs->lock); + *dentry = dget(fs->shdwpwd); + if (fs->shdwpwd) + *mnt = mntget(fs->shdwpwdmnt); + else + *mnt = NULL; + read_unlock(&fs->lock); + if (*dentry) + result = 0; + put_fs_struct(fs); + } + return result; +} + static int proc_root_link(struct inode *inode, struct dentry **dentry, struct vfsmount **mnt) { struct task_struct *task = get_proc_task(inode); @@ -189,6 +215,29 @@ static int proc_root_link(struct inode * return result; } +static int proc_shdwroot_link(struct inode *inode, struct dentry **dentry, + struct vfsmount **mnt) +{ + struct task_struct *task = get_proc_task(inode); + struct fs_struct *fs = NULL; + int result = -ENOENT; + + if (task) { + fs = get_fs_struct(task); + put_task_struct(task); + } + if (fs) { + read_lock(&fs->lock); + *mnt = mntget(fs->shdwrootmnt); + *dentry = dget(fs->shdwroot); + read_unlock(&fs->lock); + if (*dentry) + result = 0; + put_fs_struct(fs); + } + return result; +} + #define MAY_PTRACE(task) \ (task == current || \ (task->parent == current && \ @@ -1878,6 +1927,8 @@ static struct pid_entry tgid_base_stuff[ #ifdef CONFIG_FAULT_INJECTION REG("make-it-fail", S_IRUGO|S_IWUSR, fault_inject), #endif + LNK("root-shdw", shdwroot), + LNK("cwd-shdw", shdwcwd), #ifdef CONFIG_TASK_IO_ACCOUNTING INF("io", S_IRUGO, pid_io_accounting), #endif @@ -2159,6 +2210,8 @@ static struct pid_entry tid_base_stuff[] #ifdef CONFIG_FAULT_INJECTION REG("make-it-fail", S_IRUGO|S_IWUSR, fault_inject), #endif + LNK("root-shdw", shdwroot), + LNK("cwd-shdw", shdwcwd), }; static int proc_tid_base_readdir(struct file * filp, --- orig/fs/proc/array.c 2007-02-20 07:34:32.000000000 +0100 +++ new/fs/proc/array.c 2007-03-20 20:02:27.000000000 +0100 @@ -291,6 +291,28 @@ static inline char *task_cap(struct task cap_t(p->cap_effective)); } +static inline char *task_fsinfo(struct task_struct *p, char *buffer) +{ + int enabled = 0, use_esc = 0, esc_ch = 0; + + rcu_read_lock(); + task_lock(p); + if (p->fs) { + read_lock(&p->fs->lock); + enabled = (p->fs->flags & SHDW_ENABLED) ? 1 : 0; + use_esc = (p->fs->flags & SHDW_USE_ESC) ? 1 : 0; + esc_ch = p->fs->shdw_escch; + read_unlock(&p->fs->lock); + } + task_unlock(p); + rcu_read_unlock(); + + return buffer + sprintf(buffer, "Shdw_Enabled:\t%d\n" + "Shdw_UseEscChar: %d\n" + "Shdw_EscChar:\t%u\n", + enabled, use_esc, (unsigned int)esc_ch); +} + int proc_pid_status(struct task_struct *task, char * buffer) { char * orig = buffer; @@ -309,6 +331,7 @@ int proc_pid_status(struct task_struct * #if defined(CONFIG_S390) buffer = task_show_regs(task, buffer); #endif + buffer = task_fsinfo(task, buffer); return buffer - orig; } --- orig/include/linux/fs.h 2007-02-20 07:34:32.000000000 +0100 +++ new/include/linux/fs.h 2007-03-02 20:05:03.000000000 +0100 @@ -263,6 +263,14 @@ extern int dir_notify_enable; #define SYNC_FILE_RANGE_WRITE 2 #define SYNC_FILE_RANGE_WAIT_AFTER 4 +/* sys_setshdwinfo(), sys_getshdwinfo(): */ +#define FSI_SHDW_ENABLE 1 /* enable shadow directories */ +#define FSI_SHDW_ESC_EN 2 /* enable use of escape character */ +#define FSI_SHDW_ESC_CHAR 3 /* specify escape character */ +/* sys_setshdwpath */ +#define SHDW_FD_ROOT -1 /* pseudo FD for root shadow dir */ +#define SHDW_FD_PWD -2 /* pseudo FD for pwd shadow dir */ + #ifdef __KERNEL__ #include @@ -739,6 +747,16 @@ struct file { spinlock_t f_ep_lock; #endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping; + + /* the following fields are protected by f_owner.lock */ + /* | f_shdw | f_shdwmnt | result + +----------+-------------+------------ + | NULL | NULL | delayed + | NULL | !NULL | invalid + | !NULL | NULL | BUG + | !NULL | !NULL | valid */ + struct dentry *f_shdw; + struct vfsmount *f_shdwmnt; }; extern spinlock_t files_lock; #define file_list_lock() spin_lock(&files_lock); --- orig/include/linux/fs_struct.h 2007-02-20 07:34:32.000000000 +0100 +++ new/include/linux/fs_struct.h 2007-05-09 20:44:00.000000000 +0200 @@ -10,8 +10,31 @@ struct fs_struct { int umask; struct dentry * root, * pwd, * altroot; struct vfsmount * rootmnt, * pwdmnt, * altrootmnt; + + int flags; + /* shadow dirs: root and pwd */ + /* | shdwroot | shdwrootmnt | result + +----------+-------------+------------ + | NULL | NULL | BUG_ON(flags&SHDW_ENABLED) + | !NULL | !NULL | ok + +==========+=============+============ + | shdwpwd | shdwpwdmnt | result + +----------+-------------+------------ + | NULL | NULL | delayed + | NULL | !NULL | invalid + | !NULL | NULL | BUG + | !NULL | !NULL | valid */ + struct dentry *shdwroot, *shdwpwd; + struct vfsmount *shdwrootmnt, *shdwpwdmnt; + /* shadow dirs: escape character */ + unsigned char shdw_escch; }; +/* bitflags for fs_struct.flags */ +#define SHDW_ENABLED 1 /* are shadow dirs enabled? */ +#define SHDW_USE_ESC 2 /* use escape char in shadow dirs? */ + + #define INIT_FS { \ .count = ATOMIC_INIT(1), \ .lock = RW_LOCK_UNLOCKED, \ @@ -24,6 +47,8 @@ extern void exit_fs(struct task_struct * extern void set_fs_altroot(void); extern void set_fs_root(struct fs_struct *, struct vfsmount *, struct dentry *); extern void set_fs_pwd(struct fs_struct *, struct vfsmount *, struct dentry *); +extern void set_fs_shdwpwd(struct fs_struct *fs, + struct vfsmount *mnt, struct dentry *dentry); extern struct fs_struct *copy_fs_struct(struct fs_struct *); extern void put_fs_struct(struct fs_struct *); --- orig/include/linux/namei.h 2007-02-20 07:34:32.000000000 +0100 +++ new/include/linux/namei.h 2007-05-09 20:58:43.000000000 +0200 @@ -22,6 +22,7 @@ struct nameidata { int last_type; unsigned depth; char *saved_names[MAX_NESTED_LINKS + 1]; + unsigned char find_char; /* Intent data */ union { @@ -54,6 +55,16 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LA #define LOOKUP_PARENT 16 #define LOOKUP_NOALT 32 #define LOOKUP_REVAL 64 + +/* don't fallback to lookup in shadow directory */ +#define LOOKUP_NOSHDW 128 +/* try to find nameidata.find_char in pathname, + * set LOOKUP_CHARFOUND in nameidata.flags if found */ +#define LOOKUP_FINDCHAR (1<<16) +#define LOOKUP_CHARFOUND (1<<17) +/* (dentry,mnt) was found in shadow dir */ +#define LOOKUP_INSHDW (1<<18) + /* * Intent data */ @@ -68,14 +79,19 @@ extern int FASTCALL(__user_walk_fd(int d __user_walk_fd(AT_FDCWD, name, LOOKUP_FOLLOW, nd) #define user_path_walk_link(name,nd) \ __user_walk_fd(AT_FDCWD, name, 0, nd) + +extern int FASTCALL(path_lookup_shdw(int dfd, const char *name, + unsigned int flags, struct nameidata *nd)); extern int FASTCALL(path_lookup(const char *, unsigned, struct nameidata *)); extern int FASTCALL(path_walk(const char *, struct nameidata *)); extern int FASTCALL(link_path_walk(const char *, struct nameidata *)); extern void path_release(struct nameidata *); extern void path_release_on_umount(struct nameidata *); -extern int __user_path_lookup_open(const char __user *, unsigned lookup_flags, struct nameidata *nd, int open_flags); -extern int path_lookup_open(int dfd, const char *name, unsigned lookup_flags, struct nameidata *, int open_flags); +extern int __user_path_lookup_open(const char __user *, unsigned lookup_flags, + struct nameidata *nd, int open_flags); +extern int path_lookup_open(int dfd, const char *name, unsigned lookup_flags, + struct nameidata *, int open_flags); extern struct file *lookup_instantiate_filp(struct nameidata *nd, struct dentry *dentry, int (*open)(struct inode *, struct file *)); extern struct file *nameidata_to_filp(struct nameidata *nd, int flags); @@ -89,6 +105,9 @@ extern int follow_up(struct vfsmount **, extern struct dentry *lock_rename(struct dentry *, struct dentry *); extern void unlock_rename(struct dentry *, struct dentry *); +extern int get_file_shdwdir(struct file *file, struct dentry **dentry, + struct vfsmount **mnt); + static inline void nd_set_link(struct nameidata *nd, char *path) { nd->saved_names[nd->depth] = path; --- orig/include/linux/file.h 2007-02-20 07:34:32.000000000 +0100 +++ new/include/linux/file.h 2007-05-09 20:59:58.000000000 +0200 @@ -68,6 +68,8 @@ static inline void fput_light(struct fil fput(file); } +extern struct file * FASTCALL(__fget(struct files_struct *files, + unsigned int fd)); extern struct file * FASTCALL(fget(unsigned int fd)); extern struct file * FASTCALL(fget_light(unsigned int fd, int *fput_needed)); extern void FASTCALL(set_close_on_exec(unsigned int fd, int flag)); --- orig/kernel/exit.c 2007-02-20 07:34:32.000000000 +0100 +++ new/kernel/exit.c 2007-03-02 20:05:03.000000000 +0100 @@ -515,6 +515,14 @@ static inline void __put_fs_struct(struc dput(fs->altroot); mntput(fs->altrootmnt); } + if (fs->shdwroot) { + dput(fs->shdwroot); + mntput(fs->shdwrootmnt); + } + if (fs->shdwpwd) { + dput(fs->shdwpwd); + mntput(fs->shdwpwdmnt); + } kmem_cache_free(fs_cachep, fs); } } --- orig/kernel/fork.c 2007-02-20 07:34:32.000000000 +0100 +++ new/kernel/fork.c 2007-03-02 20:05:03.000000000 +0100 @@ -581,6 +581,9 @@ static inline struct fs_struct *__copy_f fs->root = dget(old->root); fs->pwdmnt = mntget(old->pwdmnt); fs->pwd = dget(old->pwd); + fs->flags = old->flags; + fs->shdw_escch = old->shdw_escch; + if (old->altroot) { fs->altrootmnt = mntget(old->altrootmnt); fs->altroot = dget(old->altroot); @@ -588,6 +591,23 @@ static inline struct fs_struct *__copy_f fs->altrootmnt = NULL; fs->altroot = NULL; } + + if (old->shdwroot) { + fs->shdwrootmnt = mntget(old->shdwrootmnt); + fs->shdwroot = dget(old->shdwroot); + } else { + fs->shdwrootmnt = NULL; + fs->shdwroot = NULL; + } + + if (old->shdwpwd) { + fs->shdwpwdmnt = mntget(old->shdwpwdmnt); + fs->shdwpwd = dget(old->shdwpwd); + } else { + fs->shdwpwdmnt = NULL; + fs->shdwpwd = NULL; + } + read_unlock(&old->lock); } return fs; --- orig/include/linux/syscalls.h 2007-02-20 07:34:32.000000000 +0100 +++ new/include/linux/syscalls.h 2007-03-02 20:05:03.000000000 +0100 @@ -605,4 +605,10 @@ asmlinkage long sys_getcpu(unsigned __us int kernel_execve(const char *filename, char *const argv[], char *const envp[]); +asmlinkage long sys_getshdwinfo(pid_t pid, int func, int __user *data); + +asmlinkage long sys_setshdwinfo(pid_t pid, int func, int data); + +asmlinkage long sys_setshdwpath(pid_t pid, int fd, const char __user *path); + #endif --- orig/arch/i386/kernel/syscall_table.S 2007-02-20 07:34:32.000000000 +0100 +++ new/arch/i386/kernel/syscall_table.S 2007-03-02 20:56:40.000000000 +0100 @@ -319,3 +319,6 @@ ENTRY(sys_call_table) .long sys_move_pages .long sys_getcpu .long sys_epoll_pwait + .long sys_getshdwinfo /* 320 */ + .long sys_setshdwinfo + .long sys_setshdwpath