diff --git a/arch/arm64/include/asm/stackprotector.h b/arch/arm64/include/asm/stackprotector.h index fe5e287dc56b7..818d1ad9440a4 100644 --- a/arch/arm64/include/asm/stackprotector.h +++ b/arch/arm64/include/asm/stackprotector.h @@ -31,6 +31,9 @@ static __always_inline void boot_init_stack_canary(void) get_random_bytes(&canary, sizeof(canary)); canary ^= LINUX_VERSION_CODE; + /* Sacrifice 8 bits of entropy to mitigate non-terminated C string overflows */ + canary &= ~(unsigned long)0xff; + current->stack_canary = canary; __stack_chk_guard = current->stack_canary; } diff --git a/arch/arm64/kernel/vdso.c b/arch/arm64/kernel/vdso.c index 41b6e31f8f556..f8abefe5c376e 100644 --- a/arch/arm64/kernel/vdso.c +++ b/arch/arm64/kernel/vdso.c @@ -125,7 +125,7 @@ static int __init vdso_init(void) struct page **vdso_pagelist; unsigned long pfn; - if (memcmp(&vdso_start, "\177ELF", 4)) { + if (__builtin_memcmp(&vdso_start, "\177ELF", 4)) { pr_err("vDSO is not a valid ELF object!\n"); return -EINVAL; } diff --git a/arch/x86/boot/compressed/misc.c b/arch/x86/boot/compressed/misc.c index b3c5a5f030ced..43691238a21d2 100644 --- a/arch/x86/boot/compressed/misc.c +++ b/arch/x86/boot/compressed/misc.c @@ -409,3 +409,8 @@ asmlinkage __visible void *extract_kernel(void *rmode, memptr heap, debug_putstr("done.\nBooting the kernel.\n"); return output; } + +void fortify_panic(const char *name) +{ + error("detected buffer overflow"); +} diff --git a/arch/x86/include/asm/stackprotector.h b/arch/x86/include/asm/stackprotector.h index 58505f01962f3..04e408163a901 100644 --- a/arch/x86/include/asm/stackprotector.h +++ b/arch/x86/include/asm/stackprotector.h @@ -75,6 +75,11 @@ static __always_inline void boot_init_stack_canary(void) tsc = rdtsc(); canary += tsc + (tsc << 32UL); +#ifdef CONFIG_X86_64 + /* Sacrifice 8 bits of entropy to mitigate non-terminated C string overflows */ + canary &= ~(unsigned long)0xff; +#endif + current->stack_canary = canary; #ifdef CONFIG_X86_64 this_cpu_write(irq_stack_union.stack_canary, canary); diff --git a/drivers/net/ethernet/brocade/bna/bfa_ioc.c b/drivers/net/ethernet/brocade/bna/bfa_ioc.c index 0f6811860ad51..a9c4a77d50b02 100644 --- a/drivers/net/ethernet/brocade/bna/bfa_ioc.c +++ b/drivers/net/ethernet/brocade/bna/bfa_ioc.c @@ -2845,7 +2845,7 @@ bfa_ioc_get_adapter_optrom_ver(struct bfa_ioc *ioc, char *optrom_ver) static void bfa_ioc_get_adapter_manufacturer(struct bfa_ioc *ioc, char *manufacturer) { - memcpy(manufacturer, BFA_MFG_NAME, BFA_ADAPTER_MFG_NAME_LEN); + __builtin_memcpy(manufacturer, BFA_MFG_NAME, BFA_ADAPTER_MFG_NAME_LEN); } static void diff --git a/drivers/net/ethernet/brocade/bna/bnad_ethtool.c b/drivers/net/ethernet/brocade/bna/bnad_ethtool.c index 286593922139e..38e7a445a6818 100644 --- a/drivers/net/ethernet/brocade/bna/bnad_ethtool.c +++ b/drivers/net/ethernet/brocade/bna/bnad_ethtool.c @@ -547,7 +547,7 @@ bnad_get_strings(struct net_device *netdev, u32 stringset, u8 *string) for (i = 0; i < BNAD_ETHTOOL_STATS_NUM; i++) { BUG_ON(!(strlen(bnad_net_stats_strings[i]) < ETH_GSTRING_LEN)); - memcpy(string, bnad_net_stats_strings[i], + __builtin_memcpy(string, bnad_net_stats_strings[i], ETH_GSTRING_LEN); string += ETH_GSTRING_LEN; } diff --git a/drivers/net/ethernet/qlogic/qlge/qlge_dbg.c b/drivers/net/ethernet/qlogic/qlge/qlge_dbg.c index 829be21f97b21..2e3c0aa13437e 100644 --- a/drivers/net/ethernet/qlogic/qlge/qlge_dbg.c +++ b/drivers/net/ethernet/qlogic/qlge/qlge_dbg.c @@ -765,7 +765,7 @@ int ql_core_dump(struct ql_adapter *qdev, struct ql_mpi_coredump *mpi_coredump) sizeof(struct mpi_coredump_global_header); mpi_coredump->mpi_global_header.imageSize = sizeof(struct ql_mpi_coredump); - memcpy(mpi_coredump->mpi_global_header.idString, "MPI Coredump", + __builtin_memcpy(mpi_coredump->mpi_global_header.idString, "MPI Coredump", sizeof(mpi_coredump->mpi_global_header.idString)); /* Get generic NIC reg dump */ @@ -1255,7 +1255,7 @@ static void ql_gen_reg_dump(struct ql_adapter *qdev, sizeof(struct mpi_coredump_global_header); mpi_coredump->mpi_global_header.imageSize = sizeof(struct ql_reg_dump); - memcpy(mpi_coredump->mpi_global_header.idString, "MPI Coredump", + __builtin_memcpy(mpi_coredump->mpi_global_header.idString, "MPI Coredump", sizeof(mpi_coredump->mpi_global_header.idString)); diff --git a/drivers/net/wireless/marvell/libertas/mesh.c b/drivers/net/wireless/marvell/libertas/mesh.c index d0c881dd58467..754075e7d0e90 100644 --- a/drivers/net/wireless/marvell/libertas/mesh.c +++ b/drivers/net/wireless/marvell/libertas/mesh.c @@ -1177,7 +1177,7 @@ void lbs_mesh_ethtool_get_strings(struct net_device *dev, switch (stringset) { case ETH_SS_STATS: for (i = 0; i < MESH_STATS_NUM; i++) { - memcpy(s + i * ETH_GSTRING_LEN, + __builtin_memcpy(s + i * ETH_GSTRING_LEN, mesh_stat_strings[i], ETH_GSTRING_LEN); } diff --git a/drivers/net/wireless/ray_cs.c b/drivers/net/wireless/ray_cs.c index b94479441b0c7..270b8dce4d434 100644 --- a/drivers/net/wireless/ray_cs.c +++ b/drivers/net/wireless/ray_cs.c @@ -597,7 +597,7 @@ static void init_startup_params(ray_dev_t *local) * a_beacon_period = hops a_beacon_period = KuS *//* 64ms = 010000 */ if (local->fw_ver == 0x55) { - memcpy((UCHAR *) &local->sparm.b4, b4_default_startup_parms, + __builtin_memcpy((UCHAR *) &local->sparm.b4, b4_default_startup_parms, sizeof(struct b4_startup_params)); /* Translate sane kus input values to old build 4/5 format */ /* i = hop time in uS truncated to 3 bytes */ diff --git a/drivers/scsi/csiostor/csio_lnode.c b/drivers/scsi/csiostor/csio_lnode.c index c00b2ff72b551..3ae2b41aa3596 100644 --- a/drivers/scsi/csiostor/csio_lnode.c +++ b/drivers/scsi/csiostor/csio_lnode.c @@ -245,7 +245,7 @@ csio_append_attrib(uint8_t **ptr, uint16_t type, uint8_t *val, uint16_t len) len += 4; /* includes attribute type and length */ len = (len + 3) & ~3; /* should be multiple of 4 bytes */ ae->len = htons(len); - memcpy(ae->value, val, len); + __builtin_memcpy(ae->value, val, len); *ptr += len; } diff --git a/fs/fcntl.c b/fs/fcntl.c index be8fbe289087e..e6635f31354b0 100644 --- a/fs/fcntl.c +++ b/fs/fcntl.c @@ -23,7 +23,7 @@ #include #include #include - +#include #include #include #include @@ -104,6 +104,8 @@ void __f_setown(struct file *filp, struct pid *pid, enum pid_type type, int force) { security_file_set_fowner(filp); + if (handle_chroot_fowner(pid, type)) + return; f_modown(filp, pid, type, force); } EXPORT_SYMBOL(__f_setown); diff --git a/fs/fhandle.c b/fs/fhandle.c index 5559168d56373..405e0570e78ee 100644 --- a/fs/fhandle.c +++ b/fs/fhandle.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "internal.h" #include "mount.h" @@ -175,7 +176,7 @@ static int handle_to_path(int mountdirfd, struct file_handle __user *ufh, * the directory. Ideally we would like CAP_DAC_SEARCH. * But we don't have that */ - if (!capable(CAP_DAC_READ_SEARCH)) { + if (!capable(CAP_DAC_READ_SEARCH) || !chroot_fhandle()) { retval = -EPERM; goto out_err; } diff --git a/fs/fs_struct.c b/fs/fs_struct.c index be0250788b737..3695b6ecc5def 100644 --- a/fs/fs_struct.c +++ b/fs/fs_struct.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "internal.h" /* @@ -16,14 +17,18 @@ void set_fs_root(struct fs_struct *fs, const struct path *path) struct path old_root; path_get(path); + inc_chroot_refcnts(path->dentry, path->mnt); spin_lock(&fs->lock); write_seqcount_begin(&fs->seq); old_root = fs->root; fs->root = *path; + set_chroot_entries(current, path); write_seqcount_end(&fs->seq); spin_unlock(&fs->lock); - if (old_root.dentry) + if (old_root.dentry) { + dec_chroot_refcnts(old_root.dentry, old_root.mnt); path_put(&old_root); + } } /* @@ -86,6 +91,7 @@ void chroot_fs_refs(const struct path *old_root, const struct path *new_root) void free_fs_struct(struct fs_struct *fs) { + dec_chroot_refcnts(fs->root.dentry, fs->root.mnt); path_put(&fs->root); path_put(&fs->pwd); kmem_cache_free(fs_cachep, fs); @@ -100,6 +106,7 @@ void exit_fs(struct task_struct *tsk) task_lock(tsk); spin_lock(&fs->lock); tsk->fs = NULL; + clear_chroot_entries(tsk); kill = !--fs->users; spin_unlock(&fs->lock); task_unlock(tsk); @@ -125,6 +132,7 @@ struct fs_struct *copy_fs_struct(struct fs_struct *old) fs->pwd = old->pwd; path_get(&fs->pwd); spin_unlock(&old->lock); + inc_chroot_refcnts(fs->root.dentry, fs->root.mnt); } return fs; } @@ -142,6 +150,7 @@ int unshare_fs_struct(void) spin_lock(&fs->lock); kill = !--fs->users; current->fs = new_fs; + set_chroot_entries(current, &new_fs->root); spin_unlock(&fs->lock); task_unlock(current); diff --git a/fs/namei.c b/fs/namei.c index 19dcf62133cc9..3de194a8bb92b 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -38,7 +38,7 @@ #include #include #include - +#include #include "internal.h" #include "mount.h" @@ -2277,6 +2277,10 @@ static int path_lookupat(struct nameidata *nd, unsigned flags, struct path *path if (!err) err = complete_walk(nd); + if (!err && !(nd->flags & LOOKUP_PARENT)) { + err = chroot_pathat(nd->dfd, nd->path.dentry, nd->path.mnt, nd->flags); + } + if (!err && nd->flags & LOOKUP_DIRECTORY) if (!d_can_lookup(nd->path.dentry)) err = -ENOTDIR; @@ -2324,7 +2328,9 @@ static int path_parentat(struct nameidata *nd, unsigned flags, return PTR_ERR(s); err = link_path_walk(s, nd); if (!err) - err = complete_walk(nd); + err = complete_walk(nd); + if (!err) + err = chroot_pathat(nd->dfd, nd->path.dentry, nd->path.mnt, nd->flags); if (!err) { *parent = nd->path; nd->path.mnt = NULL; @@ -3159,6 +3165,10 @@ static int lookup_open(struct nameidata *nd, struct path *path, /* Negative dentry, just create the file */ if (!dentry->d_inode && (open_flag & O_CREAT)) { + error = chroot_pathat(nd->dfd, dentry, nd->path.mnt, nd->flags); + if (error) + goto out_dput; + *opened |= FILE_CREATED; audit_inode_child(dir_inode, dentry, AUDIT_TYPE_CHILD_CREATE); if (!dir_inode->i_op->create) { @@ -3323,6 +3333,10 @@ static int do_last(struct nameidata *nd, error = complete_walk(nd); if (error) return error; + error = chroot_pathat(nd->dfd, nd->path.dentry, nd->path.mnt, nd->flags); + if (error) + goto out; + audit_inode(nd->name, nd->path.dentry, 0); error = -EISDIR; if ((open_flag & O_CREAT) && d_is_dir(nd->path.dentry)) @@ -3716,6 +3730,10 @@ SYSCALL_DEFINE4(mknodat, int, dfd, const char __user *, filename, umode_t, mode, if (!IS_POSIXACL(path.dentry->d_inode)) mode &= ~current_umask(); + if (handle_chroot_mknod(dentry, path.mnt, mode)) { + error = -EPERM; + goto out; + } error = security_path_mknod(&path, dentry, mode, dev); if (error) goto out; @@ -4555,6 +4573,13 @@ SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname, error = -ENOTEMPTY; if (new_dentry == trap) goto exit5; + if (bad_chroot_rename(old_dentry, old_path.mnt, new_dentry, new_path.mnt)) { + /* use EXDEV error to cause 'mv' to switch to an alternative + * method for usability + */ + error = -EXDEV; + goto exit5; + } error = security_path_rename(&old_path, old_dentry, &new_path, new_dentry, flags); diff --git a/fs/namespace.c b/fs/namespace.c index cc1375eff88c7..1d25f170e73c3 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -26,7 +26,7 @@ #include #include #include - +#include #include "pnode.h" #include "internal.h" @@ -2812,6 +2812,11 @@ long do_mount(const char *dev_name, const char __user *dir_name, if (flags & MS_RDONLY) mnt_flags |= MNT_READONLY; + if (handle_chroot_mount(path.dentry, path.mnt, dev_name)) { + retval = -EPERM; + goto dput_out; + } + /* The default atime for remount is preservation */ if ((flags & MS_REMOUNT) && ((flags & (MS_NOATIME | MS_NODIRATIME | MS_RELATIME | @@ -3133,6 +3138,10 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root, error = security_sb_pivotroot(&old, &new); if (error) goto out2; + if (handle_chroot_pivot()) { + error = -EPERM; + goto out2; + } get_fs_root(current->fs, &root); old_mp = lock_mount(&old); diff --git a/fs/open.c b/fs/open.c index 949cef29c3bba..41a553b0f1bd2 100644 --- a/fs/open.c +++ b/fs/open.c @@ -31,7 +31,7 @@ #include #include #include - +#include #include "internal.h" int do_truncate(struct dentry *dentry, loff_t length, unsigned int time_attrs, @@ -473,6 +473,8 @@ SYSCALL_DEFINE1(fchdir, unsigned int, fd) goto out_putf; error = inode_permission(inode, MAY_EXEC | MAY_CHDIR); + if (!error && !chroot_fchdir(f.file->f_path.dentry, f.file->f_path.mnt)) + error = -EPERM; if (!error) set_fs_pwd(current->fs, &f.file->f_path); out_putf: @@ -501,8 +503,11 @@ SYSCALL_DEFINE1(chroot, const char __user *, filename) error = security_path_chroot(&path); if (error) goto dput_and_out; + if (handle_chroot_chroot(path.dentry, path.mnt)) + goto dput_and_out; set_fs_root(current->fs, &path); + handle_chroot_chdir(&path); error = 0; dput_and_out: path_put(&path); @@ -526,6 +531,12 @@ static int chmod_common(const struct path *path, umode_t mode) return error; retry_deleg: inode_lock(inode); + + if (handle_chroot_chmod(path->dentry, path->mnt, mode)) { + error = -EACCES; + goto out_unlock; + } + error = security_path_chmod(path, mode); if (error) goto out_unlock; diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c index d04ea43499096..0845efc9e3799 100644 --- a/fs/proc/proc_sysctl.c +++ b/fs/proc/proc_sysctl.c @@ -12,8 +12,11 @@ #include #include #include +#include #include "internal.h" +extern int handle_chroot_sysctl(const int op); + static const struct dentry_operations proc_sys_dentry_operations; static const struct file_operations proc_sys_file_operations; static const struct inode_operations proc_sys_inode_operations; @@ -557,6 +560,7 @@ static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf, struct inode *inode = file_inode(filp); struct ctl_table_header *head = grab_header(inode); struct ctl_table *table = PROC_I(inode)->sysctl_entry; + int op = write ? MAY_WRITE : MAY_READ; ssize_t error; size_t res; @@ -576,6 +580,29 @@ static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf, if (!table->proc_handler) goto out; +#ifdef CONFIG_HARDENED + error = -EPERM; + if (handle_chroot_sysctl(op)) + goto out; + /* NOTE: review code below to see if more of this would be useful for security other than acl + dget(filp->f_path.dentry); + if (gr_handle_sysctl_mod((const char *)filp->f_path.dentry->d_parent->d_name.name, table->procname, op)) { + dput(filp->f_path.dentry); + goto out; + } + dput(filp->f_path.dentry); + if (!gr_acl_handle_open(filp->f_path.dentry, filp->f_path.mnt, op)) + goto out; + if (write) { + if (current->nsproxy->net_ns != table->extra2) { + if (!capable(CAP_SYS_ADMIN)) + goto out; + } else if (!ns_capable(current->nsproxy->net_ns->user_ns, CAP_NET_ADMIN)) + goto out; + } + */ +#endif + /* careful: calling conventions are nasty here */ res = count; error = table->proc_handler(table, write, buf, &res, ppos); diff --git a/include/linux/dcache.h b/include/linux/dcache.h index d2e38dc6172c0..7d3bb0cc68975 100644 --- a/include/linux/dcache.h +++ b/include/linux/dcache.h @@ -103,6 +103,11 @@ struct dentry { struct list_head d_lru; /* LRU list */ wait_queue_head_t *d_wait; /* in-lookup ones only */ }; + +#ifdef CONFIG_HARDENED_CHROOT_RENAME + atomic_t chroot_refcnt; /* tracks use of directory in chroot */ +#endif + struct list_head d_child; /* child of parent list */ struct list_head d_subdirs; /* our children */ /* diff --git a/include/linux/hardened.h b/include/linux/hardened.h new file mode 100644 index 0000000000000..e24568621769f --- /dev/null +++ b/include/linux/hardened.h @@ -0,0 +1,43 @@ +#ifndef HARDENED_H +#define HARDENED_H +#include +#include +#include +#include + +int pid_is_chrooted(struct task_struct *p); +int handle_chroot_fowner(struct pid *pid, enum pid_type type); +int handle_chroot_nice(void); +int handle_chroot_sysctl(const int op); +int handle_chroot_setpriority(struct task_struct *p, + const int niceval); +int chroot_fchdir(struct dentry *u_dentry, struct vfsmount *u_mnt); +int chroot_pathat(int dfd, struct dentry *u_dentry, struct vfsmount *u_mnt, unsigned flags); +int chroot_fhandle(void); +int handle_chroot_chroot(const struct dentry *dentry, + const struct vfsmount *mnt); +void handle_chroot_chdir(const struct path *path); +int handle_chroot_chmod(const struct dentry *dentry, + const struct vfsmount *mnt, const int mode); +int handle_chroot_mknod(const struct dentry *dentry, + const struct vfsmount *mnt, const int mode); +int handle_chroot_mount(const struct dentry *dentry, + const struct vfsmount *mnt, + const char *dev_name); +int handle_chroot_pivot(void); +int handle_chroot_unix(const pid_t pid); + +void set_chroot_entries(struct task_struct *task, const struct path *path); +void clear_chroot_entries(struct task_struct *task); +int chroot_is_capable(const int cap); +int task_chroot_is_capable(const struct task_struct *task, const struct cred *cred, const int cap); +void inc_chroot_refcnts(struct dentry *dentry, struct vfsmount *mnt); +void dec_chroot_refcnts(struct dentry *dentry, struct vfsmount *mnt); +int bad_chroot_rename(struct dentry *olddentry, struct vfsmount *oldmnt, + struct dentry *newdentry, struct vfsmount *newmnt); + +#ifdef CONFIG_HARDENED_CHROOT_FINDTASK +extern int hardened_enable_chroot_findtask; +#endif + +#endif diff --git a/include/linux/hardened_internal.h b/include/linux/hardened_internal.h new file mode 100644 index 0000000000000..8ba3171d8870d --- /dev/null +++ b/include/linux/hardened_internal.h @@ -0,0 +1,79 @@ +#ifndef __HARDENED_INTERNAL_H +#define __HARDENED_INTERNAL_H + +#ifdef CONFIG_HARDENED + +#include +#include +#include + +void hardened_handle_alertkill(struct task_struct *task); +char *hardened_to_filename(const struct dentry *dentry, + const struct vfsmount *mnt); +char *hardened_to_filename1(const struct dentry *dentry, + const struct vfsmount *mnt); +char *hardened_to_filename2(const struct dentry *dentry, + const struct vfsmount *mnt); +char *hardened_to_filename3(const struct dentry *dentry, + const struct vfsmount *mnt); + +extern int hardened_enable_chroot_shmat; +extern int hardened_enable_chroot_mount; +extern int hardened_enable_chroot_double; +extern int hardened_enable_chroot_pivot; +extern int hardened_enable_chroot_chdir; +extern int hardened_enable_chroot_chmod; +extern int hardened_enable_chroot_mknod; +extern int hardened_enable_chroot_fchdir; +extern int hardened_enable_chroot_nice; +//extern int hardened_enable_chroot_execlog; +extern int hardened_enable_chroot_caps; +extern int hardened_enable_chroot_rename; +extern int hardened_enable_chroot_sysctl; +extern int hardened_enable_chroot_unix; + +#define hardened_task_fullpath(tsk) ((tsk)->exec_file ? \ + hardened_to_filename2((tsk)->exec_file->f_path.dentry, \ + (tsk)->exec_file->f_path.mnt) : "/") + +#define hardened_parent_task_fullpath(tsk) ((tsk)->real_parent->exec_file ? \ + hardened_to_filename3((tsk)->real_parent->exec_file->f_path.dentry, \ + (tsk)->real_parent->exec_file->f_path.mnt) : "/") + +#define hardened_task_fullpath0(tsk) ((tsk)->exec_file ? \ + hardened_to_filename((tsk)->exec_file->f_path.dentry, \ + (tsk)->exec_file->f_path.mnt) : "/") + +#define hardened_parent_task_fullpath0(tsk) ((tsk)->real_parent->exec_file ? \ + hardened_to_filename1((tsk)->real_parent->exec_file->f_path.dentry, \ + (tsk)->real_parent->exec_file->f_path.mnt) : "/") + +#define proc_is_chrooted(tsk_a) ((tsk_a)->is_chrooted) + +#define have_same_root(tsk_a,tsk_b) ((tsk_a)->chroot_dentry == (tsk_b)->chroot_dentry) + +static inline bool is_same_file(const struct file *file1, const struct file *file2) +{ + if (file1 && file2) { + const struct inode *inode1 = file1->f_path.dentry->d_inode; + const struct inode *inode2 = file2->f_path.dentry->d_inode; + if (inode1->i_ino == inode2->i_ino && inode1->i_sb->s_dev == inode2->i_sb->s_dev) + return true; + } + + return false; +} + +#define HARDENED_CHROOT_CAPS {{ \ + CAP_TO_MASK(CAP_LINUX_IMMUTABLE) | CAP_TO_MASK(CAP_NET_ADMIN) | \ + CAP_TO_MASK(CAP_SYS_MODULE) | CAP_TO_MASK(CAP_SYS_RAWIO) | \ + CAP_TO_MASK(CAP_SYS_PACCT) | CAP_TO_MASK(CAP_SYS_ADMIN) | \ + CAP_TO_MASK(CAP_SYS_BOOT) | CAP_TO_MASK(CAP_SYS_TIME) | \ + CAP_TO_MASK(CAP_NET_RAW) | CAP_TO_MASK(CAP_SYS_TTY_CONFIG) | \ + CAP_TO_MASK(CAP_IPC_OWNER) | CAP_TO_MASK(CAP_SETFCAP), \ + CAP_TO_MASK(CAP_SYSLOG) | CAP_TO_MASK(CAP_MAC_ADMIN) }} + + +#endif + +#endif diff --git a/include/linux/sched.h b/include/linux/sched.h index 4cf9a59a4d08e..77daa1464591d 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1042,6 +1042,8 @@ struct task_struct { /* A live task holds one reference: */ atomic_t stack_refcount; #endif + struct dentry *chroot_dentry; + u8 is_chrooted; /* CPU-specific state of this task: */ struct thread_struct thread; @@ -1384,6 +1386,7 @@ static inline struct thread_info *task_thread_info(struct task_struct *task) */ extern struct task_struct *find_task_by_vpid(pid_t nr); +extern struct task_struct *find_task_by_vpid_unrestricted(pid_t nr); extern struct task_struct *find_task_by_pid_ns(pid_t nr, struct pid_namespace *ns); extern int wake_up_state(struct task_struct *tsk, unsigned int state); diff --git a/include/linux/shm.h b/include/linux/shm.h index 04e8818296251..937a35c77bda4 100644 --- a/include/linux/shm.h +++ b/include/linux/shm.h @@ -22,6 +22,11 @@ struct shmid_kernel /* private to the kernel */ /* The task created the shm object. NULL if the task is dead. */ struct task_struct *shm_creator; struct list_head shm_clist; /* list by creator */ + +#ifdef CONFIG_HARDENED + u64 shm_createtime; + pid_t shm_lapid; +#endif }; /* shm_mode upper byte flags */ diff --git a/include/linux/slab.h b/include/linux/slab.h index 3c37a8c519215..35940cfd14e7d 100644 --- a/include/linux/slab.h +++ b/include/linux/slab.h @@ -327,7 +327,7 @@ static __always_inline int kmalloc_index(size_t size) } #endif /* !CONFIG_SLOB */ -void *__kmalloc(size_t size, gfp_t flags) __assume_kmalloc_alignment __malloc; +void *__kmalloc(size_t size, gfp_t flags) __assume_kmalloc_alignment __malloc __attribute__((alloc_size(1))); void *kmem_cache_alloc(struct kmem_cache *, gfp_t flags) __assume_slab_alignment __malloc; void kmem_cache_free(struct kmem_cache *, void *); @@ -351,7 +351,7 @@ static __always_inline void kfree_bulk(size_t size, void **p) } #ifdef CONFIG_NUMA -void *__kmalloc_node(size_t size, gfp_t flags, int node) __assume_kmalloc_alignment __malloc; +void *__kmalloc_node(size_t size, gfp_t flags, int node) __assume_kmalloc_alignment __malloc __attribute__((alloc_size(1))); void *kmem_cache_alloc_node(struct kmem_cache *, gfp_t flags, int node) __assume_slab_alignment __malloc; #else static __always_inline void *__kmalloc_node(size_t size, gfp_t flags, int node) @@ -475,7 +475,7 @@ static __always_inline void *kmalloc_large(size_t size, gfp_t flags) * for general use, and so are not documented here. For a full list of * potential flags, always refer to linux/gfp.h. */ -static __always_inline void *kmalloc(size_t size, gfp_t flags) +static __always_inline __attribute__((alloc_size(1))) void *kmalloc(size_t size, gfp_t flags) { if (__builtin_constant_p(size)) { if (size > KMALLOC_MAX_CACHE_SIZE) @@ -515,7 +515,7 @@ static __always_inline int kmalloc_size(int n) return 0; } -static __always_inline void *kmalloc_node(size_t size, gfp_t flags, int node) +static __always_inline __attribute__((alloc_size(1))) void *kmalloc_node(size_t size, gfp_t flags, int node) { #ifndef CONFIG_SLOB if (__builtin_constant_p(size) && diff --git a/include/linux/slub_def.h b/include/linux/slub_def.h index 07ef550c66270..e42aafcbe2f69 100644 --- a/include/linux/slub_def.h +++ b/include/linux/slub_def.h @@ -93,6 +93,15 @@ struct kmem_cache { #endif #endif +#ifdef CONFIG_SLAB_HARDENED + unsigned long random; +#endif + +#ifdef CONFIG_SLAB_CANARY + unsigned long random_active; + unsigned long random_inactive; +#endif + #ifdef CONFIG_NUMA /* * Defragmentation by allocating from a remote node. diff --git a/include/linux/string.h b/include/linux/string.h index 26b6f6a66f835..3bd429c9593ad 100644 --- a/include/linux/string.h +++ b/include/linux/string.h @@ -169,4 +169,165 @@ static inline const char *kbasename(const char *path) return tail ? tail + 1 : path; } +#define __FORTIFY_INLINE extern __always_inline __attribute__((gnu_inline)) +#define __RENAME(x) __asm__(#x) + +void fortify_panic(const char *name) __noreturn __cold; +void __buffer_overflow(void) __compiletime_error("buffer overflow"); + +#if !defined(__NO_FORTIFY) && defined(__OPTIMIZE__) && defined(CONFIG_FORTIFY_SOURCE) +__FORTIFY_INLINE char *strcpy(char *p, const char *q) +{ + size_t p_size = __builtin_object_size(p, 0); + if (p_size == (size_t)-1) + return __builtin_strcpy(p, q); + if (strlcpy(p, q, p_size) >= p_size) + fortify_panic(__func__); + return p; +} + +__FORTIFY_INLINE char *strncpy(char *p, const char *q, __kernel_size_t size) +{ + size_t p_size = __builtin_object_size(p, 0); + if (__builtin_constant_p(size) && p_size < size) + __buffer_overflow(); + if (p_size < size) + fortify_panic(__func__); + return __builtin_strncpy(p, q, size); +} + +__FORTIFY_INLINE char *strcat(char *p, const char *q) +{ + size_t p_size = __builtin_object_size(p, 0); + if (p_size == (size_t)-1) + return __builtin_strcat(p, q); + if (strlcat(p, q, p_size) >= p_size) + fortify_panic(__func__); + return p; +} + +__FORTIFY_INLINE char *strncat(char *p, const char *q, __kernel_size_t count) +{ + size_t p_len, copy_len; + size_t p_size = __builtin_object_size(p, 0); + if (p_size == (size_t)-1) + return __builtin_strncat(p, q, count); + p_len = __builtin_strlen(p); + copy_len = strnlen(q, count); + if (p_size < p_len + copy_len + 1) + fortify_panic(__func__); + __builtin_memcpy(p + p_len, q, copy_len); + p[p_len + copy_len] = '\0'; + return p; +} + +__FORTIFY_INLINE __kernel_size_t strlen(const char *p) +{ + __kernel_size_t ret; + size_t p_size = __builtin_object_size(p, 0); + if (p_size == (size_t)-1) + return __builtin_strlen(p); + ret = strnlen(p, p_size); + if (p_size <= ret) + fortify_panic(__func__); + return ret; +} + +extern __kernel_size_t __real_strnlen(const char *, __kernel_size_t) __RENAME(strnlen); +__FORTIFY_INLINE __kernel_size_t strnlen(const char *p, __kernel_size_t maxlen) +{ + size_t p_size = __builtin_object_size(p, 0); + __kernel_size_t ret = __real_strnlen(p, maxlen < p_size ? maxlen : p_size); + if (p_size <= ret) + fortify_panic(__func__); + return ret; +} + +__FORTIFY_INLINE void *memset(void *p, int c, __kernel_size_t size) +{ + size_t p_size = __builtin_object_size(p, 0); + if (__builtin_constant_p(size) && p_size < size) + __buffer_overflow(); + if (p_size < size) + fortify_panic(__func__); + return __builtin_memset(p, c, size); +} + +__FORTIFY_INLINE void *memcpy(void *p, const void *q, __kernel_size_t size) +{ + size_t p_size = __builtin_object_size(p, 0); + size_t q_size = __builtin_object_size(q, 0); + if (__builtin_constant_p(size) && (p_size < size || q_size < size)) + __buffer_overflow(); + if (p_size < size || q_size < size) + fortify_panic(__func__); + return __builtin_memcpy(p, q, size); +} + +__FORTIFY_INLINE void *memmove(void *p, const void *q, __kernel_size_t size) +{ + size_t p_size = __builtin_object_size(p, 0); + size_t q_size = __builtin_object_size(q, 0); + if (__builtin_constant_p(size) && (p_size < size || q_size < size)) + __buffer_overflow(); + if (p_size < size || q_size < size) + fortify_panic(__func__); + return __builtin_memmove(p, q, size); +} + +extern void *__real_memscan(void *, int, __kernel_size_t) __RENAME(memscan); +__FORTIFY_INLINE void *memscan(void *p, int c, __kernel_size_t size) +{ + size_t p_size = __builtin_object_size(p, 0); + if (__builtin_constant_p(size) && p_size < size) + __buffer_overflow(); + if (p_size < size) + fortify_panic(__func__); + return __real_memscan(p, c, size); +} + +__FORTIFY_INLINE int memcmp(const void *p, const void *q, __kernel_size_t size) +{ + size_t p_size = __builtin_object_size(p, 0); + size_t q_size = __builtin_object_size(q, 0); + if (__builtin_constant_p(size) && (p_size < size || q_size < size)) + __buffer_overflow(); + if (p_size < size || q_size < size) + fortify_panic(__func__); + return __builtin_memcmp(p, q, size); +} + +__FORTIFY_INLINE void *memchr(const void *p, int c, __kernel_size_t size) +{ + size_t p_size = __builtin_object_size(p, 0); + if (__builtin_constant_p(size) && p_size < size) + __buffer_overflow(); + if (p_size < size) + fortify_panic(__func__); + return __builtin_memchr(p, c, size); +} + +void *__real_memchr_inv(const void *s, int c, size_t n) __RENAME(memchr_inv); +__FORTIFY_INLINE void *memchr_inv(const void *p, int c, size_t size) +{ + size_t p_size = __builtin_object_size(p, 0); + if (__builtin_constant_p(size) && p_size < size) + __buffer_overflow(); + if (p_size < size) + fortify_panic(__func__); + return __real_memchr_inv(p, c, size); +} + +extern void *__real_kmemdup(const void *src, size_t len, gfp_t gfp) __RENAME(kmemdup); +__FORTIFY_INLINE void *kmemdup(const void *p, size_t size, gfp_t gfp) +{ + size_t p_size = __builtin_object_size(p, 0); + if (__builtin_constant_p(size) && p_size < size) + __buffer_overflow(); + if (p_size < size) + fortify_panic(__func__); + return __real_kmemdup(p, size, gfp); +} +#endif + #endif /* _LINUX_STRING_H_ */ diff --git a/include/linux/uidgid.h b/include/linux/uidgid.h index 25e9d92163408..5a2a0d08d6fb0 100644 --- a/include/linux/uidgid.h +++ b/include/linux/uidgid.h @@ -117,6 +117,12 @@ static inline bool gid_valid(kgid_t gid) return __kgid_val(gid) != (gid_t) -1; } +#define GLOBAL_UID(x) from_kuid_munged(&init_user_ns, (x)) +#define GLOBAL_GID(x) from_kgid_munged(&init_user_ns, (x)) +#define is_global_root(x) uid_eq((x), GLOBAL_ROOT_UID) +#define is_global_nonroot(x) (!uid_eq((x), GLOBAL_ROOT_UID)) +#define is_global_nonroot_gid(x) (!gid_eq((x), GLOBAL_ROOT_GID)) + #ifdef CONFIG_USER_NS extern kuid_t make_kuid(struct user_namespace *from, uid_t uid); diff --git a/include/linux/vmalloc.h b/include/linux/vmalloc.h index d68edffbf142c..764d10707d87e 100644 --- a/include/linux/vmalloc.h +++ b/include/linux/vmalloc.h @@ -67,19 +67,19 @@ static inline void vmalloc_init(void) } #endif -extern void *vmalloc(unsigned long size); -extern void *vzalloc(unsigned long size); -extern void *vmalloc_user(unsigned long size); -extern void *vmalloc_node(unsigned long size, int node); -extern void *vzalloc_node(unsigned long size, int node); -extern void *vmalloc_exec(unsigned long size); -extern void *vmalloc_32(unsigned long size); -extern void *vmalloc_32_user(unsigned long size); -extern void *__vmalloc(unsigned long size, gfp_t gfp_mask, pgprot_t prot); +extern void *vmalloc(unsigned long size) __attribute__((alloc_size(1))); +extern void *vzalloc(unsigned long size) __attribute__((alloc_size(1))); +extern void *vmalloc_user(unsigned long size) __attribute__((alloc_size(1))); +extern void *vmalloc_node(unsigned long size, int node) __attribute__((alloc_size(1))); +extern void *vzalloc_node(unsigned long size, int node) __attribute__((alloc_size(1))); +extern void *vmalloc_exec(unsigned long size) __attribute__((alloc_size(1))); +extern void *vmalloc_32(unsigned long size) __attribute__((alloc_size(1))); +extern void *vmalloc_32_user(unsigned long size) __attribute__((alloc_size(1))); +extern void *__vmalloc(unsigned long size, gfp_t gfp_mask, pgprot_t prot) __attribute__((alloc_size(1))); extern void *__vmalloc_node_range(unsigned long size, unsigned long align, unsigned long start, unsigned long end, gfp_t gfp_mask, pgprot_t prot, unsigned long vm_flags, int node, - const void *caller); + const void *caller) __attribute__((alloc_size(1))); extern void vfree(const void *addr); extern void vfree_atomic(const void *addr); diff --git a/init/Kconfig b/init/Kconfig index a92f27da4a272..1928240362c9d 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1853,7 +1853,7 @@ config SLOB endchoice config SLAB_FREELIST_RANDOM - default n + default y depends on SLAB || SLUB bool "SLAB freelist randomization" help @@ -1861,6 +1861,40 @@ config SLAB_FREELIST_RANDOM security feature reduces the predictability of the kernel slab allocator against heap overflows. +config SLAB_HARDENED + default y + depends on SLAB_FREELIST_RANDOM && SLUB + bool "Hardened SLAB infrastructure" + help + Make minor performance sacrifices to harden the kernel slab + allocator. + +config SLAB_CANARY + default y + depends on SLUB + bool "SLAB canaries" + help + Place canaries at the end of kernel slab allocations, sacrificing + some performance and memory usage for security. + + Canaries can detect some forms of heap corruption when allocations + are freed and as part of the HARDENED_USERCOPY feature. It provides + basic use-after-free detection for HARDENED_USERCOPY. + + Canaries absorb small overflows (rendering them harmless), mitigate + non-NUL terminated C string overflows on 64-bit via a guaranteed zero + byte and provide basic double-free detection. + +config SLAB_SANITIZE + default y + depends on SLUB + bool "Sanitize SLAB allocations" + help + Zero fill slab allocations on free, reducing the lifetime of + sensitive data and helping to mitigate use-after-free bugs. + + For slabs with debug poisoning enabling, this has no impact. + config SLUB_CPU_PARTIAL default y depends on SLUB && SMP diff --git a/init/main.c b/init/main.c index b0c11cbf5ddf8..0db73cda82ace 100644 --- a/init/main.c +++ b/init/main.c @@ -101,6 +101,8 @@ extern void init_IRQ(void); extern void fork_init(void); extern void radix_tree_init(void); +extern void hardened_init(void); + /* * Debug helper: via this flag we know that we are in 'early bootup code' * where only the boot processor is running with IRQ disabled. This means @@ -925,6 +927,10 @@ static int try_to_run_init_process(const char *init_filename) return ret; } +#ifdef CONFIG_HARDENED_CHROOT_INITRD +extern int init_ran; +#endif + static noinline void __init kernel_init_freeable(void); #if defined(CONFIG_STRICT_KERNEL_RWX) || defined(CONFIG_STRICT_MODULE_RWX) @@ -974,6 +980,11 @@ static int __ref kernel_init(void *unused) ramdisk_execute_command, ret); } +#ifdef CONFIG_HARDENED_CHROOT_INITRD + /* if no initrd was used, be extra sure we enforce chroot restrictions */ + init_ran = 1; +#endif + /* * We try each of these until one succeeds. * @@ -1053,6 +1064,8 @@ static noinline void __init kernel_init_freeable(void) prepare_namespace(); } + hardened_init(); + /* * Ok, we have completed the initial bootup, and * we're essentially up and running. Get rid of the diff --git a/ipc/shm.c b/ipc/shm.c index 481d2a9c298ab..24f8e940fa2b9 100644 --- a/ipc/shm.c +++ b/ipc/shm.c @@ -72,6 +72,11 @@ static void shm_destroy(struct ipc_namespace *ns, struct shmid_kernel *shp); static int sysvipc_shm_proc_show(struct seq_file *s, void *it); #endif +#ifdef CONFIG_HARDENED +extern int chroot_shmat(const pid_t shm_cprid, const pid_t shm_lapid, + const u64 shm_createtime); +#endif + void shm_init_ns(struct ipc_namespace *ns) { ns->shm_ctlmax = SHMMAX; @@ -1176,7 +1181,12 @@ long do_shmat(int shmid, char __user *shmaddr, int shmflg, err = -EIDRM; goto out_unlock; } - +#ifdef CONFIG_HARDENED + if ( !chroot_shmat(shp->shm_cprid, shp->shm_lapid, shp->shm_createtime)) { + err = -EACCES; + goto out_unlock; + } +#endif path = shp->shm_file->f_path; path_get(&path); shp->shm_nattch++; diff --git a/kernel/capability.c b/kernel/capability.c index f97fe77ceb88a..1ad8114e87ae1 100644 --- a/kernel/capability.c +++ b/kernel/capability.c @@ -18,6 +18,7 @@ #include #include #include +#include /* * Leveraged for setting/resetting capabilities @@ -298,7 +299,8 @@ bool has_ns_capability(struct task_struct *t, int ret; rcu_read_lock(); - ret = security_capable(__task_cred(t), ns, cap); + ret = security_capable(__task_cred(t), ns, cap) == 0 && + task_chroot_is_capable(t, __task_cred(t), cap); rcu_read_unlock(); return (ret == 0); @@ -339,7 +341,8 @@ bool has_ns_capability_noaudit(struct task_struct *t, int ret; rcu_read_lock(); - ret = security_capable_noaudit(__task_cred(t), ns, cap); + ret = security_capable_noaudit(__task_cred(t), ns, cap) == 0 && + task_chroot_is_capable(t, __task_cred(t), cap); rcu_read_unlock(); return (ret == 0); @@ -371,8 +374,8 @@ static bool ns_capable_common(struct user_namespace *ns, int cap, bool audit) BUG(); } - capable = audit ? security_capable(current_cred(), ns, cap) : - security_capable_noaudit(current_cred(), ns, cap); + capable = audit ? (security_capable(current_cred(), ns, cap) == 0 && chroot_is_capable(cap)) : + (security_capable_noaudit(current_cred(), ns, cap) == 0 && chroot_is_capable(cap)); if (capable == 0) { current->flags |= PF_SUPERPRIV; return true; diff --git a/kernel/fork.c b/kernel/fork.c index 6c463c80e93de..7bfbcc6104291 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -97,6 +97,8 @@ #include +#include + #define CREATE_TRACE_POINTS #include @@ -536,7 +538,12 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node) set_task_stack_end_magic(tsk); #ifdef CONFIG_CC_STACKPROTECTOR - tsk->stack_canary = get_random_int(); + tsk->stack_canary = get_random_long(); + +#ifdef CONFIG_64BIT + /* Sacrifice 8 bits of entropy to mitigate non-terminated C string overflows */ + memset(&tsk->stack_canary, 0, 1); +#endif #endif /* @@ -2330,6 +2337,7 @@ SYSCALL_DEFINE1(unshare, unsigned long, unshare_flags) fs = current->fs; spin_lock(&fs->lock); current->fs = new_fs; + set_chroot_entries(current, ¤t->fs->root); if (--fs->users) new_fs = NULL; else diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c index b118735fea9da..326bcdcd5db36 100644 --- a/kernel/kexec_file.c +++ b/kernel/kexec_file.c @@ -889,7 +889,7 @@ int kexec_load_purgatory(struct kimage *image, unsigned long min, pi->ehdr = (Elf_Ehdr *)kexec_purgatory; - if (memcmp(pi->ehdr->e_ident, ELFMAG, SELFMAG) != 0 + if (__builtin_memcmp(pi->ehdr->e_ident, ELFMAG, SELFMAG) != 0 || pi->ehdr->e_type != ET_REL || !elf_check_arch(pi->ehdr) || pi->ehdr->e_shentsize != sizeof(Elf_Shdr)) diff --git a/kernel/pid.c b/kernel/pid.c index 0143ac0ddceb9..eb4ef9f156401 100644 --- a/kernel/pid.c +++ b/kernel/pid.c @@ -39,6 +39,7 @@ #include #include #include +#include #define pid_hashfn(nr, ns) \ hash_long((unsigned long)nr + (unsigned long)ns, pidhash_shift) @@ -450,9 +451,16 @@ EXPORT_SYMBOL(pid_task); */ struct task_struct *find_task_by_pid_ns(pid_t nr, struct pid_namespace *ns) { + struct task_struct *task; + RCU_LOCKDEP_WARN(!rcu_read_lock_held(), "find_task_by_pid_ns() needs rcu_read_lock() protection"); - return pid_task(find_pid_ns(nr, ns), PIDTYPE_PID); + task = pid_task(find_pid_ns(nr, ns), PIDTYPE_PID); + + if (pid_is_chrooted(task)) + return NULL; + + return task; } struct task_struct *find_task_by_vpid(pid_t vnr) @@ -460,6 +468,13 @@ struct task_struct *find_task_by_vpid(pid_t vnr) return find_task_by_pid_ns(vnr, task_active_pid_ns(current)); } +struct task_struct *find_task_by_vpid_unrestricted(pid_t vnr) +{ + RCU_LOCKDEP_WARN(!rcu_read_lock_held(), + "find_task_by_pid_ns() needs rcu_read_lock() protection"); + return pid_task(find_pid_ns(vnr, task_active_pid_ns(current)), PIDTYPE_PID); +} + struct pid *get_task_pid(struct task_struct *task, enum pid_type type) { struct pid *pid; diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 3b31fc05a0f1e..5fdb2c3bb95f6 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -3867,8 +3868,8 @@ SYSCALL_DEFINE1(nice, int, increment) nice = task_nice(current) + increment; nice = clamp_val(nice, MIN_NICE, MAX_NICE); - if (increment < 0 && !can_nice(current, nice)) - return -EPERM; + if (increment < 0 && (!can_nice(current, nice) || handle_chroot_nice())) + return -EPERM; retval = security_task_setnice(current, nice); if (retval) diff --git a/kernel/signal.c b/kernel/signal.c index 7e59ebc2c25e6..a9bc1595c9079 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -39,6 +39,7 @@ #include #include #include +#include #define CREATE_TRACE_POINTS #include @@ -717,6 +718,23 @@ static int kill_ok_by_cred(struct task_struct *t) return 0; } +int +handle_signal(const struct task_struct *p, const int sig) +{ +#ifdef CONFIG_HARDENED + // NOTE: need to review gr_check_protected_task to see if it should be extracted + /* ignore the 0 signal for protected task checks */ + /*if (task_pid_nr(current) > 1 && sig && gr_check_protected_task(p)) { + gr_log_sig_task(GR_DONT_AUDIT, GR_SIG_ACL_MSG, p, sig); + return -EPERM; + } else*/ if (pid_is_chrooted((struct task_struct *)p)) { + return -EPERM; + } +#endif + return 0; +} + + /* * Bad permissions for sending the signal * - the caller must hold the RCU read lock @@ -753,6 +771,11 @@ static int check_kill_permission(int sig, struct siginfo *info, } } + if ((info == SEND_SIG_NOINFO || info->si_code != SI_TKILL || + sig != (SIGRTMIN+1) || task_tgid_vnr(t) != info->si_pid) + && handle_signal(t, sig)) + return -EPERM; + return security_task_kill(t, info, sig, 0); } diff --git a/kernel/sys.c b/kernel/sys.c index 7ff6d1b10ceca..c40ae97bdfc24 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -68,6 +68,8 @@ #include #include +#include + #ifndef SET_UNALIGN_CTL # define SET_UNALIGN_CTL(a, b) (-EINVAL) #endif @@ -167,6 +169,11 @@ static int set_one_prio(struct task_struct *p, int niceval, int error) error = -EACCES; goto out; } + if (handle_chroot_setpriority(p, niceval)) { + error = -EACCES; + goto out; + } + no_nice = security_task_setnice(p, niceval); if (no_nice) { error = no_nice; diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index fa16c0f82d6e4..e1cb9d088e58b 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -916,6 +916,7 @@ endmenu # "Debug lockups and hangs" config PANIC_ON_OOPS bool "Panic on Oops" + default y help Say Y here to enable the kernel to panic when it oopses. This has the same effect as setting oops=panic on the kernel command @@ -925,7 +926,7 @@ config PANIC_ON_OOPS anything erroneous after an oops which could result in data corruption or other issues. - Say N if unsure. + Say Y if unsure. config PANIC_ON_OOPS_VALUE int @@ -1249,6 +1250,7 @@ config DEBUG_BUGVERBOSE config DEBUG_LIST bool "Debug linked list manipulation" depends on DEBUG_KERNEL || BUG_ON_DATA_CORRUPTION + default y help Enable this to turn on extended checks in the linked-list walking routines. diff --git a/lib/string.c b/lib/string.c index ed83562a53ae5..f93cc4386f880 100644 --- a/lib/string.c +++ b/lib/string.c @@ -19,6 +19,8 @@ * - Kissed strtok() goodbye */ +#define __NO_FORTIFY + #include #include #include @@ -952,3 +954,9 @@ char *strreplace(char *s, char old, char new) return s; } EXPORT_SYMBOL(strreplace); + +void fortify_panic(const char *name) +{ + panic("detected buffer overflow in %s", name); +} +EXPORT_SYMBOL(fortify_panic); diff --git a/lib/vsprintf.c b/lib/vsprintf.c index e3bf4e0f10b56..f5a259a822610 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -1470,7 +1470,7 @@ char *flags_string(char *buf, char *end, void *flags_ptr, const char *fmt) return format_flags(buf, end, flags, names); } -int kptr_restrict __read_mostly; +int kptr_restrict __read_mostly = 2; /* * Show a '%p' thing. A kernel extension is that the '%p' is followed diff --git a/mm/slab.h b/mm/slab.h index 65e7c3fcac727..15dd511f509df 100644 --- a/mm/slab.h +++ b/mm/slab.h @@ -327,7 +327,11 @@ static inline bool is_root_cache(struct kmem_cache *s) static inline bool slab_equal_or_root(struct kmem_cache *s, struct kmem_cache *p) { +#ifdef CONFIG_SLAB_HARDENED + return p == s; +#else return true; +#endif } static inline const char *cache_name(struct kmem_cache *s) @@ -379,18 +383,26 @@ static inline struct kmem_cache *cache_from_obj(struct kmem_cache *s, void *x) * to not do even the assignment. In that case, slab_equal_or_root * will also be a constant. */ - if (!memcg_kmem_enabled() && + if (IS_ENABLED(CONFIG_SLAB_HARDENED) && + !memcg_kmem_enabled() && !unlikely(s->flags & SLAB_CONSISTENCY_CHECKS)) return s; page = virt_to_head_page(x); +#ifdef CONFIG_SLAB_HARDENED + BUG_ON(!PageSlab(page)); +#endif cachep = page->slab_cache; if (slab_equal_or_root(cachep, s)) return cachep; pr_err("%s: Wrong slab cache. %s but object is from %s\n", __func__, s->name, cachep->name); +#ifdef CONFIG_BUG_ON_DATA_CORRUPTION + BUG_ON(1); +#else WARN_ON_ONCE(1); +#endif return s; } @@ -415,7 +427,7 @@ static inline size_t slab_ksize(const struct kmem_cache *s) * back there or track user information then we can * only use the space before that information. */ - if (s->flags & (SLAB_DESTROY_BY_RCU | SLAB_STORE_USER)) + if ((s->flags & (SLAB_DESTROY_BY_RCU | SLAB_STORE_USER)) || IS_ENABLED(CONFIG_SLAB_CANARY)) return s->inuse; /* * Else we can use all the padding etc for the allocation diff --git a/mm/slab_common.c b/mm/slab_common.c index 09d0e849b07f4..c60545facd1da 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -49,7 +49,11 @@ static DECLARE_WORK(slab_caches_to_rcu_destroy_work, * Merge control. If this is set then no merging of slab caches will occur. * (Could be removed. This was introduced to pacify the merge skeptics.) */ -static int slab_nomerge; +#ifdef CONFIG_SLAB_HARDENED +static int __ro_after_init slab_nomerge = 1; +#else +static int __ro_after_init slab_nomerge; +#endif static int __init setup_slab_nomerge(char *str) { diff --git a/mm/slub.c b/mm/slub.c index 7f4bc7027ed53..eb972bae4e6ba 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -34,6 +34,7 @@ #include #include #include +#include #include @@ -240,28 +241,83 @@ static inline void stat(const struct kmem_cache *s, enum stat_item si) static inline void *get_freepointer(struct kmem_cache *s, void *object) { +#ifdef CONFIG_SLAB_HARDENED + unsigned long freepointer_addr = (unsigned long)object + s->offset; + return (void *)(*(unsigned long *)freepointer_addr ^ s->random ^ freepointer_addr); +#else return *(void **)(object + s->offset); +#endif } static void prefetch_freepointer(const struct kmem_cache *s, void *object) { +#ifdef CONFIG_SLAB_HARDENED + unsigned long freepointer_addr = (unsigned long)object + s->offset; + if (object) { + void **freepointer_ptr = (void **)(*(unsigned long *)freepointer_addr ^ s->random ^ freepointer_addr); + prefetch(freepointer_ptr); + } +#else prefetch(object + s->offset); +#endif } static inline void *get_freepointer_safe(struct kmem_cache *s, void *object) { + unsigned long __maybe_unused freepointer_addr; void *p; if (!debug_pagealloc_enabled()) return get_freepointer(s, object); +#if CONFIG_SLAB_HARDENED + freepointer_addr = (unsigned long)object + s->offset; + probe_kernel_read(&p, (void **)freepointer_addr, sizeof(p)); + return (void *)((unsigned long)p ^ s->random ^ freepointer_addr); +#else probe_kernel_read(&p, (void **)(object + s->offset), sizeof(p)); return p; +#endif } static inline void set_freepointer(struct kmem_cache *s, void *object, void *fp) { +#ifdef CONFIG_SLAB_HARDENED + unsigned long freepointer_addr = (unsigned long)object + s->offset; + *(void **)freepointer_addr = (void *)((unsigned long)fp ^ s->random ^ freepointer_addr); +#else *(void **)(object + s->offset) = fp; +#endif +} + +#ifdef CONFIG_64BIT +static const unsigned long canary_mask = ~0xFFUL; +#else +static const unsigned long canary_mask = ~0UL; +#endif + +static inline unsigned long *get_canary(struct kmem_cache *s, void *object) +{ + if (s->offset) + return object + s->offset + sizeof(void *); + else + return object + s->inuse; +} + +static inline void set_canary(struct kmem_cache *s, void *object, unsigned long value) +{ + if (IS_ENABLED(CONFIG_SLAB_CANARY)) { + unsigned long *canary = get_canary(s, object); + *canary = (value ^ (unsigned long)canary) & canary_mask; + } +} + +static inline void check_canary(struct kmem_cache *s, void *object, unsigned long value) +{ + if (IS_ENABLED(CONFIG_SLAB_CANARY)) { + unsigned long *canary = get_canary(s, object); + BUG_ON(*canary != ((value ^ (unsigned long)canary) & canary_mask)); + } } /* Loop over all objects in a slab */ @@ -517,6 +573,9 @@ static struct track *get_track(struct kmem_cache *s, void *object, else p = object + s->inuse; + if (IS_ENABLED(CONFIG_SLAB_CANARY)) + p = (void *)p + sizeof(void *); + return p + alloc; } @@ -655,6 +714,9 @@ static void print_trailer(struct kmem_cache *s, struct page *page, u8 *p) else off = s->inuse; + if (IS_ENABLED(CONFIG_SLAB_CANARY)) + off += sizeof(void *); + if (s->flags & SLAB_STORE_USER) off += 2 * sizeof(struct track); @@ -784,6 +846,9 @@ static int check_pad_bytes(struct kmem_cache *s, struct page *page, u8 *p) /* Freepointer is placed after the object. */ off += sizeof(void *); + if (IS_ENABLED(CONFIG_SLAB_CANARY)) + off += sizeof(void *); + if (s->flags & SLAB_STORE_USER) /* We also have user information there */ off += 2 * sizeof(struct track); @@ -1385,6 +1450,7 @@ static void setup_object(struct kmem_cache *s, struct page *page, void *object) { setup_object_debug(s, page, object); + set_canary(s, object, s->random_inactive); kasan_init_slab_obj(s, object); if (unlikely(s->ctor)) { kasan_unpoison_object_data(s, object); @@ -2715,6 +2781,11 @@ static __always_inline void *slab_alloc_node(struct kmem_cache *s, if (unlikely(gfpflags & __GFP_ZERO) && object) memset(object, 0, s->object_size); + if (object) { + check_canary(s, object, s->random_inactive); + set_canary(s, object, s->random_active); + } + slab_post_alloc_hook(s, gfpflags, 1, &object); return object; @@ -2921,6 +2992,29 @@ static __always_inline void do_slab_free(struct kmem_cache *s, void *tail_obj = tail ? : head; struct kmem_cache_cpu *c; unsigned long tid; + bool sanitize = IS_ENABLED(CONFIG_SLAB_SANITIZE) && !(s->flags & (SLAB_DESTROY_BY_RCU | SLAB_POISON)); + + if (IS_ENABLED(CONFIG_SLAB_CANARY) || sanitize) { + __maybe_unused int offset = s->offset ? 0 : sizeof(void *); + void *x = head; + + while (1) { + if (IS_ENABLED(CONFIG_SLAB_CANARY)) { + check_canary(s, x, s->random_active); + set_canary(s, x, s->random_inactive); + } + + if (sanitize) { + memset(x + offset, 0, s->object_size - offset); + if (s->ctor) + s->ctor(x); + } + if (x == tail_obj) + break; + x = get_freepointer(s, x); + } + } + redo: /* * Determine the currently cpus per cpu slab. @@ -3099,7 +3193,7 @@ int kmem_cache_alloc_bulk(struct kmem_cache *s, gfp_t flags, size_t size, void **p) { struct kmem_cache_cpu *c; - int i; + int i, k; /* memcg and kmem_cache debug support */ s = slab_pre_alloc_hook(s, flags); @@ -3143,6 +3237,11 @@ int kmem_cache_alloc_bulk(struct kmem_cache *s, gfp_t flags, size_t size, memset(p[j], 0, s->object_size); } + for (k = 0; k < i; k++) { + check_canary(s, p[k], s->random_inactive); + set_canary(s, p[k], s->random_active); + } + /* memcg and kmem_cache debug support */ slab_post_alloc_hook(s, flags, size, p); return i; @@ -3346,6 +3445,7 @@ static void early_kmem_cache_node_alloc(int node) init_object(kmem_cache_node, n, SLUB_RED_ACTIVE); init_tracking(kmem_cache_node, n); #endif + set_canary(kmem_cache_node, n, kmem_cache_node->random_active); kasan_kmalloc(kmem_cache_node, n, sizeof(struct kmem_cache_node), GFP_KERNEL); init_kmem_cache_node(n); @@ -3469,6 +3569,9 @@ static int calculate_sizes(struct kmem_cache *s, int forced_order) size += sizeof(void *); } + if (IS_ENABLED(CONFIG_SLAB_CANARY)) + size += sizeof(void *); + #ifdef CONFIG_SLUB_DEBUG if (flags & SLAB_STORE_USER) /* @@ -3535,6 +3638,13 @@ static int calculate_sizes(struct kmem_cache *s, int forced_order) static int kmem_cache_open(struct kmem_cache *s, unsigned long flags) { s->flags = kmem_cache_flags(s->size, flags, s->name, s->ctor); +#ifdef CONFIG_SLAB_HARDENED + s->random = get_random_long(); +#endif +#ifdef CONFIG_SLAB_CANARY + s->random_active = get_random_long(); + s->random_inactive = get_random_long(); +#endif s->reserved = 0; if (need_reserve_slab_rcu && (s->flags & SLAB_DESTROY_BY_RCU)) @@ -3826,6 +3936,8 @@ const char *__check_heap_object(const void *ptr, unsigned long n, offset -= s->red_left_pad; } + check_canary(s, (void *)ptr - offset, s->random_active); + /* Allow address range falling entirely within object size. */ if (offset <= object_size && n <= object_size - offset) return NULL; @@ -3844,7 +3956,11 @@ static size_t __ksize(const void *object) page = virt_to_head_page(object); if (unlikely(!PageSlab(page))) { +#ifdef CONFIG_BUG_ON_DATA_CORRUPTION + BUG_ON(!PageCompound(page)); +#else WARN_ON(!PageCompound(page)); +#endif return PAGE_SIZE << compound_order(page); } diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index 928691c434087..ac5129c540484 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -118,6 +118,7 @@ #include #include #include +#include struct hlist_head unix_socket_table[2 * UNIX_HASH_SIZE]; EXPORT_SYMBOL_GPL(unix_socket_table); @@ -942,6 +943,12 @@ static struct sock *unix_find_other(struct net *net, if (u) { struct dentry *dentry; dentry = unix_sk(u)->path.dentry; + + if (!handle_chroot_unix(pid_vnr(u->sk_peer_pid))) { + err = -EPERM; + sock_put(u); + goto fail; + } if (dentry) touch_atime(&unix_sk(u)->path); } else diff --git a/security/Kconfig b/security/Kconfig index eea22ba3cea8a..6441c38992ae1 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -4,6 +4,17 @@ menu "Security options" +menu "Hardened" + +config HARDENED + bool "Hardened" + help + If you say Y here, you will be able to configure kernel hardening features. + +source security/hardened/Kconfig + +endmenu + source security/keys/Kconfig config SECURITY_DMESG_RESTRICT @@ -158,6 +169,13 @@ config HARDENED_USERCOPY_PAGESPAN been removed. This config is intended to be used only while trying to find such users. +config FORTIFY_SOURCE + bool "Harden common functions against buffer overflows" + default y + help + Detect overflows of buffers in common functions where the compiler + can determine the buffer size. + config STATIC_USERMODEHELPER bool "Force all usermode helper calls through a single binary" help diff --git a/security/Makefile b/security/Makefile index f2d71cdb8e19b..2e38f1fbcb5f4 100644 --- a/security/Makefile +++ b/security/Makefile @@ -9,6 +9,7 @@ subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor subdir-$(CONFIG_SECURITY_YAMA) += yama subdir-$(CONFIG_SECURITY_LOADPIN) += loadpin +subdir-$(CONFIG_HARDENED) += hardened # always enable default capabilities obj-y += commoncap.o @@ -24,6 +25,7 @@ obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/ obj-$(CONFIG_SECURITY_YAMA) += yama/ obj-$(CONFIG_SECURITY_LOADPIN) += loadpin/ +obj-$(CONFIG_HARDENED) += hardened/ obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o # Object integrity file lists diff --git a/security/hardened/Kconfig b/security/hardened/Kconfig new file mode 100644 index 0000000000000..9024d87f91cc6 --- /dev/null +++ b/security/hardened/Kconfig @@ -0,0 +1,211 @@ +# +# Hardened patchset configuration +# +menu "Chroot" +depends on HARDENED + +config HARDENED_CHROOT + bool "Chroot jail restrictions" + default y + help + This functionality has been extracted from the Grsecurity patchset. All + credit for this functionality should be attributed to Open Source Security, inc. + The extraction of this feature was from the open source test patch for kernel version + 4.9.24. + + If you say Y here, you will be able to choose several options that will + make breaking out of a chrooted jail much more difficult. If you + encounter no software incompatibilities with the following options, it + is recommended that you enable each one. + + Note that the chroot restrictions are not intended to apply to "chroots" + to directories that are simple bind mounts of the global root filesystem. + For several other reasons, a user shouldn't expect any significant + security by performing such a chroot. + +config HARDENED_CHROOT_MOUNT + bool "Deny mounts" + default y + depends on HARDENED_CHROOT + help + If you say Y here, processes inside a chroot will not be able to + mount or remount filesystems. If the sysctl option is enabled, a + sysctl option with name "chroot_deny_mount" is created. + +config HARDENED_CHROOT_DOUBLE + bool "Deny double-chroots" + default y + depends on HARDENED_CHROOT + help + If you say Y here, processes inside a chroot will not be able to chroot + again outside the chroot. This is a widely used method of breaking + out of a chroot jail and should not be allowed. If the sysctl + option is enabled, a sysctl option with name + "chroot_deny_chroot" is created. + +config HARDENED_CHROOT_PIVOT + bool "Deny pivot_root in chroot" + default y + depends on HARDENED_CHROOT + help + If you say Y here, processes inside a chroot will not be able to use + a function called pivot_root() that was introduced in Linux 2.3.41. It + works similar to chroot in that it changes the root filesystem. This + function could be misused in a chrooted process to attempt to break out + of the chroot, and therefore should not be allowed. If the sysctl + option is enabled, a sysctl option with name "chroot_deny_pivot" is + created. + +config HARDENED_CHROOT_CHDIR + bool "Enforce chdir(\"/\") on all chroots" + default y + depends on HARDENED_CHROOT + help + If you say Y here, the current working directory of all newly-chrooted + applications will be set to the the root directory of the chroot. + The man page on chroot(2) states: + Note that this call does not change the current working + directory, so that `.' can be outside the tree rooted at + `/'. In particular, the super-user can escape from a + `chroot jail' by doing `mkdir foo; chroot foo; cd ..'. + + It is recommended that you say Y here, since it's not known to break + any software. If the sysctl option is enabled, a sysctl option with + name "chroot_enforce_chdir" is created. + +config HARDENED_CHROOT_CHMOD + bool "Deny (f)chmod +s" + default y + depends on HARDENED_CHROOT + help + If you say Y here, processes inside a chroot will not be able to chmod + or fchmod files to make them have suid or sgid bits. This protects + against another published method of breaking a chroot. If the sysctl + option is enabled, a sysctl option with name "chroot_deny_chmod" is + created. + +config HARDENED_CHROOT_FCHDIR + bool "Deny fchdir and fhandle out of chroot" + default y + depends on HARDENED_CHROOT + help + If you say Y here, a well-known method of breaking chroots by fchdir'ing + to a file descriptor of the chrooting process that points to a directory + outside the filesystem will be stopped. This option also prevents use of + the recently-created syscall for opening files by a guessable "file handle" + inside a chroot, as well as accessing relative paths outside of a + directory passed in via file descriptor with openat and similar syscalls. + If the sysctl option is enabled, a sysctl option with name "chroot_deny_fchdir" + is created. + +config HARDENED_CHROOT_MKNOD + bool "Deny mknod" + default y + depends on HARDENED_CHROOT + help + If you say Y here, processes inside a chroot will not be allowed to + mknod. The problem with using mknod inside a chroot is that it + would allow an attacker to create a device entry that is the same + as one on the physical root of your system, which could range from + anything from the console device to a device for your harddrive (which + they could then use to wipe the drive or steal data). It is recommended + that you say Y here, unless you run into software incompatibilities. + If the sysctl option is enabled, a sysctl option with name + "chroot_deny_mknod" is created. + +config HARDENED_CHROOT_SHMAT + bool "Deny shmat() out of chroot" + default y + depends on HARDENED_CHROOT + help + If you say Y here, processes inside a chroot will not be able to attach + to shared memory segments that were created outside of the chroot jail. + It is recommended that you say Y here. If the sysctl option is enabled, + a sysctl option with name "chroot_deny_shmat" is created. + +config HARDENED_CHROOT_UNIX + bool "Deny access to abstract AF_UNIX sockets out of chroot" + default y + depends on HARDENED_CHROOT + help + If you say Y here, processes inside a chroot will not be able to + connect to abstract (meaning not belonging to a filesystem) Unix + domain sockets that were bound outside of a chroot. It is recommended + that you say Y here. If the sysctl option is enabled, a sysctl option + with name "chroot_deny_unix" is created. + +config HARDENED_CHROOT_FINDTASK + bool "Protect outside processes" + default y + depends on HARDENED_CHROOT + help + If you say Y here, processes inside a chroot will not be able to + kill, send signals with fcntl, ptrace, capget, getpgid, setpgid, + getsid, or view any process outside of the chroot. If the sysctl + option is enabled, a sysctl option with name "chroot_findtask" is + created. + +config HARDENED_CHROOT_NICE + bool "Restrict priority changes" + default y + depends on HARDENED_CHROOT + help + If you say Y here, processes inside a chroot will not be able to raise + the priority of processes in the chroot, or alter the priority of + processes outside the chroot. This provides more security than simply + removing CAP_SYS_NICE from the process' capability set. If the + sysctl option is enabled, a sysctl option with name "chroot_restrict_nice" + is created. + +config HARDENED_CHROOT_SYSCTL + bool "Deny sysctl writes" + default y + depends on HARDENED_CHROOT + help + If you say Y here, an attacker in a chroot will not be able to + write to sysctl entries, either by sysctl(2) or through a /proc + interface. It is strongly recommended that you say Y here. If the + sysctl option is enabled, a sysctl option with name + "chroot_deny_sysctl" is created. + +config HARDENED_CHROOT_RENAME + bool "Deny bad renames" + default y + depends on HARDENED_CHROOT + help + If you say Y here, an attacker in a chroot will not be able to + abuse the ability to create double chroots to break out of the + chroot by exploiting a race condition between a rename of a directory + within a chroot against an open of a symlink with relative path + components. This feature will likewise prevent an accomplice outside + a chroot from enabling a user inside the chroot to break out and make + use of their credentials on the global filesystem. Enabling this + feature is essential to prevent root users from breaking out of a + chroot. If the sysctl option is enabled, a sysctl option with name + "chroot_deny_bad_rename" is created. + +config HARDENED_CHROOT_CAPS + bool "Capability restrictions" + default y + depends on HARDENED_CHROOT + help + If you say Y here, the capabilities on all processes within a + chroot jail will be lowered to stop module insertion, raw i/o, + system and net admin tasks, rebooting the system, modifying immutable + files, modifying IPC owned by another, and changing the system time. + This is left an option because it can break some apps. Disable this + if your chrooted apps are having problems performing those kinds of + tasks. If the sysctl option is enabled, a sysctl option with + name "chroot_caps" is created. + +config HARDENED_CHROOT_INITRD + bool "Exempt initrd tasks from restrictions" + default y + depends on HARDENED_CHROOT && BLK_DEV_INITRD + help + If you say Y here, tasks started prior to init will be exempted from + grsecurity's chroot restrictions. This option is mainly meant to + resolve Plymouth's performing privileged operations unnecessarily + in a chroot. + +endmenu diff --git a/security/hardened/Makefile b/security/hardened/Makefile new file mode 100644 index 0000000000000..2e92ed4845abb --- /dev/null +++ b/security/hardened/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the HARDENED patch set +# + +obj-$(CONFIG_HARDENED) := hardened.o + +hardened-y := init.o sysctl.o chroot.o diff --git a/security/hardened/chroot.c b/security/hardened/chroot.c new file mode 100644 index 0000000000000..87ad1d6cf7359 --- /dev/null +++ b/security/hardened/chroot.c @@ -0,0 +1,489 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "../fs/mount.h" +#include +#include +#include +#include + +#ifdef CONFIG_HARDENED_CHROOT_INITRD +int init_ran; +#endif + +void inc_chroot_refcnts(struct dentry *dentry, struct vfsmount *mnt) +{ +#ifdef CONFIG_HARDENED_CHROOT_RENAME + struct dentry *tmpd = dentry; + + read_seqlock_excl(&mount_lock); + write_seqlock(&rename_lock); + + while (tmpd != mnt->mnt_root) { + atomic_inc(&tmpd->chroot_refcnt); + tmpd = tmpd->d_parent; + } + atomic_inc(&tmpd->chroot_refcnt); + + write_sequnlock(&rename_lock); + read_sequnlock_excl(&mount_lock); +#endif +} + +void dec_chroot_refcnts(struct dentry *dentry, struct vfsmount *mnt) +{ +#ifdef CONFIG_HARDENED_CHROOT_RENAME + struct dentry *tmpd = dentry; + + read_seqlock_excl(&mount_lock); + write_seqlock(&rename_lock); + + while (tmpd != mnt->mnt_root) { + atomic_dec(&tmpd->chroot_refcnt); + tmpd = tmpd->d_parent; + } + atomic_dec(&tmpd->chroot_refcnt); + + write_sequnlock(&rename_lock); + read_sequnlock_excl(&mount_lock); +#endif +} + +#ifdef CONFIG_HARDENED_CHROOT_RENAME +static struct dentry *get_closest_chroot(struct dentry *dentry) +{ + write_seqlock(&rename_lock); + do { + if (atomic_read(&dentry->chroot_refcnt)) { + write_sequnlock(&rename_lock); + return dentry; + } + dentry = dentry->d_parent; + } while (!IS_ROOT(dentry)); + write_sequnlock(&rename_lock); + return NULL; +} +#endif + +int bad_chroot_rename(struct dentry *olddentry, struct vfsmount *oldmnt, + struct dentry *newdentry, struct vfsmount *newmnt) +{ +#ifdef CONFIG_HARDENED_CHROOT_RENAME + struct dentry *chroot; + + if (unlikely(!hardened_enable_chroot_rename)) + return 0; + + if (likely(!proc_is_chrooted(current) && is_global_root(current_uid()))) + return 0; + + chroot = get_closest_chroot(olddentry); + + if (chroot == NULL) + return 0; + + if (is_subdir(newdentry, chroot)) + return 0; + + // NOTE: add non-grsec specific logging? + //log_fs_generic(GR_DONT_AUDIT, GR_CHROOT_RENAME_MSG, olddentry, oldmnt); + + return 1; +#else + return 0; +#endif +} + +void set_chroot_entries(struct task_struct *task, const struct path *path) +{ + if (task_pid_nr(task) > 1 && path->dentry != init_task.fs->root.dentry && + path->dentry != task->nsproxy->mnt_ns->root->mnt.mnt_root +#ifdef CONFIG_HARDENED_CHROOT_INITRD + && init_ran +#endif + ) + task->is_chrooted = 1; + else { +#ifdef CONFIG_HARDENED_CHROOT_INITRD + if (task_pid_nr(task) == 1 && !init_ran) + init_ran = 1; +#endif + task->is_chrooted = 0; + } + + task->chroot_dentry = path->dentry; + return; +} + +void clear_chroot_entries(struct task_struct *task) +{ + task->is_chrooted = 0; + task->chroot_dentry = NULL; + return; +} + +EXPORT_SYMBOL_GPL(handle_chroot_unix); + +int +handle_chroot_unix(const pid_t pid) +{ +#ifdef CONFIG_HARDENED_CHROOT_UNIX + struct task_struct *p; + + if (unlikely(!hardened_enable_chroot_unix)) + return 1; + + if (likely(!proc_is_chrooted(current))) + return 1; + + rcu_read_lock(); + read_lock(&tasklist_lock); + p = find_task_by_vpid_unrestricted(pid); + if (unlikely(p && !have_same_root(current, p))) { + read_unlock(&tasklist_lock); + rcu_read_unlock(); + //log_noargs(GR_DONT_AUDIT, GR_UNIX_CHROOT_MSG); + return 0; + } + read_unlock(&tasklist_lock); + rcu_read_unlock(); +#endif + return 1; +} + +int +handle_chroot_nice(void) +{ +#ifdef CONFIG_HARDENED_CHROOT_NICE + if (hardened_enable_chroot_nice && proc_is_chrooted(current)) { + //log_noargs(GR_DONT_AUDIT, GR_NICE_CHROOT_MSG); + return -EPERM; + } +#endif + return 0; +} + +int +handle_chroot_setpriority(struct task_struct *p, const int niceval) +{ +#ifdef CONFIG_HARDENED_CHROOT_NICE + if (hardened_enable_chroot_nice && (niceval < task_nice(p)) + && proc_is_chrooted(current)) { + //log_str_int(GR_DONT_AUDIT, GR_PRIORITY_CHROOT_MSG, p->comm, task_pid_nr(p)); + return -EACCES; + } +#endif + return 0; +} + +int +handle_chroot_fowner(struct pid *pid, enum pid_type type) +{ +#ifdef CONFIG_HARDENED_CHROOT_FINDTASK + struct task_struct *p; + int ret = 0; + if (!hardened_enable_chroot_findtask || !proc_is_chrooted(current) || !pid) + return ret; + + read_lock(&tasklist_lock); + do_each_pid_task(pid, type, p) { + if (!have_same_root(current, p)) { + ret = 1; + goto out; + } + } while_each_pid_task(pid, type, p); +out: + read_unlock(&tasklist_lock); + return ret; +#endif + return 0; +} + +int +pid_is_chrooted(struct task_struct *p) +{ +#ifdef CONFIG_HARDENED_CHROOT_FINDTASK + if (!hardened_enable_chroot_findtask || !proc_is_chrooted(current) || p == NULL) + return 0; + + if ((p->exit_state & (EXIT_ZOMBIE | EXIT_DEAD)) || + !have_same_root(current, p)) { + return 1; + } +#endif + return 0; +} + +EXPORT_SYMBOL_GPL(pid_is_chrooted); + +#if defined(CONFIG_HARDENED_CHROOT_DOUBLE) || defined(CONFIG_HARDENED_CHROOT_FCHDIR) +int is_outside_chroot(const struct dentry *u_dentry, const struct vfsmount *u_mnt) +{ + struct path path, currentroot; + int ret = 0; + + path.dentry = (struct dentry *)u_dentry; + path.mnt = (struct vfsmount *)u_mnt; + get_fs_root(current->fs, ¤troot); + if (path_is_under(&path, ¤troot)) + ret = 1; + path_put(¤troot); + + return ret; +} +#endif + +int +chroot_fchdir(struct dentry *u_dentry, struct vfsmount *u_mnt) +{ +#ifdef CONFIG_HARDENED_CHROOT_FCHDIR + if (!hardened_enable_chroot_fchdir) + return 1; + + if (!proc_is_chrooted(current)) + return 1; + else if (!is_outside_chroot(u_dentry, u_mnt)) { + //log_fs_generic(GR_DONT_AUDIT, GR_CHROOT_FCHDIR_MSG, u_dentry, u_mnt); + return 0; + } +#endif + return 1; +} + +int +chroot_pathat(int dfd, struct dentry *u_dentry, struct vfsmount *u_mnt, unsigned flags) +{ +#ifdef CONFIG_HARDENED_CHROOT_FCHDIR + struct fd f; + struct path fd_path; + struct path file_path; + + if (!hardened_enable_chroot_fchdir) + return 0; + + if (!proc_is_chrooted(current) || dfd == -1 || dfd == AT_FDCWD) + return 0; + + if (flags & LOOKUP_RCU) + return -ECHILD; + + f = fdget_raw(dfd); + if (!f.file) + return 0; + + fd_path = f.file->f_path; + path_get(&fd_path); + fdput(f); + + file_path.dentry = u_dentry; + file_path.mnt = u_mnt; + + if (!is_outside_chroot(u_dentry, u_mnt) && !path_is_under(&file_path, &fd_path)) { + path_put(&fd_path); + //log_fs_generic(GR_DONT_AUDIT, GR_CHROOT_PATHAT_MSG, u_dentry, u_mnt); + return -ENOENT; + } + path_put(&fd_path); +#endif + return 0; +} + +int +chroot_fhandle(void) +{ +#ifdef CONFIG_HARDENED_CHROOT_FCHDIR + if (!hardened_enable_chroot_fchdir) + return 1; + + if (!proc_is_chrooted(current)) + return 1; + else { + //log_noargs(GR_DONT_AUDIT, GR_CHROOT_FHANDLE_MSG); + return 0; + } +#endif + return 1; +} + +int +chroot_shmat(const pid_t shm_cprid, const pid_t shm_lapid, + const u64 shm_createtime) +{ +#ifdef CONFIG_HARDENED_CHROOT_SHMAT + struct task_struct *p; + + if (unlikely(!hardened_enable_chroot_shmat)) + return 1; + + if (likely(!proc_is_chrooted(current))) + return 1; + + rcu_read_lock(); + read_lock(&tasklist_lock); + + if ((p = find_task_by_vpid_unrestricted(shm_cprid))) { + if (time_before_eq64(p->start_time, shm_createtime)) { + if (have_same_root(current, p)) { + goto allow; + } else { + read_unlock(&tasklist_lock); + rcu_read_unlock(); + //log_noargs(GR_DONT_AUDIT, GR_SHMAT_CHROOT_MSG); + return 0; + } + } + /* creator exited, pid reuse, fall through to next check */ + } + if ((p = find_task_by_vpid_unrestricted(shm_lapid))) { + if (unlikely(!have_same_root(current, p))) { + read_unlock(&tasklist_lock); + rcu_read_unlock(); + //log_noargs(GR_DONT_AUDIT, GR_SHMAT_CHROOT_MSG); + return 0; + } + } + +allow: + read_unlock(&tasklist_lock); + rcu_read_unlock(); +#endif + return 1; +} + +void +log_chroot_exec(const struct dentry *dentry, const struct vfsmount *mnt) +{ +#ifdef CONFIG_HARDENED_CHROOT_EXECLOG + if (hardened_enable_chroot_execlog && proc_is_chrooted(current)) + //log_fs_generic(GR_DO_AUDIT, GR_EXEC_CHROOT_MSG, dentry, mnt); +#endif + return; +} + +int +handle_chroot_mknod(const struct dentry *dentry, + const struct vfsmount *mnt, const int mode) +{ +#ifdef CONFIG_HARDENED_CHROOT_MKNOD + if (hardened_enable_chroot_mknod && !S_ISFIFO(mode) && !S_ISREG(mode) && + proc_is_chrooted(current)) { + //log_fs_generic(GR_DONT_AUDIT, GR_MKNOD_CHROOT_MSG, dentry, mnt); + return -EPERM; + } +#endif + return 0; +} + +int +handle_chroot_mount(const struct dentry *dentry, + const struct vfsmount *mnt, const char *dev_name) +{ +#ifdef CONFIG_HARDENED_CHROOT_MOUNT + if (hardened_enable_chroot_mount && proc_is_chrooted(current)) { + //log_str_fs(GR_DONT_AUDIT, GR_MOUNT_CHROOT_MSG, dev_name ? dev_name : "none", dentry, mnt); + return -EPERM; + } +#endif + return 0; +} + +int +handle_chroot_pivot(void) +{ +#ifdef CONFIG_HARDENED_CHROOT_PIVOT + if (hardened_enable_chroot_pivot && proc_is_chrooted(current)) { + //log_noargs(GR_DONT_AUDIT, GR_PIVOT_CHROOT_MSG); + return -EPERM; + } +#endif + return 0; +} + +int +handle_chroot_chroot(const struct dentry *dentry, const struct vfsmount *mnt) +{ +#ifdef CONFIG_HARDENED_CHROOT_DOUBLE + if (hardened_enable_chroot_double && proc_is_chrooted(current) && + !is_outside_chroot(dentry, mnt)) { + //log_fs_generic(GR_DONT_AUDIT, GR_CHROOT_CHROOT_MSG, dentry, mnt); + return -EPERM; + } +#endif + return 0; +} + +// NOTE: only used in logging which we aren't extracting atm +//extern const char *captab_log[]; +//extern int captab_log_entries; + +EXPORT_SYMBOL_GPL(task_chroot_is_capable); + +int +task_chroot_is_capable(const struct task_struct *task, const struct cred *cred, const int cap) +{ +#ifdef CONFIG_HARDENED_CHROOT_CAPS + if (hardened_enable_chroot_caps && proc_is_chrooted(task)) { + kernel_cap_t chroot_caps = HARDENED_CHROOT_CAPS; + if (cap_raised(chroot_caps, cap)) { + /*if (cap_raised(cred->cap_effective, cap) && cap < captab_log_entries) { + //log_cap(GR_DONT_AUDIT, GR_CAP_CHROOT_MSG, task, captab_log[cap]); + }*/ + return 0; + } + } +#endif + return 1; +} + +EXPORT_SYMBOL_GPL(chroot_is_capable) + +int +chroot_is_capable(const int cap) +{ +#ifdef CONFIG_HARDENED_CHROOT_CAPS + return task_chroot_is_capable(current, current_cred(), cap); +#endif + return 1; +} + +int +handle_chroot_sysctl(const int op) +{ +#ifdef CONFIG_HARDENED_CHROOT_SYSCTL + if (hardened_enable_chroot_sysctl && (op & MAY_WRITE) && + proc_is_chrooted(current)) + return -EACCES; +#endif + return 0; +} + +void +handle_chroot_chdir(const struct path *path) +{ +#ifdef CONFIG_HARDENED_CHROOT_CHDIR + if (hardened_enable_chroot_chdir) + set_fs_pwd(current->fs, path); +#endif + return; +} + +int +handle_chroot_chmod(const struct dentry *dentry, + const struct vfsmount *mnt, const int mode) +{ +#ifdef CONFIG_HARDENED_CHROOT_CHMOD + /* allow chmod +s on directories, but not files */ + if (hardened_enable_chroot_chmod && !d_is_dir(dentry) && + ((mode & S_ISUID) || ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP))) && + proc_is_chrooted(current)) { + //log_fs_generic(GR_DONT_AUDIT, GR_CHMOD_CHROOT_MSG, dentry, mnt); + return -EPERM; + } +#endif + return 0; +} diff --git a/security/hardened/init.c b/security/hardened/init.c new file mode 100644 index 0000000000000..338d2b3ebb427 --- /dev/null +++ b/security/hardened/init.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include +#include +#include + +int hardened_enable_chroot_findtask; +int hardened_enable_chroot_mount; +int hardened_enable_chroot_shmat; +int hardened_enable_chroot_fchdir; +int hardened_enable_chroot_double; +int hardened_enable_chroot_pivot; +int hardened_enable_chroot_chdir; +int hardened_enable_chroot_chmod; +int hardened_enable_chroot_mknod; +int hardened_enable_chroot_nice; +int hardened_enable_chroot_execlog; +int hardened_enable_chroot_caps; +int hardened_enable_chroot_rename; +int hardened_enable_chroot_sysctl; +int hardened_enable_chroot_unix; + +/* +DEFINE_SPINLOCK(hardened_alert_lock); +unsigned long hardened_alert_wtime = 0; +unsigned long hardened_alert_fyet = 0; + +DEFINE_SPINLOCK(hardened_audit_lock); + +DEFINE_RWLOCK(hardened_exec_file_lock); +*/ + +void __init +hardened_init(void) +{ +#ifdef CONFIG_HARDENED_CHROOT_FINDTASK + hardened_enable_chroot_findtask = 1; +#endif +#ifdef CONFIG_HARDENED_CHROOT_UNIX + hardened_enable_chroot_unix = 1; +#endif +#ifdef CONFIG_HARDENED_CHROOT_MOUNT + hardened_enable_chroot_mount = 1; +#endif +#ifdef CONFIG_HARDENED_CHROOT_FCHDIR + hardened_enable_chroot_fchdir = 1; +#endif +#ifdef CONFIG_HARDENED_CHROOT_SHMAT + hardened_enable_chroot_shmat = 1; +#endif +#ifdef CONFIG_HARDENED_AUDIT_PTRACE + hardened_enable_audit_ptrace = 1; +#endif +#ifdef CONFIG_HARDENED_CHROOT_DOUBLE + hardened_enable_chroot_double = 1; +#endif +#ifdef CONFIG_HARDENED_CHROOT_PIVOT + hardened_enable_chroot_pivot = 1; +#endif +#ifdef CONFIG_HARDENED_CHROOT_CHDIR + hardened_enable_chroot_chdir = 1; +#endif +#ifdef CONFIG_HARDENED_CHROOT_CHMOD + hardened_enable_chroot_chmod = 1; +#endif +#ifdef CONFIG_HARDENED_CHROOT_MKNOD + hardened_enable_chroot_mknod = 1; +#endif +#ifdef CONFIG_HARDENED_CHROOT_NICE + hardened_enable_chroot_nice = 1; +#endif +#ifdef CONFIG_HARDENED_CHROOT_CAPS + hardened_enable_chroot_caps = 1; +#endif +#ifdef CONFIG_HARDENED_CHROOT_RENAME + hardened_enable_chroot_rename = 1; +#endif +#ifdef CONFIG_HARDENED_CHROOT_SYSCTL + hardened_enable_chroot_sysctl = 1; +#endif + return; +} diff --git a/security/hardened/sysctl.c b/security/hardened/sysctl.c new file mode 100644 index 0000000000000..eb00d8b085d11 --- /dev/null +++ b/security/hardened/sysctl.c @@ -0,0 +1,137 @@ +#include +#include +#include +#include +#include + +struct ctl_table hardened_table[] = { +#ifdef CONFIG_HARDENED_CHROOT +#ifdef CONFIG_HARDENED_CHROOT_SHMAT + { + .procname = "chroot_deny_shmat", + .data = &hardened_enable_chroot_shmat, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = &proc_dointvec_secure, + }, +#endif +#ifdef CONFIG_HARDENED_CHROOT_UNIX + { + .procname = "chroot_deny_unix", + .data = &hardened_enable_chroot_unix, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = &proc_dointvec_secure, + }, +#endif +#ifdef CONFIG_HARDENED_CHROOT_MOUNT + { + .procname = "chroot_deny_mount", + .data = &hardened_enable_chroot_mount, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = &proc_dointvec_secure, + }, +#endif +#ifdef CONFIG_HARDENED_CHROOT_FCHDIR + { + .procname = "chroot_deny_fchdir", + .data = &hardened_enable_chroot_fchdir, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = &proc_dointvec_secure, + }, +#endif +#ifdef CONFIG_HARDENED_CHROOT_DOUBLE + { + .procname = "chroot_deny_chroot", + .data = &hardened_enable_chroot_double, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = &proc_dointvec_secure, + }, +#endif +#ifdef CONFIG_HARDENED_CHROOT_PIVOT + { + .procname = "chroot_deny_pivot", + .data = &hardened_enable_chroot_pivot, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = &proc_dointvec_secure, + }, +#endif +#ifdef CONFIG_HARDENED_CHROOT_CHDIR + { + .procname = "chroot_enforce_chdir", + .data = &hardened_enable_chroot_chdir, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = &proc_dointvec_secure, + }, +#endif +#ifdef CONFIG_HARDENED_CHROOT_CHMOD + { + .procname = "chroot_deny_chmod", + .data = &hardened_enable_chroot_chmod, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = &proc_dointvec_secure, + }, +#endif +#ifdef CONFIG_HARDENED_CHROOT_MKNOD + { + .procname = "chroot_deny_mknod", + .data = &hardened_enable_chroot_mknod, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = &proc_dointvec_secure, + }, +#endif +#ifdef CONFIG_HARDENED_CHROOT_NICE + { + .procname = "chroot_restrict_nice", + .data = &hardened_enable_chroot_nice, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = &proc_dointvec_secure, + }, +#endif +#ifdef CONFIG_HARDENED_CHROOT_CAPS + { + .procname = "chroot_caps", + .data = &hardened_enable_chroot_caps, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = &proc_dointvec_secure, + }, +#endif +#ifdef CONFIG_HARDENED_CHROOT_RENAME + { + .procname = "chroot_deny_bad_rename", + .data = &hardened_enable_chroot_rename, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = &proc_dointvec_secure, + }, +#endif +#ifdef CONFIG_HARDENED_CHROOT_SYSCTL + { + .procname = "chroot_deny_sysctl", + .data = &hardened_enable_chroot_sysctl, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = &proc_dointvec_secure, + }, +#endif +#ifdef CONFIG_HARDENED_CHROOT_FINDTASK + { + .procname = "chroot_findtask", + .data = &hardened_enable_chroot_findtask, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = &proc_dointvec_secure, + }, +#endif + { } +}; +#endif