#ifndef _LINK_PROTECT_H_ #define _LINK_PROTECT_H_ /* * We should define reasonable limit for maximum symlink count * in a single path, as kernel's limit of 40 is too much in terms * of stack foortprint (can cause kernel stack overflow). * * 8 - for "nested", 8 - for "trailing" */ #define MAX_LINK_COUNT 16 static inline int check_link_group_permission(const struct inode *inode, bool symlink) { int error = -EACCES; gid_t target_gid; uint64_t allow_gid; target_gid = i_gid_read(inode); if (lve_get_param(symlink ? LVE_SYMLINK_PROT_ALLOW_GID : LVE_HARDLINK_PROT_ALLOW_GID, &allow_gid) < 0) return -EINVAL; if (allow_gid != 0UL && target_gid == (gid_t) allow_gid) error = 0; return error; } static inline int check_user_link_permission(bool is_symlink) { uint64_t allow_gid; /* Root has no restrictions */ if (uid_eq(current_fsuid(), GLOBAL_ROOT_UID) == true) return 0; if (lve_get_param(is_symlink ? LVE_SYMLINK_PROT_ALLOW_GID : LVE_HARDLINK_PROT_ALLOW_GID, &allow_gid) < 0) return -EINVAL; /* Special user has no restrictions too */ if (allow_gid != 0UL) { kgid_t gid = KGIDT_INIT((unsigned int) allow_gid); if (in_group_p(gid)) return 0; } return -EPERM; } #define is_global_nonroot(x) (!uid_eq((x), GLOBAL_ROOT_UID)) static inline int handle_owner_gid(void) { uint64_t symlinkown_gid; kgid_t symlinkown_kgid; if (lve_get_param(LVE_SYMLINK_OWNER_GID, &symlinkown_gid) < 0) return -EINVAL; /* Protect ourselves from accident incorrect setting */ if (symlinkown_gid == 0UL) return -ERANGE; symlinkown_kgid = make_kgid(current_user_ns(), (unsigned int) symlinkown_gid); if (!in_group_p(symlinkown_kgid)) return -ERANGE; return 0; } extern char *symlink_task_filter; static inline bool handle_task_filter(void) { struct task_struct *parent = current->real_parent; if (!param_is_enabled(LVE_HANDLE_SYMLINK_BY_TASK)) return false; while (parent != &init_task) { if (!strncmp(parent->comm, symlink_task_filter, strlen(symlink_task_filter))) return true; parent = parent->real_parent; } return false; } static inline int handle_symlink_owner(const struct inode *link_inode, const struct inode *target_inode) { bool nonroot; if (link_inode == NULL || target_inode == NULL) return 0; nonroot = param_is_enabled(LVE_GLOBAL_NONROOT) ? true : is_global_nonroot(link_inode->i_uid); /* ignore root-owned links, e.g. /proc/self */ if (uid_valid(link_inode->i_uid) && nonroot && !uid_eq(link_inode->i_uid, target_inode->i_uid)) { goto eacces; } return 0; eacces: if (printk_ratelimit()) pr_info("access denied uid %u target uid %u\n", from_kuid(current_user_ns(), link_inode->i_uid), from_kuid(current_user_ns(), target_inode->i_uid)); ; return -EACCES; } static inline int handle_symlink_filter(struct inode *link, struct inode* target) { /* Root has no restrictions */ if (uid_eq(current_fsuid(), GLOBAL_ROOT_UID)) return 0; if (uid_eq(link->i_uid, GLOBAL_ROOT_UID)) return 0; if (!uid_eq(current_fsuid(), target->i_uid)) return -EACCES; return 0; } static inline int follow_link_init(bool *handle_owner, bool *handle_filter, bool *handle_proc) { int ret; uint64_t p_owner; bool p_by_task = param_is_enabled(LVE_HANDLE_SYMLINK_BY_TASK); bool p_by_proc = param_is_enabled(LVE_HANDLE_SYMLINK_PROC); /* * LVE_HANDLE_SYMLINK_OWNER have different values: * 0 - turned off * 1 - restrict if in fs.symlinkown_gid group * 2 - restrict if in lve or in fs.symlinkown_gid group */ if (lve_get_param(LVE_HANDLE_SYMLINK_OWNER, &p_owner) < 0) { pr_err("can't read param %u\n", LVE_HANDLE_SYMLINK_OWNER); p_owner = 0; } if (!p_owner && !p_by_task && !p_by_proc) return -1; if (handle_proc) *handle_proc = p_by_proc; ret = handle_owner_gid(); if (ret == -EINVAL) return ret; if ((is_in_lve(current) && p_owner >= 2) || (p_owner && ret == 0)) *handle_owner = true; if (p_by_task) *handle_filter = handle_task_filter(); /* if both enabled - owner check is in priority */ if (*handle_owner && *handle_filter) *handle_filter = false; return 0; } static inline int may_create_hard_link(struct dentry *old_dentry) { int error = 0; struct inode *inode; if (check_user_link_permission(false) && old_dentry && (inode = old_dentry->d_inode) && !uid_eq(current_fsuid(), inode->i_uid)) error = check_link_group_permission(inode, false); return error; } #if defined(IMPL_LINK_PROT_EXPERIMENTAL) || defined(IMPL_LINK_PROT_OLD) /* * Replicate a few first fields of the "struct nameidata" */ #ifdef HAVE_LOOKUP_FLAGS struct __nameidata { struct path path; struct qstr last; struct path root; struct inode *inode; /* path.dentry.d_inode */ unsigned int flags; }; static inline bool nd_get_jumped(struct nameidata *nd) { return (((struct __nameidata *)nd)->flags & LOOKUP_JUMPED) != 0; } static inline void nd_set_jumped(struct nameidata *nd) { ((struct __nameidata *)nd)->flags |= LOOKUP_JUMPED; } #else /* HAVE_LOOKUP_FLAGS */ struct __nameidata { struct path path; struct qstr last; struct path root; struct inode *inode; /* path.dentry.d_inode */ unsigned int flags, state; }; #define ND_JUMPED 4 static inline bool nd_get_jumped(struct nameidata *nd) { return (((struct __nameidata *)nd)->state & ND_JUMPED) != 0; } static inline void nd_set_jumped(struct nameidata *nd) { ((struct __nameidata *)nd)->state |= ND_JUMPED; } #endif /* HAVE_LOOKUP_FLAGS */ #endif /* defined(IMPL_LINK_PROT_EXPERIMENTAL) || defined(IMPL_LINK_PROT_OLD) */ #ifdef IMPL_LINK_PROT_OLD static inline int lve_do_path_lookup(int dfd, const char *name, unsigned int flags, struct nameidata *nd) { struct filename *filename = lve_getname_kernel(name); int retval = PTR_ERR(filename); if (!IS_ERR(filename)) { retval = lve_filename_lookup(dfd, filename, flags, nd); lve_putname(filename); } return retval; } #elif defined(IMPL_LINK_PROT_EXPERIMENTAL) static inline struct path* nd_get_path(struct nameidata *nd) { return &((struct __nameidata *)nd)->path; } static inline struct inode* nd_get_inode(struct nameidata *nd) { return ((struct __nameidata *)nd)->inode; } extern kuid_t get_current_link_uid(void); extern int set_current_link_uid(kuid_t uid); extern void reset_current_link_uid(void); extern int lve_register_wc_hook_current(void); extern void lve_unregister_wc_hook_current(void); #endif #if defined(IMPL_LINK_PROT_NEW) || defined(IMPL_LINK_PROT_EXPERIMENTAL) static inline int lve_do_path_lookup(int dfd, const char *name, unsigned int flags, struct path *target_path) { int ret; void *saved_audit_ctx; #ifdef KERNEL_DS mm_segment_t oldfs; oldfs = get_fs(); set_fs(KERNEL_DS); #endif saved_audit_ctx = audit_context(); audit_set_context(current, NULL); #if RHEL_MAJOR<9 ret = user_path_at_empty(dfd, name, flags, target_path, NULL); #else ret = lve_filename_lookup(dfd, lve_getname_kernel(name), flags, target_path, NULL); #endif audit_set_context(current, saved_audit_ctx); #ifdef KERNEL_DS set_fs(oldfs); #endif return ret; } #endif /* defined(IMPL_LINK_PROT_NEW) || defined(IMPL_LINK_PROT_EXPERIMENTAL) */ extern int may_create_sym_link(const char *name, const struct path *path, struct dentry *link); /* * We're looking for "../" or "/../" patterns */ static inline bool is_link_body_secure(const char *link_body) { size_t len = strnlen(link_body, PATH_MAX); if (len < 3) return true; if (!strncmp(link_body, "../", sizeof("../") - 1)) return false; if (strnstr(link_body, "/../", len)) return false; return true; } int lve_symlink_lookup(const struct path *base, const char *name, struct path *target); #endif /* _LINK_PROTECT_H_ */