From 97ec7dbf2df810981acc980f0d1f39b18a7bf1f1 Mon Sep 17 00:00:00 2001 From: "Lucas C. Villa Real" Date: Mon, 11 Apr 2016 00:44:39 -0300 Subject: [PATCH] New branch to track the development of the GoboHide kernel extension. --- fs/Kconfig | 10 + fs/Makefile | 1 + fs/gobohide.c | 516 +++++++++++++++++++++++++++++++++++++++ fs/ioctl.c | 5 + fs/namei.c | 31 ++- fs/readdir.c | 29 +++ include/linux/gobohide.h | 58 +++++ include/uapi/linux/fs.h | 1 + 8 files changed, 649 insertions(+), 2 deletions(-) create mode 100644 fs/gobohide.c create mode 100644 include/linux/gobohide.h diff --git a/fs/Kconfig b/fs/Kconfig index 6725f59c18e6b5..cba54d982d020f 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -110,6 +110,16 @@ source "fs/udf/Kconfig" endmenu endif # BLOCK +config GOBOHIDE_FS + bool "GoboHide support on file systems" + default y + help + GoboHide is a general interface for providing real hidden files in + a filesystem. GoboHide supports all filesystems sitting on the VFS, + such as EXT3, SquashFS and JFFS2. + To use it, see the documentation of the gobohide userspace tool at + . + if BLOCK menu "DOS/FAT/NT Filesystems" diff --git a/fs/Makefile b/fs/Makefile index 85b6e13b62d363..af004490f0d3c3 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -47,6 +47,7 @@ obj-$(CONFIG_FS_POSIX_ACL) += posix_acl.o obj-$(CONFIG_NFS_COMMON) += nfs_common/ obj-$(CONFIG_COREDUMP) += coredump.o obj-$(CONFIG_SYSCTL) += drop_caches.o +obj-$(CONFIG_GOBOHIDE_FS) += gobohide.o obj-$(CONFIG_FHANDLE) += fhandle.o diff --git a/fs/gobohide.c b/fs/gobohide.c new file mode 100644 index 00000000000000..493f65aa45ffff --- /dev/null +++ b/fs/gobohide.c @@ -0,0 +1,516 @@ +/* + * Copyright (C) 2002-2011 GoboLinux.org + * + * These modifications are released under the GNU General Public License + * version 2 or later, incorporated herein by reference. + * Modifications/features/bug fixes based on or derived from this code + * fall under the GPL and must retain the authorship, copyright and license + * notice. This file is not a complete program and may only be used when + * the entire operating system is licensed under the GPL. + * + * See the file COPYING in this distribution for more information. + * + * Author: Felipe W Damasio . + * Original idea: Lucas C. Villa Real + * + * Changes: + * 12-Sep-2011 - Lucas C. Villa Real + * Take the superblock into account when comparing the dentries + * in order to allow mount points to be hidden. + * + * 03-Sep-2011 - Lucas C. Villa Real + * Security updates. Thanks to Dan Rosenberg for his code review. + * + * 18-May-2007 - Lucas C. Villa Real + * Added support to unionfs. + * + * 04-Jul-2006 - Lucas C. Villa Real + * Added GoboHide support to all filesystems through the VFS. + * + * 21-Feb-2004 - Lucas C. Villa Real + * Added an extra check for the inode's VFS root, so that + * the same inode number on different partitions don't get + * hidden mistakenly. + * + * 11-Nov-2003 - Lucas C. Villa Real + * Removed the spinlocks from gobolinux_show_hidden(), since + * we were already working with list_for_each_safe(), which + * iterates safely against removal of list entries. + * + * 05-May-2003 - Felipe W Damasio + * Using read-write locks instead of spinlocks, + * improving quite a bit read operations + * (allow concurrent readers, but only a single writer) + * + * 28-Apr-2003 - Lucas C. Villa Real + * Centralized checks for UID on gobolinux/fs/ioctl.c. + * Fixed get_free_page() to work on 64-bit archs as well. + * + * 12-Apr-2003 - Lucas C. Villa Real + * Removed support for UID's different than 0 hide inodes. + * + * 24-Mar-2003 - Lucas C. Villa Real + * Modified struct hide and calls so we have pathnames related + * to the "real" root dir and not the the mount point. + * + * 17-Mar-2003 - Lucas C. Villa Real + * Added support for full pathname, rather than dealing only + * with inode numbers. + * + * 10-Jan-2003 - Lucas C. Villa Real + * Added statistics. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mount.h" + +#ifdef CONFIG_UNION_FS +#include "unionfs/union.h" +#endif + +#include + +#define GOBOHIDE_INODE_EQUALS(p,i) ((p)->dentry->d_inode->i_ino == i) + +static LIST_HEAD(gobohide_inode_list); +static int gobohide_inode_list_size = 0; +static DEFINE_RWLOCK(gobohide_inode_rwlock); + +static int gobohide_resolve_path(struct hide *entry); +static int gobohide_remove_unlocked(struct hide *entry, int remove); +static int gobohide_inode_add(ino_t ino, const char *pathname); +static int gobohide_inode_del(ino_t ino, const char *pathname); +static int gobohide_count_hidden(struct gobohide_user __user *uhide); +static int gobohide_get_hidden(int count, struct gobohide_user __user *uhide); +static struct hide *gobohide_get_unlocked(ino_t i_ino, const char *filename, + int namelen, struct dentry *parent); + +/** + * gobohide_fs_ioctl - Handle fs-related ioctls + * @inode: inode number being added/removed from the hide-list + * @hide: structure containing the user's request + */ +int gobohide_fs_ioctl(struct inode *inode, unsigned long arg) +{ + struct gobohide_user __user *uhide = (struct gobohide_user __user *) arg; + struct gobohide_user hide; + struct user_namespace *ns = current_user_ns(); + const struct cred *cred = current_cred(); + kuid_t root_uid = make_kuid(ns, 0); + int error = 0; + + if (copy_from_user(&hide, uhide, sizeof(struct gobohide_user))) + return -EFAULT; + + /* We only support symbolic links and directories */ + if (hide.inode && !S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode)) + return -EINVAL; + + /* We only allow process with admin privileges + * to use the fs-related gobo ioctls + */ + if (!uid_eq(cred->uid, root_uid) && !uid_eq(cred->euid, root_uid)) + return -EPERM; + + switch (hide.operation) { + case GOBOHIDE_HIDEINODE: + error = gobohide_inode_add(hide.inode, hide.pathname); + break; + case GOBOHIDE_UNHIDEINODE: + error = gobohide_inode_del(hide.inode, hide.pathname); + break; + case GOBOHIDE_COUNTHIDDEN: + error = gobohide_count_hidden(uhide); + break; + case GOBOHIDE_GETHIDDEN: + error = gobohide_get_hidden(hide.stats.hidden_inodes, uhide); + break; + default: + return -EOPNOTSUPP; + } + + return error; +} + +/** + * gobohide_resolve_path - Resolves the pathname of a given dentry + * @entry: structure holding the dentry structure and the destination buffer. + * + * After the structure has been used, its member 'page' must be returned with + * a call to free_page(). + */ +static int gobohide_resolve_path(struct hide *entry) +{ + int len, ret; + struct file *filp = entry->filp; + struct path path = { .mnt = filp->f_path.mnt, .dentry = filp->f_path.dentry }; + + entry->page = __get_free_page(GFP_USER); + if (! entry->page) + return -ENOMEM; + + entry->pathname = d_path(&path, (char *) entry->page, PAGE_SIZE); + if (IS_ERR(entry->pathname)) { + ret = PTR_ERR(entry->pathname); + entry->pathname = NULL; + free_page(entry->page); + entry->page = 0; + return ret; + } + + len = PAGE_SIZE + entry->page - (unsigned long) entry->pathname; + return len < PATH_MAX ? len : PATH_MAX; +} + +/** + * gobohide_count_hidden - Counts how many inodes are hidden. + * @hide: the structure containing a pointer to store the number of inodes + * hidden. + */ +static int gobohide_count_hidden(struct gobohide_user __user *uhide) +{ + struct gobohide_stats __user *stats = &uhide->stats; + unsigned long flags; + int num, ret; + + read_lock_irqsave(&gobohide_inode_rwlock, flags); + num = gobohide_inode_list_size; + read_unlock_irqrestore(&gobohide_inode_rwlock, flags); + + ret = put_user(num, &stats->hidden_inodes); + if (ret) + return -EFAULT; + + return 0; +} + +/** + * gobohide_get_hidden - Get the currently hidden inodes. The uhide structure + * has been already verified and validated by the caller, so we're safe + * to invoke copy_to_user to members of that structure. + * + * @count: maximum number of entries to copy to the user-provided buffer. + * @hide: the structure containing a pointer to a previous-allocated array + * of no more than @hide->stats.hidden_inodes elements of unsigned long. + * + * This array is filled with the directories being hidden. + */ +static int gobohide_get_hidden(int count, struct gobohide_user __user *uhide) +{ + struct gobohide_stats __user *stats = &uhide->stats; + struct hide *entry, *next, **array; + int i, copied_entries = 0, ret = 0; + unsigned long flags; + size_t len; + + array = kmalloc(sizeof(struct hide *) * count, GFP_KERNEL); + if (! array) + return -ENOMEM; + + /* Since copy_to_user may sleep data can't be copied with the lock held */ + write_lock_irqsave(&gobohide_inode_rwlock, flags); + if (gobohide_inode_list_size) { + list_for_each_entry_safe(entry, next, &gobohide_inode_list, head) { + if (entry && (copied_entries < count)) { + /* Don't let the entry go away */ + entry->refcount++; + array[copied_entries++] = entry; + } else + break; + } + } + write_unlock_irqrestore(&gobohide_inode_rwlock, flags); + + /* Write the list of entries to user memory */ + for (i=0; ipathname); + if (ret == 0 && copy_to_user(stats->hidden_list[i], + entry->pathname,len)) { + /* Don't break out so that all entries are put() back */ + ret = -EFAULT; + } + gobohide_put(entry); + } + + /* Update filled_size with the number of entries which were copied */ + if (ret == 0 && put_user(copied_entries, &stats->filled_size)) + ret = -EFAULT; + + kfree(array); + return ret; +} + +ino_t gobohide_translate_inode_nr(struct inode *inode) +{ + ino_t ino = inode->i_ino; +#ifdef CONFIG_UNION_FS + if (inode->i_sb->s_op == &unionfs_sops) { + /* we must take the inode number from the underlying filesystem */ + struct inode *lower_inode = unionfs_lower_inode(inode); + ino = lower_inode ? lower_inode->i_ino : inode->i_ino; + } +#endif + return ino; +} + +/** + * gobohide_inode_add - Add the inode to the "must hide" list + * @ino: inode to be added + * @pathname: the pathname associated with @ino + */ +static int gobohide_inode_add(ino_t ino, const char *pathname) +{ + int len, ret; + struct path *path; + struct hide *entry, *old; + struct dentry *dentry; + unsigned long flags; + + entry = kmalloc(sizeof(struct hide), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + entry->refcount = 1; + entry->unlinked = 0; + + path = &entry->path; + ret = user_lpath(pathname, path); + if (ret) + goto out_free; + else if (! GOBOHIDE_INODE_EQUALS(path, ino)) { + ret = -ENOENT; + goto out_path_put; + } + + entry->filp = dentry_open(path, O_NOFOLLOW|O_PATH, current_cred()); + if (IS_ERR(entry->filp)) { + ret = PTR_ERR(entry->filp); + goto out_path_put; + } + + len = gobohide_resolve_path(entry); + if (len < 0) { + ret = len; + goto out_fput; + } + + dentry = entry->filp->f_path.dentry; + if (! dentry) { + ret = -ENOENT; + goto out_free_page; + } + + /* Get updated inode number */ + entry->i_ino = gobohide_translate_inode_nr(dentry->d_inode); + + write_lock_irqsave(&gobohide_inode_rwlock, flags); + old = gobohide_get_unlocked(entry->i_ino, dentry->d_name.name, + dentry->d_name.len+1, dentry->d_parent); + + if (old) { + write_unlock_irqrestore(&gobohide_inode_rwlock, flags); + ret = -EEXIST; + goto out_free_page; + } + + gobohide_inode_list_size++; + list_add(&entry->head, &gobohide_inode_list); + write_unlock_irqrestore(&gobohide_inode_rwlock, flags); + + return 0; + +out_free_page: + free_page(entry->page); +out_fput: + fput(entry->filp); +out_path_put: + path_put(path); +out_free: + kfree(entry); + return ret; +} + +/** + * gobohide_inode_del - Remove the inode from the "must hide" list + * @ino: inode to be removed + */ +static int gobohide_inode_del(ino_t ino, const char *pathname) +{ + int len, ret; + struct path *path; + unsigned long flags; + struct hide n, *entry, *aux; + + path = &n.path; + ret = user_lpath(pathname, path); + if (ret) + return ret; + else if (! GOBOHIDE_INODE_EQUALS(path, ino)) { + ret = -ENOENT; + goto out_path_put; + } + + n.filp = dentry_open(path, O_NOFOLLOW|O_PATH, current_cred()); + if (IS_ERR(n.filp)) { + ret = PTR_ERR(n.filp); + goto out_path_put; + } + + len = gobohide_resolve_path(&n); + if (len < 0) { + ret = len; + goto out_fput; + } + + /* Get updated inode number */ + ino = gobohide_translate_inode_nr(n.filp->f_path.dentry->d_inode); + + write_lock_irqsave(&gobohide_inode_rwlock, flags); + if (gobohide_inode_list_size) { + list_for_each_entry_safe(entry, aux, &gobohide_inode_list, head) { + struct file *filp = entry->filp; + struct dentry *filp_dentry = filp->f_path.dentry; + struct mount *mnt = real_mount(filp->f_path.mnt)->mnt_parent; + struct dentry *mnt_dentry = mnt->mnt_mountpoint; + ino_t mnt_ino = mnt_dentry->d_inode->i_ino; + + if ((entry->i_ino == ino && path->dentry->d_sb == filp_dentry->d_sb) || + (mnt_ino == ino && path->dentry->d_sb == mnt_dentry->d_sb)) { + gobohide_remove_unlocked(entry, 1); + break; + } + } + } + write_unlock_irqrestore(&gobohide_inode_rwlock, flags); + + free_page(n.page); + ret = 0; + +out_fput: + fput(n.filp); +out_path_put: + path_put(path); + return ret; +} + +/** + * gobohide_remove - Effectively removes the inode from the inode_list. + * @hide: struct hide to be removed + */ +int gobohide_remove(struct hide *entry) +{ + unsigned long flags; + int ret; + + write_lock_irqsave(&gobohide_inode_rwlock, flags); + ret = gobohide_remove_unlocked(entry, 1); + write_unlock_irqrestore(&gobohide_inode_rwlock, flags); + + return ret; +} + +static int gobohide_remove_unlocked(struct hide *entry, int remove) +{ + if (remove && ! entry->unlinked) { + /* Remove from the linked list */ + entry->unlinked = true; + list_del(&entry->head); + gobohide_inode_list_size--; + } + if (--entry->refcount == 0) { + free_page(entry->page); + fput(entry->filp); + path_put(&entry->path); + kfree(entry); + } + return 0; +} + +/** + * gobohide_get - Get the struct hide associated to the given inode. The inode + * is verified to exist in the "must hide" list through the comparison of the + * inode number and the superblock. + * + * @ino: inode being readdir'd + * @filename: inode's filename + * @namelen: inodes's filename length in bytes + * @parent: the parent dentry for the given inode. + * + * If the inode number is in the inode_list, returns a pointer to its entry + * in the inode_list or NULL if it isn't there. The returned entry must be + * released with gobohide_put(). + */ +struct hide *gobohide_get(ino_t ino, const char *filename, int namelen, + struct dentry *parent) +{ + unsigned long flags; + struct hide *entry; + + write_lock_irqsave(&gobohide_inode_rwlock, flags); + entry = gobohide_get_unlocked(ino, filename, namelen, parent); + write_unlock_irqrestore(&gobohide_inode_rwlock, flags); + + return entry; +} + +static struct hide *gobohide_get_unlocked(ino_t ino, const char *filename, + int namelen, struct dentry *parent) +{ + struct hide *entry = NULL; + + if (! ino || ! gobohide_inode_list_size) + return NULL; + + list_for_each_entry(entry, &gobohide_inode_list, head) { + struct file *filp = entry->filp; + struct dentry *filp_dentry = filp->f_path.dentry; + struct mount *mnt = real_mount(filp->f_path.mnt); + struct dentry *mnt_dentry = mnt->mnt_mountpoint; + ino_t mnt_ino = mnt_dentry->d_inode->i_ino; + + if ((entry->i_ino == ino && parent->d_sb == filp_dentry->d_sb) || + (mnt_ino == ino && parent->d_sb == mnt_dentry->d_sb)) { + /* Increment the reference count and return the object */ + entry->refcount++; + return entry; + } + } + + return NULL; +} + +/* + * Return an entry obtained from the gobohide_inode_list with gobohide_get(). + * @param entry Entry obtained from gobohide_get(). + */ +int gobohide_put(struct hide *entry) +{ + unsigned long flags; + int ret = -EINVAL; + + if (entry) { + write_lock_irqsave(&gobohide_inode_rwlock, flags); + ret = gobohide_remove_unlocked(entry, 0); + write_unlock_irqrestore(&gobohide_inode_rwlock, flags); + } + + return ret; +} + +EXPORT_SYMBOL(gobohide_get); +EXPORT_SYMBOL(gobohide_put); +EXPORT_SYMBOL(gobohide_remove); +EXPORT_SYMBOL(gobohide_fs_ioctl); diff --git a/fs/ioctl.c b/fs/ioctl.c index 116a333e9c773c..5b48927241f0cc 100644 --- a/fs/ioctl.c +++ b/fs/ioctl.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "internal.h" #include @@ -667,6 +668,10 @@ int do_vfs_ioctl(struct file *filp, unsigned int fd, unsigned int cmd, case FIDEDUPERANGE: return ioctl_file_dedupe_range(filp, argp); + case FIGOBOHIDE: + error = arg ? gobohide_fs_ioctl(inode, arg) : -EINVAL; + break; + default: if (S_ISREG(inode->i_mode)) error = file_ioctl(filp, cmd, arg); diff --git a/fs/namei.c b/fs/namei.c index 1d9ca2d5dff68e..349d231c332fee 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include "internal.h" @@ -3689,6 +3690,7 @@ SYSCALL_DEFINE2(mkdir, const char __user *, pathname, umode_t, mode) int vfs_rmdir(struct inode *dir, struct dentry *dentry) { + struct hide *hidden = NULL; int error = may_delete(dir, dentry, 1); if (error) @@ -3720,8 +3722,13 @@ int vfs_rmdir(struct inode *dir, struct dentry *dentry) out: inode_unlock(dentry->d_inode); dput(dentry); - if (!error) - d_delete(dentry); + if (!error) { + if (hidden) + gobohide_remove(hidden); + d_delete(dentry); + } + if (hidden) + gobohide_put(hidden); return error; } EXPORT_SYMBOL(vfs_rmdir); @@ -3810,6 +3817,7 @@ SYSCALL_DEFINE1(rmdir, const char __user *, pathname) */ int vfs_unlink(struct inode *dir, struct dentry *dentry, struct inode **delegated_inode) { + struct hide *hidden = NULL; struct inode *target = dentry->d_inode; int error = may_delete(dir, dentry, 0); @@ -3819,6 +3827,13 @@ int vfs_unlink(struct inode *dir, struct dentry *dentry, struct inode **delegate if (!dir->i_op->unlink) return -EPERM; + if (dentry->d_inode) { + ino_t ino = gobohide_translate_inode_nr(dentry->d_inode); + if (ino) + hidden = gobohide_get(ino, dentry->d_name.name, + dentry->d_name.len, dentry->d_parent); + } + inode_lock(target); if (is_local_mountpoint(dentry)) error = -EBUSY; @@ -3840,10 +3855,14 @@ int vfs_unlink(struct inode *dir, struct dentry *dentry, struct inode **delegate /* We don't d_delete() NFS sillyrenamed files--they still exist. */ if (!error && !(dentry->d_flags & DCACHE_NFSFS_RENAMED)) { + if (hidden) + gobohide_remove(hidden); fsnotify_link_count(target); d_delete(dentry); } + if (hidden) + gobohide_put(hidden); return error; } EXPORT_SYMBOL(vfs_unlink); @@ -3945,6 +3964,7 @@ SYSCALL_DEFINE1(unlink, const char __user *, pathname) int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname) { + struct hide *hidden = NULL; int error = may_create(dir, dentry); if (error) @@ -3953,6 +3973,13 @@ int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname) if (!dir->i_op->symlink) return -EPERM; + if (dentry->d_inode) { + ino_t ino = gobohide_translate_inode_nr(dentry->d_inode); + if (ino) + hidden = gobohide_get(ino, dentry->d_name.name, + dentry->d_name.len, dentry->d_parent); + } + error = security_inode_symlink(dir, dentry, oldname); if (error) return error; diff --git a/fs/readdir.c b/fs/readdir.c index e69ef3b79787b9..dd272625319e0e 100644 --- a/fs/readdir.c +++ b/fs/readdir.c @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -72,6 +73,7 @@ struct readdir_callback { struct dir_context ctx; struct old_linux_dirent __user * dirent; int result; + struct dentry *dentry; }; static int fillonedir(struct dir_context *ctx, const char *name, int namlen, @@ -120,6 +122,7 @@ SYSCALL_DEFINE3(old_readdir, unsigned int, fd, if (!f.file) return -EBADF; + buf.dentry = f.file->f_path.dentry; error = iterate_dir(f.file, &buf.ctx); if (buf.result) error = buf.result; @@ -147,6 +150,7 @@ struct getdents_callback { struct linux_dirent __user * previous; int count; int error; + struct dentry *dentry; }; static int filldir(struct dir_context *ctx, const char *name, int namlen, @@ -156,6 +160,7 @@ static int filldir(struct dir_context *ctx, const char *name, int namlen, struct getdents_callback *buf = container_of(ctx, struct getdents_callback, ctx); unsigned long d_ino; + struct hide *hidden; int reclen = ALIGN(offsetof(struct linux_dirent, d_name) + namlen + 2, sizeof(long)); @@ -169,10 +174,20 @@ static int filldir(struct dir_context *ctx, const char *name, int namlen, } dirent = buf->previous; if (dirent) { + hidden = gobohide_get(d_ino, name, namlen, buf->dentry); + if (hidden) { + gobohide_put(hidden); + return 0; + } if (__put_user(offset, &dirent->d_off)) goto efault; } dirent = buf->current_dir; + hidden = gobohide_get(d_ino, name, namlen, buf->dentry); + if (hidden) { + gobohide_put(hidden); + return 0; + } if (__put_user(d_ino, &dirent->d_ino)) goto efault; if (__put_user(reclen, &dirent->d_reclen)) @@ -212,6 +227,7 @@ SYSCALL_DEFINE3(getdents, unsigned int, fd, if (!f.file) return -EBADF; + buf.dentry = f.file->f_path.dentry; error = iterate_dir(f.file, &buf.ctx); if (error >= 0) error = buf.error; @@ -232,12 +248,14 @@ struct getdents_callback64 { struct linux_dirent64 __user * previous; int count; int error; + struct dentry *dentry; }; static int filldir64(struct dir_context *ctx, const char *name, int namlen, loff_t offset, u64 ino, unsigned int d_type) { struct linux_dirent64 __user *dirent; + struct hide *hidden; struct getdents_callback64 *buf = container_of(ctx, struct getdents_callback64, ctx); int reclen = ALIGN(offsetof(struct linux_dirent64, d_name) + namlen + 1, @@ -248,10 +266,20 @@ static int filldir64(struct dir_context *ctx, const char *name, int namlen, return -EINVAL; dirent = buf->previous; if (dirent) { + hidden = gobohide_get(ino, name, namlen, buf->dentry); + if (hidden) { + gobohide_put(hidden); + return 0; + } if (__put_user(offset, &dirent->d_off)) goto efault; } dirent = buf->current_dir; + hidden = gobohide_get(ino, name, namlen, buf->dentry); + if (hidden) { + gobohide_put(hidden); + return 0; + } if (__put_user(ino, &dirent->d_ino)) goto efault; if (__put_user(0, &dirent->d_off)) @@ -293,6 +321,7 @@ SYSCALL_DEFINE3(getdents64, unsigned int, fd, if (!f.file) return -EBADF; + buf.dentry = f.file->f_path.dentry; error = iterate_dir(f.file, &buf.ctx); if (error >= 0) error = buf.error; diff --git a/include/linux/gobohide.h b/include/linux/gobohide.h new file mode 100644 index 00000000000000..fd96a7b2dc046b --- /dev/null +++ b/include/linux/gobohide.h @@ -0,0 +1,58 @@ +#ifndef _LINUX_GOBOHIDE_H +#define _LINUX_GOBOHIDE_H + +#include +#include + +/* Gobohide internal ioctls */ + +#define GOBOHIDE_HIDEINODE 0x0000001 /* Hide a given inode number */ +#define GOBOHIDE_UNHIDEINODE 0x0000002 /* Unhide a given inode number */ +#define GOBOHIDE_COUNTHIDDEN 0x0000003 /* Get the number of inodes hidden */ +#define GOBOHIDE_GETHIDDEN 0x0000004 /* Get the inodes hidden */ + +struct hide { + ino_t i_ino; /* shortcut to inode number */ + struct file *filp; /* used to recover the inode's pathname */ + struct path path; /* stores the path after a call to user_lpath */ + char *pathname; /* a fresh cache of the inode's pathname */ + unsigned long page; /* page on which pathname has been copied to */ + unsigned long refcount; /* number of reference counts to this object */ + int unlinked; /* has the structure been unlinked yet? */ + struct list_head head; /* a simple doubly linked list */ +}; + +struct gobohide_stats { + int hidden_inodes; /* how many inodes we're hiding */ + int filled_size; /* how many inodes we filled in hidden_list */ + char **hidden_list; /* the hidden list */ +}; + +/* Structure provided by the user on the ioctl to hide or unhide an entry */ +struct gobohide_user { + char operation; /* the operation to be performed */ + ino_t inode; /* the inode number */ + const char *pathname; /* the pathname being submitted */ + char symlink; /* is inode a symlink? */ + struct gobohide_stats stats; /* holds statistics */ +}; + +#ifdef CONFIG_GOBOHIDE_FS + +int gobohide_fs_ioctl(struct inode *inode, unsigned long arg); +ino_t gobohide_translate_inode_nr(struct inode *inode); +struct hide *gobohide_get(ino_t ino, const char *filename, + int namelen, struct dentry *parent); +int gobohide_put(struct hide *entry); +int gobohide_remove(struct hide *hide); + +#else + +#define gobohide_fs_ioctl(inode, arg) 0 +#define ino_t gobohide_translate_inode_nr(inode) 0 +#define gobohide_get(ino, filename, namelen, parent) NULL +#define gobohide_put(entry) 0 +#define gobohide_remove(hide) 0 + +#endif /* CONFIG_GOBOHIDE_FS */ +#endif /* _LINUX_GOBOHIDE_H */ diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h index a079d50376e15b..284ba0ce3fbd2d 100644 --- a/include/uapi/linux/fs.h +++ b/include/uapi/linux/fs.h @@ -233,6 +233,7 @@ struct fsxattr { #define FICLONE _IOW(0x94, 9, int) #define FICLONERANGE _IOW(0x94, 13, struct file_clone_range) #define FIDEDUPERANGE _IOWR(0x94, 54, struct file_dedupe_range) +#define FIGOBOHIDE _IOW(0x00, 0x22, size_t) /* gobohide-fs ioctl */ #define FS_IOC_GETFLAGS _IOR('f', 1, long) #define FS_IOC_SETFLAGS _IOW('f', 2, long)