#include #include #include #include #include #include #include #include #include #include #if 0 #define DEB(X) printk X #else #define DEB(X) #endif #define REDIR_VERSION "0.3" extern void *sys_call_table[]; typedef asmlinkage int (*chdir_func) (const char *); typedef asmlinkage int (*stat_func) (const char *, struct stat *); typedef asmlinkage int (*access_func) (const char *, int); typedef asmlinkage int (*open_func) (const char *, int, int); typedef asmlinkage int (*readlink_func) (const char *, char *, int); typedef asmlinkage int (*getcwd_func) (char *, unsigned long); static chdir_func orig_chdir; static stat_func orig_stat; static stat_func orig_lstat; static access_func orig_access; static open_func orig_open; static readlink_func orig_readlink; static getcwd_func orig_getcwd; typedef asmlinkage long (*stat64_func) (const char *, struct stat64 *, long); static stat64_func orig_stat64; static stat64_func orig_lstat64; #ifdef __i386__ typedef asmlinkage int (*execve_func) (struct pt_regs); static execve_func orig_execve; #endif #define AVFS_MAGIC_CHAR '#' #define PF_AVFS 0x00008000 #define OVERLAY_BASE "/mnt/tmp" #define path_ok(pwd) (pwd->d_parent == pwd || !list_empty(&pwd->d_hash)) static char *path_pwd(char *page) { return d_path(current->fs->pwd, current->fs->pwdmnt, page, PAGE_SIZE); } static int a_path_walk(const char *pathname, int flags, struct nameidata *nd) { int error; error = 0; if (path_init(pathname, flags, nd)) error = path_walk(pathname, nd); return error; } static void a_path_release(struct nameidata *nd) { dput(nd->dentry); mntput(nd->mnt); } static char *resolv_virt(const char *pathname, int must_exist, int flags) { struct nameidata root; struct nameidata nd; struct dentry *origroot; struct vfsmount *origrootmnt; char *newpathname = NULL; char *page = NULL; char *path = NULL; int pathlen = 0; int error; int newflags; char overlay_dir[128]; unsigned overlay_dir_len; sprintf(overlay_dir, "%s/%u", OVERLAY_BASE, current->fsuid); overlay_dir_len = strlen(overlay_dir); lock_kernel(); DEB((KERN_INFO "resolve_virt pathname: '%s'\n", pathname ? pathname : "(null)")); error = a_path_walk(overlay_dir, LOOKUP_POSITIVE|LOOKUP_FOLLOW, &root); if(error) goto out; origroot = current->fs->root; origrootmnt = current->fs->rootmnt; current->fs->root = root.dentry; current->fs->rootmnt = root.mnt; newflags = flags; if(must_exist) newflags |= LOOKUP_POSITIVE; error = a_path_walk(pathname, newflags, &nd); if(!error) { if(path_ok(nd.dentry)) { page = (char *) __get_free_page(GFP_USER); if(page) { path = d_path(nd.dentry, nd.mnt, page, PAGE_SIZE); DEB((KERN_INFO "resolve_virt path = '%s'\n", path)); pathlen = (unsigned int) page + PAGE_SIZE - (unsigned int) path; } } a_path_release(&nd); } current->fs->root = origroot; current->fs->rootmnt = origrootmnt; a_path_release(&root); if(path) { int isvirtual; error = a_path_walk(path, flags, &nd); if(!error) { if(nd.dentry->d_inode) isvirtual = 0; else if(must_exist) isvirtual = 1; else if(strchr(path, AVFS_MAGIC_CHAR)) isvirtual = 1; else isvirtual = 0; a_path_release(&nd); } else { isvirtual = 1; } if(!isvirtual) { newpathname = kmalloc(pathlen + 1, GFP_USER); if(newpathname) strncpy(newpathname, path, pathlen); } else { newpathname = kmalloc(overlay_dir_len + pathlen + 1, GFP_USER); if(newpathname) { strcpy(newpathname, overlay_dir); strncat(newpathname, path, pathlen); } } } if(page) free_page((unsigned long) page); DEB((KERN_INFO "resolve_virt newpathname: '%s'\n", newpathname ? newpathname : "(null)")); out: unlock_kernel(); return newpathname; } #define FUSE_SUPER_MAGIC 0x65735546 #define cwd_virtual() \ (current->fs->pwd->d_sb->s_magic == FUSE_SUPER_MAGIC) static char *get_abs_path(const char *filename) { char *cwd; int cwdlen, fnamelen; char *abspath, *s; char *page; char overlay_dir[128]; unsigned overlay_dir_len; sprintf(overlay_dir, "/mnt/avfs/%010u", current->fsuid); overlay_dir_len = strlen(overlay_dir); if(!path_ok(current->fs->pwd)) return NULL; page = (char *) __get_free_page(GFP_USER); if(!page) return NULL; cwd = path_pwd(page); cwdlen = (unsigned int) page + PAGE_SIZE - (unsigned int) cwd - 1; if(cwd_virtual() && cwdlen > overlay_dir_len) { cwd += overlay_dir_len; cwdlen -= overlay_dir_len; } fnamelen = strlen(filename); abspath = kmalloc(cwdlen + 1 + fnamelen + 1, GFP_USER); if(abspath) { s = abspath; strncpy(s, cwd, cwdlen); s += cwdlen; *s++ = '/'; strncpy(s, filename, fnamelen + 1); } free_page((unsigned long) page); return abspath; } static char *resolve_name(const char *kfilename, int must_exist, int flags) { char *tmp; char *newfilename; tmp = getname(kfilename); if(IS_ERR(tmp)) return tmp; if((tmp[0] != '/' && cwd_virtual()) || strchr(tmp, AVFS_MAGIC_CHAR)) { DEB((KERN_INFO "resolve_name: %s (%i/%s)\n", tmp, current->pid, (current->flags & PF_AVFS) ? "on" : "off")); if(strcmp(tmp, "/#avfs-on") == 0) { printk(KERN_INFO "AVFS ON (pid: %i)\n", current->pid); current->flags |= PF_AVFS; newfilename = ERR_PTR(-EEXIST); } else if(!(current->flags & PF_AVFS)) newfilename = NULL; else if(strcmp(tmp, "/#avfs-off") == 0) { printk(KERN_INFO "AVFS OFF (pid: %i)\n", current->pid); current->flags &= ~PF_AVFS; newfilename = ERR_PTR(-EEXIST); } else { if(tmp[0] == '/') { newfilename = resolv_virt(tmp, must_exist, flags); } else { char *abspath; abspath = get_abs_path(tmp); if(abspath) { newfilename = resolv_virt(abspath, must_exist, flags); kfree(abspath); } else newfilename = NULL; } } } else newfilename = NULL; putname(tmp); return newfilename; } asmlinkage int virt_chdir(const char *filename) { int ret; mm_segment_t old_fs; char *newfilename; if(!cwd_virtual()) { ret = (*orig_chdir)(filename); if(ret != -ENOENT) return ret; } else ret = 0; newfilename = resolve_name(filename, 1, 1); if(!newfilename) { if(ret) return ret; else return (*orig_chdir)(filename); } if(IS_ERR(newfilename)) return PTR_ERR(newfilename); DEB((KERN_INFO "CHDIR: trying '%s'\n", newfilename)); old_fs = get_fs(); set_fs(get_ds()); ret = (*orig_chdir)(newfilename); set_fs(old_fs); kfree(newfilename); DEB((KERN_INFO "CHDIR: result %i\n", ret)); return ret; } static int do_orig_stat(stat_func sfunc, const char *filename, struct stat *statbuf) { int ret; mm_segment_t old_fs; struct stat locbuf; old_fs = get_fs(); set_fs(get_ds()); ret = (*sfunc)(filename, &locbuf); set_fs(old_fs); if(ret == 0) ret = (copy_to_user(statbuf, &locbuf, sizeof(locbuf)) ? -EFAULT : 0); return ret; } asmlinkage int virt_stat(const char *filename, struct stat *statbuf) { int ret; char *newfilename; if(!cwd_virtual()) { ret = (*orig_stat)(filename, statbuf); if(ret != -ENOENT) return ret; } else ret = 0; newfilename = resolve_name(filename, 1, 1); if(!newfilename) { if(ret) return ret; else return (*orig_stat)(filename, statbuf); } if(IS_ERR(newfilename)) return PTR_ERR(newfilename); DEB((KERN_INFO "STAT: trying '%s'\n", newfilename)); ret = do_orig_stat(orig_stat, newfilename, statbuf); kfree(newfilename); DEB((KERN_INFO "STAT: result %i\n", ret)); return ret; } asmlinkage int virt_lstat(const char *filename, struct stat *statbuf) { int ret; char *newfilename; if(!cwd_virtual()) { ret = (*orig_lstat)(filename, statbuf); if(ret != -ENOENT) return ret; } else ret = 0; newfilename = resolve_name(filename, 1, 0); if(!newfilename) { if(ret) return ret; else return (*orig_lstat)(filename, statbuf); } if(IS_ERR(newfilename)) return PTR_ERR(newfilename); DEB((KERN_INFO "LSTAT: trying '%s'\n", newfilename)); ret = do_orig_stat(orig_lstat, newfilename, statbuf); kfree(newfilename); DEB((KERN_INFO "LSTAT: result %i\n", ret)); return ret; } asmlinkage int virt_access(const char *filename, int mode) { int ret; mm_segment_t old_fs; char *newfilename; if(!cwd_virtual()) { ret = (*orig_access)(filename, mode); if(ret != -ENOENT) return ret; } else ret = 0; newfilename = resolve_name(filename, 1, 1); if(!newfilename) { if(ret) return ret; else return (*orig_access)(filename, mode); } if(IS_ERR(newfilename)) return PTR_ERR(newfilename); DEB((KERN_INFO "ACCESS: trying '%s'\n", newfilename)); old_fs = get_fs(); set_fs(get_ds()); ret = (*orig_access)(newfilename, mode); set_fs(old_fs); kfree(newfilename); DEB((KERN_INFO "ACCESS: result %i\n", ret)); return ret; } asmlinkage int virt_open(const char *filename, int flags, int mode) { int ret; mm_segment_t old_fs; char *newfilename; if(!cwd_virtual()) { ret = (*orig_open)(filename, flags, mode); if(ret != -ENOENT) return ret; } else ret = 0; newfilename = resolve_name(filename, 1, 1); if(!newfilename) { if(ret) return ret; else return (*orig_open)(filename, flags, mode); } if(IS_ERR(newfilename)) return PTR_ERR(newfilename); DEB((KERN_INFO "OPEN: trying '%s'\n", newfilename)); old_fs = get_fs(); set_fs(get_ds()); ret = (*orig_open)(newfilename, flags, mode); set_fs(old_fs); kfree(newfilename); DEB((KERN_INFO "OPEN: result %i\n", ret)); return ret; } asmlinkage int virt_readlink(const char *filename, char *buf, int bufsiz) { int ret; mm_segment_t old_fs; char *newfilename; char *locbuf; int len; if(!cwd_virtual()) { ret = (*orig_readlink)(filename, buf, bufsiz); if(ret != -ENOENT) return ret; } else ret = 0; newfilename = resolve_name(filename, 1, 0); if(!newfilename) { if(ret) return ret; else return (*orig_readlink)(filename, buf, bufsiz); } if(IS_ERR(newfilename)) return PTR_ERR(newfilename); DEB((KERN_INFO "READLINK: trying '%s'\n", newfilename)); /* bufsiz is legal (already checked by sys_readlink) */ len = bufsiz; if(bufsiz > PAGE_SIZE) len = PAGE_SIZE; locbuf = (char *) __get_free_page(GFP_USER); ret = -ENOMEM; if(locbuf) { old_fs = get_fs(); set_fs(get_ds()); ret = (*orig_readlink)(newfilename, locbuf, len); set_fs(old_fs); if(ret >= 0) if(copy_to_user(buf, locbuf, len)) ret = -EFAULT; free_page((unsigned long) locbuf); } kfree(newfilename); DEB((KERN_INFO "READLINK: result %i\n", ret)); return ret; } asmlinkage int virt_getcwd(char *buf, unsigned long size) { int ret; char *cwd; unsigned long cwdlen; char *page; char *newcwd; unsigned long newlen; char overlay_dir[128]; unsigned overlay_dir_len; ret = (*orig_getcwd)(buf, size); if(!cwd_virtual() || ret < 0) return ret; if(!path_ok(current->fs->pwd)) return -ENOENT; page = (char *) __get_free_page(GFP_USER); if(!page) return -ENOMEM; cwd = path_pwd(page); cwdlen = PAGE_SIZE + (page - cwd) - 1; sprintf(overlay_dir, "/mnt/avfs/%010u", current->fsuid); overlay_dir_len = strlen(overlay_dir); if(cwdlen >= overlay_dir_len && strncmp(cwd, overlay_dir, overlay_dir_len) == 0) { if(cwdlen == overlay_dir_len) { newcwd = "/"; newlen = 1; } else { newcwd = cwd + overlay_dir_len; newlen = cwdlen - overlay_dir_len; } ret = -ERANGE; if(newlen + 1 <= size) { ret = newlen + 1; if(copy_to_user(buf, newcwd, newlen + 1)) ret = -EFAULT; } } free_page((unsigned long) page); return ret; } static long do_orig_stat64(stat64_func sfunc, const char *filename, struct stat64 * statbuf, long flags) { long ret; mm_segment_t old_fs; struct stat64 locbuf; old_fs = get_fs(); set_fs(get_ds()); ret = (*sfunc)(filename, &locbuf, flags); set_fs(old_fs); if(ret == 0) ret = (copy_to_user(statbuf, &locbuf, sizeof(locbuf)) ? -EFAULT : 0); return ret; } asmlinkage long virt_stat64(char * filename, struct stat64 * statbuf, long flags) { long ret; char *newfilename; if(!cwd_virtual()) { ret = (*orig_stat64)(filename, statbuf, flags); if(ret != -ENOENT) return ret; } else ret = 0; newfilename = resolve_name(filename, 1, 1); if(!newfilename) { if(ret) return ret; else return (*orig_stat64)(filename, statbuf, flags); } if(IS_ERR(newfilename)) return PTR_ERR(newfilename); DEB((KERN_INFO "STAT64: trying '%s'\n", newfilename)); ret = do_orig_stat64(orig_stat64, newfilename, statbuf, flags); kfree(newfilename); DEB((KERN_INFO "STAT64: result %li\n", ret)); return ret; } asmlinkage long virt_lstat64(char * filename, struct stat64 * statbuf, long flags) { long ret; char *newfilename; if(!cwd_virtual()) { ret = (*orig_lstat64)(filename, statbuf, flags); if(ret != -ENOENT) return ret; } else ret = 0; newfilename = resolve_name(filename, 1, 0); if(!newfilename) { if(ret) return ret; else return (*orig_lstat64)(filename, statbuf, flags); } if(IS_ERR(newfilename)) return PTR_ERR(newfilename); DEB((KERN_INFO "LSTAT64: trying '%s'\n", newfilename)); ret = do_orig_stat64(orig_lstat64, newfilename, statbuf, flags); kfree(newfilename); DEB((KERN_INFO "LSTAT64: result %li\n", ret)); return ret; } #ifdef __i386__ asmlinkage int real_execve(struct pt_regs *regs) { int error; char * filename; filename = getname((char *) regs->ebx); error = PTR_ERR(filename); if (IS_ERR(filename)) goto out; error = do_execve(filename, (char **) regs->ecx, (char **) regs->edx, regs); if (error == 0) current->ptrace &= ~PT_DTRACE; putname(filename); out: return error; } asmlinkage int virt_execve(struct pt_regs regs) { int ret; char *newfilename; char *filename = (char *) regs.ebx; if(!cwd_virtual()) { ret = real_execve(®s); if(ret != -ENOENT) return ret; } else ret = 0; newfilename = resolve_name(filename, 1, 1); if(!newfilename) { if(ret) return ret; else return real_execve(®s); } if(IS_ERR(newfilename)) return PTR_ERR(newfilename); DEB((KERN_INFO "EXECVE: trying '%s'\n", newfilename)); ret = do_execve(newfilename, (char **) regs.ecx, (char **) regs.edx, ®s); if (ret == 0) current->ptrace &= ~PT_DTRACE; kfree(newfilename); DEB((KERN_INFO "EXECVE: result %i\n", ret)); return ret; } #endif /* __i386__ */ void *replace_syscall(int index, void *new_syscall) { void *orig_syscall = sys_call_table[index]; printk(KERN_INFO "replacing syscall nr. %3i [%p] with [%p]\n", index, orig_syscall, new_syscall); sys_call_table[index] = new_syscall; return orig_syscall; } int init_module(void) { printk(KERN_INFO "redir init (version %s)\n", REDIR_VERSION); orig_chdir = replace_syscall(__NR_chdir, virt_chdir); orig_stat = replace_syscall(__NR_stat, virt_stat); orig_lstat = replace_syscall(__NR_lstat, virt_lstat); orig_access = replace_syscall(__NR_access, virt_access); orig_open = replace_syscall(__NR_open, virt_open); orig_readlink = replace_syscall(__NR_readlink, virt_readlink); orig_getcwd = replace_syscall(__NR_getcwd, virt_getcwd); orig_stat64 = replace_syscall(__NR_stat64, virt_stat64); orig_lstat64 = replace_syscall(__NR_lstat64, virt_lstat64); #ifdef __i386__ orig_execve = replace_syscall(__NR_execve, virt_execve); #endif return 0; } void cleanup_module(void) { printk(KERN_INFO "redir cleanup\n"); replace_syscall(__NR_chdir, orig_chdir); replace_syscall(__NR_stat, orig_stat); replace_syscall(__NR_lstat, orig_lstat); replace_syscall(__NR_access, orig_access); replace_syscall(__NR_open, orig_open); replace_syscall(__NR_readlink, orig_readlink); replace_syscall(__NR_getcwd, orig_getcwd); replace_syscall(__NR_stat64, orig_stat64); replace_syscall(__NR_lstat64, orig_lstat64); #ifdef __i386__ replace_syscall(__NR_execve, orig_execve); #endif }