From a003fcdac9f862c37c2d44f47043797738dc6f21 Mon Sep 17 00:00:00 2001 From: madaidan <50278627+madaidan@users.noreply.github.com> Date: Wed, 12 Feb 2020 23:03:35 +0000 Subject: [PATCH] Implement Trusted Path Execution This is modified from Brad Spengler/PaX Team's code in the last public patch of grsecurity/PaX based on my understanding of the code. Changes or omissions from the original code are mine and don't reflect the original grsecurity/PaX code. Trusted Path Execution (TPE) will restrict certain users so that they are only able to execute files in root-owned directories, writable only by root. This makes it significantly harder for an attacker to execute their own code. --- Documentation/admin-guide/sysctl/fs.rst | 58 +++++++++++++++++++ fs/Makefile | 2 +- fs/exec.c | 5 ++ fs/tpe.c | 75 +++++++++++++++++++++++++ include/linux/fs.h | 6 ++ kernel/sysctl.c | 35 ++++++++++++ security/Kconfig | 69 +++++++++++++++++++++++ 7 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 fs/tpe.c diff --git a/Documentation/admin-guide/sysctl/fs.rst b/Documentation/admin-guide/sysctl/fs.rst index 2a501c9ddc556..7b5c30824f555 100644 --- a/Documentation/admin-guide/sysctl/fs.rst +++ b/Documentation/admin-guide/sysctl/fs.rst @@ -48,6 +48,10 @@ Currently, these files are in /proc/sys/fs: - suid_dumpable - super-max - super-nr +- tpe +- tpe_restrict_all +- tpe_invert +- tpe_gid aio-nr & aio-max-nr @@ -326,6 +330,60 @@ This denotes the maximum number of mounts that may exist in a mount namespace. +tpe +--- + +This indicates whether Trusted Path Execution (TPE) is +enabled. + +When tpe is set to (0), TPE is disabled. When tpe is set +to (1), you will be able to choose a gid to add to the +supplementary groups of users you want to mark as "untrusted." +These users will not be able to execute any files that are not in +root-owned directories writable only by root. This makes it far +harder for attackers to execute their own code. + +The kernel config option CONFIG_SECURITY_TPE sets the +default value of tpe. + + +tpe_restrict_all +---------------- + +If tpe_restrict_all is enabled, all non-root users will be covered under +a weaker TPE restriction. This is separate from, and in addition to, +the main TPE options that you have selected elsewhere. Thus, if a +"trusted" GID is chosen, this restriction applies to even that GID. +Under this restriction, all non-root users will only be allowed to +execute files in directories they own that are not group or +world-writable, or in directories owned by root and writable only by +root. + +The kernel config option CONFIG_SECURITY_TPE_ALL sets the +default value of tpe_restrict_all. + + +tpe_invert +---------- + +If tpe_invert is enabled, the group you specify in the TPE configuration will +decide what group TPE restrictions will be *disabled* for. This +option is useful if you want TPE restrictions to be applied to most +users on the system. + +The kernel config option CONFIG_SECURITY_TPE_INVERT sets the +default value of tpe_invert. + + +tpe_gid +------- + +Setting this GID determines what group TPE restrictions will be +enabled or disabled for. + +The kernel config option CONFIG_SECURITY_TPE_GID sets the +default value of tpe_gid. + 2. /proc/sys/fs/binfmt_misc =========================== diff --git a/fs/Makefile b/fs/Makefile index 3215fe205256d..c69f11f9381b8 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -14,7 +14,7 @@ obj-y := open.o read_write.o file_table.o super.o \ pnode.o splice.o sync.o utimes.o d_path.o \ stack.o fs_struct.o statfs.o fs_pin.o nsfs.o \ fs_types.o fs_context.o fs_parser.o fsopen.o init.o \ - kernel_read_file.o remap_range.o + kernel_read_file.o remap_range.o tpe.o ifeq ($(CONFIG_BLOCK),y) obj-y += buffer.o block_dev.o direct-io.o mpage.o diff --git a/fs/exec.c b/fs/exec.c index 1abcfa1ddba19..ed91bbf98ed3d 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1831,6 +1831,11 @@ static int bprm_execve(struct linux_binprm *bprm, if (retval) goto out; + if (!tpe_allow(file)) { + retval = -EACCES; + goto out; + } + retval = exec_binprm(bprm); if (retval < 0) goto out; diff --git a/fs/tpe.c b/fs/tpe.c new file mode 100644 index 0000000000000..58f316fcfb5db --- /dev/null +++ b/fs/tpe.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include + +#define TPE_GLOBAL_UID(x) from_kuid_munged(&init_user_ns, (x)) +#define TPE_GLOBAL_GID(x) from_kgid_munged(&init_user_ns, (x)) +#define tpe_is_global_root(x) uid_eq((x), GLOBAL_ROOT_UID) +#define tpe_is_global_nonroot(x) (!uid_eq((x), GLOBAL_ROOT_UID)) +#define tpe_is_global_nonroot_gid(x) (!gid_eq((x), GLOBAL_ROOT_GID)) + +int security_tpe = IS_ENABLED(CONFIG_SECURITY_TPE); +int security_tpe_all = IS_ENABLED(CONFIG_SECURITY_TPE_ALL); +int security_tpe_invert = IS_ENABLED(CONFIG_SECURITY_TPE_INVERT); +kgid_t security_tpe_gid = KGIDT_INIT(CONFIG_SECURITY_TPE_GID); + +int +tpe_allow(const struct file *file) +{ + struct inode *inode = d_backing_inode(file->f_path.dentry->d_parent); + struct inode *file_inode = d_backing_inode(file->f_path.dentry); + const struct cred *cred = current_cred(); + char *msg = NULL; + char *msg2 = NULL; + + if (!security_tpe) + return 1; + + // never restrict root + if (tpe_is_global_root(cred->uid)) + return 1; + + if (security_tpe_all) { + if (tpe_is_global_nonroot(inode->i_uid) && !uid_eq(inode->i_uid, cred->uid)) + msg = "directory not owned by user"; + else if (inode->i_mode & S_IWOTH) + msg = "file in world-writable directory"; + else if ((inode->i_mode & S_IWGRP) && tpe_is_global_nonroot_gid(inode->i_gid)) + msg = "file in group-writable directory"; + else if (file_inode->i_mode & S_IWOTH) + msg = "file is world-writable"; + } else { + if (security_tpe_invert && !in_group_p(security_tpe_gid)) + msg2 = "not being in trusted group"; + else if (!security_tpe_invert && in_group_p(security_tpe_gid)) + msg2 = "being in untrusted group"; + else + return 1; + + if (tpe_is_global_nonroot(inode->i_uid)) + msg = "file in non-root-owned directory"; + else if (inode->i_mode & S_IWOTH) + msg = "file in world-writable directory"; + else if ((inode->i_mode & S_IWGRP) && tpe_is_global_nonroot_gid(inode->i_gid)) + msg = "file in group-writable directory"; + else if (file_inode->i_mode & S_IWOTH) + msg = "file is world-writable"; + } + + if (msg) { + char fullmsg[70] = {0}; + + if (msg2) + snprintf(fullmsg, sizeof(fullmsg)-1, "%s and %s", msg, msg2); + else + snprintf(fullmsg, sizeof(fullmsg)-1, "%s", msg); + + pr_warn_ratelimited("TPE: denied attempt to execute file Reason: %s\n", fullmsg); + return 0; + } + return 1; +} diff --git a/include/linux/fs.h b/include/linux/fs.h index 6c00f06d962cd..5c4671b2f6fa3 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -86,6 +86,12 @@ extern int sysctl_protected_hardlinks; extern int sysctl_protected_fifos; extern int sysctl_protected_regular; +extern int tpe_allow(const struct file *file); +extern int security_tpe; +extern int security_tpe_all; +extern int security_tpe_invert; +extern kgid_t security_tpe_gid; + typedef __kernel_rwf_t rwf_t; struct buffer_head; diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 36470990b2e6e..fab93acdd11e0 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -3407,6 +3407,41 @@ static struct ctl_table fs_table[] = { .proc_handler = proc_dointvec_minmax, .extra1 = SYSCTL_ONE, }, + { + .procname = "tpe", + .data = &security_tpe, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax_sysadmin, + .extra1 = SYSCTL_ZERO, + .extra2 = SYSCTL_ONE, + }, + { + .procname = "tpe_restrict_all", + .data = &security_tpe_all, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax_sysadmin, + .extra1 = SYSCTL_ZERO, + .extra2 = SYSCTL_ONE, + }, + { + .procname = "tpe_invert", + .data = &security_tpe_invert, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax_sysadmin, + .extra1 = SYSCTL_ZERO, + .extra2 = SYSCTL_ONE, + }, + { + .procname = "tpe_gid", + .data = &security_tpe_gid, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax_sysadmin, + .extra1 = SYSCTL_ZERO, + }, { } }; diff --git a/security/Kconfig b/security/Kconfig index ccae931a1c6cf..dd921d84441b5 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -42,6 +42,75 @@ config SECURITY_TIOCSTI_RESTRICT If you are unsure how to answer this question, answer N. +config SECURITY_TPE + bool "Trusted Path Execution (TPE)" + default n + help + If you say Y here, you will be able to choose a gid to add to the + supplementary groups of users you want to mark as "untrusted." + These users will not be able to execute any files that are not in + root-owned directories writable only by root. + + This setting can be overridden at runtime via the fs.enable_tpe + sysctl. + + If unsure, say N. + +config SECURITY_TPE_ALL + bool "Partially restrict all non-root users" + depends on SECURITY_TPE + help + If you say Y here, all non-root users will be covered under + a weaker TPE restriction. This is separate from, and in addition to, + the main TPE options that you have selected elsewhere. Thus, if a + "trusted" GID is chosen, this restriction applies to even that GID. + Under this restriction, all non-root users will only be allowed to + execute files in directories they own that are not group or + world-writable, or in directories owned by root and writable only by + root. + + This setting can be overridden at runtime via the fs.tpe_restrict_all + sysctl. + +config SECURITY_TPE_INVERT + bool "Invert GID option" + depends on SECURITY_TPE + help + If you say Y here, the group you specify in the TPE configuration will + decide what group TPE restrictions will be *disabled* for. This + option is useful if you want TPE restrictions to be applied to most + users on the system. + + This setting can be overridden at runtime via the fs.tpe_invert + sysctl. + +config SECURITY_TPE_GID + int + default SECURITY_TPE_UNTRUSTED_GID if (SECURITY_TPE && !SECURITY_TPE_INVERT) + default SECURITY_TPE_TRUSTED_GID if (SECURITY_TPE && SECURITY_TPE_INVERT) + +config SECURITY_TPE_UNTRUSTED_GID + int "GID for TPE-untrusted users" + depends on SECURITY_TPE && !SECURITY_TPE_INVERT + default 1005 + help + Setting this GID determines what group TPE restrictions will be + *enabled* for. + + This setting can be overridden at runtime via the fs.tpe_gid + sysctl. + +config SECURITY_TPE_TRUSTED_GID + int "GID for TPE-trusted users" + depends on SECURITY_TPE && SECURITY_TPE_INVERT + default 1005 + help + Setting this GID determines what group TPE restrictions will be + *disabled* for. + + This setting can be overridden at runtime via the fs.tpe_gid + sysctl. + config SECURITY bool "Enable different security models" depends on SYSFS