#include #include #include #include #include #include #include #include #include #include #include #include "lve_internal.h" #include "lve_global_params.h" #include "lsm_int.h" #include "link_protect.h" #ifdef IMPL_LINK_PROT_OLD int sandbox_path_symlink(struct path *dir /* symlink parent dir */ , struct dentry *dentry /* symlink dentry */ , const char *old_name /* target path */ ) { if (!param_is_enabled(LVE_SYMLINK_PROTECTION)) return 0; return may_create_sym_link(old_name, dir, dentry); } /* As we cannot access "link" from the LSM, let's work around it */ static inline void lve_put_link(struct nameidata *nd, struct dentry *link_dentry, void *cookie) { struct inode *inode = link_dentry->d_inode; if (inode->i_op->put_link) inode->i_op->put_link(link_dentry, nd, cookie); /* path_put(link) will be done by follow_link() on error path */ #if 0 path_put(link); #endif } static char *get_link_body(struct dentry *link, struct nameidata *nd, void **cookie) { void *p = link->d_inode->i_op->follow_link(link, nd); if (IS_ERR(p)) return NULL; *cookie = p; return nd_get_link(nd); } /* * In CL7, symlinks in the path can be one of two main types: * * "nested" - occur in the middle/beginning of the path, * for ex. /a/b/c/link/d/e, /link/b/c * * "trailing" - occur in the last path component, for ex. /a/b/c/link * * "nested" symlink depth is controlled with "current->link_count" (max = 8) * "trailing" + "nested" overall count is controlled with "current->total_link_count" (max = 40) * * "Nested" symlinks are fine, because "current->link_count" is being inc/dec on every * nested link follow-begin/follow-end, and never assigned to any fixed value. * Thus "current->link_count" is always correct and bound, * even when we invoke VFS lookup recursively from our sandbox_inode_follow_link callback. * * But we have some issues with "trailing" symlinks. * * "current->total_link_count" is zeroed every time when VFS lookup is started. * so it's incorrect when VFS lookup is being used from sandbox_inode_follow_link, * if we have more than single symlink in the path. * Such that "current->total_link_count" becomes unbound, * leading to infinite recursion in the cyclic symlink case (a->b->a). * * That's why we need a custom data structure for that recursion depth level tracking. */ static char follow_link_level[PID_MAX_LIMIT]; static inline int link_level_inc(void) { char link_level = follow_link_level[current->pid]; if (link_level == MAX_LINK_COUNT) return -ELOOP; follow_link_level[current->pid]++; return 0; } static inline void link_level_dec(void) { BUG_ON(follow_link_level[current->pid]-- == 0); } int sandbox_inode_follow_link(struct dentry *link_dentry, struct nameidata *nd) { int error = 0; char *link_body; struct path target; bool handle_filter = false; bool handle_owner = false; bool handle_proc = false; void *cookie; error = follow_link_init(&handle_owner, &handle_filter, &handle_proc); if (error != 0) return error == -1 ? 0 : error; link_body = get_link_body(link_dentry, nd, &cookie); if (IS_ERR(link_body)) return 0; if (handle_proc && is_in_lve(current) && unlikely(!link_body && nd_get_jumped(nd) && link_dentry->d_op != lve_tid_fd_dentry_operations)) { struct path root_path; get_fs_root(current->fs, &root_path); if (!path_is_under(&nd->path, &root_path)) error = -EPERM; path_put(&root_path); if (error != 0) goto out; } if (link_body == NULL || (!handle_owner && !handle_filter)) { error = 0; goto out; } error = link_level_inc(); if (error < 0) goto out; error = lve_symlink_lookup(&nd->path, link_body, &target); link_level_dec(); if (error != 0) goto out; /* if the current matches the filter */ if (handle_filter && !is_link_body_secure(link_body)) error = handle_symlink_filter(link_dentry->d_inode, target.dentry->d_inode); if (handle_owner) error = handle_symlink_owner(link_dentry->d_inode, target.dentry->d_inode); path_put(&target); out: lve_put_link(nd, link_dentry, cookie); return error; } /* Hardlink protection */ int sandbox_inode_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) { if (!param_is_enabled(LVE_HARDLINK_PROTECTION)) return 0; return may_create_hard_link(old_dentry); } int sandbox_inode_readlink(struct dentry *link_dentry, struct vfsmount *link_mnt) { int error = 0; char *link_body; void *cookie; struct path target; bool handle_filter = false; bool handle_owner = false; struct nameidata nd; error = follow_link_init(&handle_owner, &handle_filter, NULL); if (error != 0) return error == -1 ? 0 : error; if (!handle_filter) return 0; memset(&nd, 0, sizeof(struct nameidata)); nd.path.dentry = link_dentry->d_parent; nd.path.mnt = link_mnt; link_body = get_link_body(link_dentry, &nd, &cookie); if (IS_ERR_OR_NULL(link_body)) return 0; error = lve_symlink_lookup(&nd.path, link_body, &target); if (error != 0) goto out; /* if the current matches the filter */ if (handle_filter && !is_link_body_secure(link_body)) error = handle_symlink_filter(link_dentry->d_inode, target.dentry->d_inode); path_put(&target); out: lve_put_link(&nd, link_dentry, cookie); return error; } /* Copy-paste lookup_slow() w/o parent i_mutex locking */ static void lve_path_put_conditional(struct path *path, struct nameidata *nd) { dput(path->dentry); if (path->mnt != nd->path.mnt) mntput(path->mnt); } int lve_lookup_slow_locked(struct nameidata *nd, struct path *path) { struct dentry *dentry, *parent; int err; parent = nd->path.dentry; BUG_ON(nd->inode != parent->d_inode); dentry = lve__lookup_hash(&nd->last, parent, nd->flags); if (IS_ERR(dentry)) return PTR_ERR(dentry); path->mnt = nd->path.mnt; path->dentry = dentry; err = lve_follow_managed(path, nd->flags); if (unlikely(err < 0)) { lve_path_put_conditional(path, nd); return err; } if (err) nd_set_jumped(nd); return 0; } #endif