diff --git a/Documentation/x86/zero-page.txt b/Documentation/x86/zero-page.txt index 95a4d34af3fdd7..b8527c6b76461c 100644 --- a/Documentation/x86/zero-page.txt +++ b/Documentation/x86/zero-page.txt @@ -31,6 +31,8 @@ Offset Proto Name Meaning 1E9/001 ALL eddbuf_entries Number of entries in eddbuf (below) 1EA/001 ALL edd_mbr_sig_buf_entries Number of entries in edd_mbr_sig_buffer (below) +1EB/001 ALL kbd_status Numlock is enabled +1EC/001 ALL secure_boot Secure boot is enabled in the firmware 1EF/001 ALL sentinel Used to detect broken bootloaders 290/040 ALL edd_mbr_sig_buffer EDD MBR signatures 2D0/A00 ALL e820_map E820 memory map table diff --git a/Makefile b/Makefile index 66da9a38b13b77..4d55d3834bf0d0 100644 --- a/Makefile +++ b/Makefile @@ -147,7 +147,8 @@ $(filter-out _all sub-make $(CURDIR)/Makefile, $(MAKECMDGOALS)) _all: sub-make @: sub-make: - $(Q)$(MAKE) -C $(KBUILD_OUTPUT) KBUILD_SRC=$(CURDIR) \ + $(Q)$(MAKE) -C $(KBUILD_OUTPUT) \ + KBUILD_SRC=$(shell realpath --relative-to=$(KBUILD_OUTPUT) $(CURDIR)) \ -f $(CURDIR)/Makefile $(filter-out _all sub-make,$(MAKECMDGOALS)) # Leave processing to above invocation of make diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index d9a94da0c29fd7..5b8b8c30670e31 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -1776,6 +1776,17 @@ config EFI_MIXED If unsure, say N. +config EFI_SECURE_BOOT_SIG_ENFORCE + def_bool n + depends on EFI + prompt "Force module signing when UEFI Secure Boot is enabled" + ---help--- + UEFI Secure Boot provides a mechanism for ensuring that the + firmware will only load signed bootloaders and kernels. Certain + use cases may also require that all kernel modules also be signed. + Say Y here to automatically enable module signature enforcement + when a system boots with UEFI Secure Boot enabled. + config SECCOMP def_bool y prompt "Enable seccomp to safely compute untrusted bytecode" diff --git a/arch/x86/boot/compressed/eboot.c b/arch/x86/boot/compressed/eboot.c index 52fef606bc5425..faa223b000fa82 100644 --- a/arch/x86/boot/compressed/eboot.c +++ b/arch/x86/boot/compressed/eboot.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "../string.h" #include "eboot.h" @@ -571,6 +572,36 @@ static void setup_efi_pci(struct boot_params *params) efi_call_early(free_pool, pci_handle); } +static int get_secure_boot(void) +{ + u8 sb, setup; + unsigned long datasize = sizeof(sb); + efi_guid_t var_guid = EFI_GLOBAL_VARIABLE_GUID; + efi_status_t status; + + status = efi_early->call((unsigned long)sys_table->runtime->get_variable, + L"SecureBoot", &var_guid, NULL, &datasize, &sb); + + if (status != EFI_SUCCESS) + return 0; + + if (sb == 0) + return 0; + + + status = efi_early->call((unsigned long)sys_table->runtime->get_variable, + L"SetupMode", &var_guid, NULL, &datasize, + &setup); + + if (status != EFI_SUCCESS) + return 0; + + if (setup == 1) + return 0; + + return 1; +} + static efi_status_t setup_uga32(void **uga_handle, unsigned long size, u32 *width, u32 *height) { @@ -1126,6 +1157,10 @@ struct boot_params *efi_main(struct efi_config *c, else setup_boot_services32(efi_early); + sanitize_boot_params(boot_params); + + boot_params->secure_boot = get_secure_boot(); + setup_graphics(boot_params); setup_efi_pci(boot_params); diff --git a/arch/x86/include/uapi/asm/bootparam.h b/arch/x86/include/uapi/asm/bootparam.h index c18ce67495fadf..2b3e5427097b4f 100644 --- a/arch/x86/include/uapi/asm/bootparam.h +++ b/arch/x86/include/uapi/asm/bootparam.h @@ -134,7 +134,8 @@ struct boot_params { __u8 eddbuf_entries; /* 0x1e9 */ __u8 edd_mbr_sig_buf_entries; /* 0x1ea */ __u8 kbd_status; /* 0x1eb */ - __u8 _pad5[3]; /* 0x1ec */ + __u8 secure_boot; /* 0x1ec */ + __u8 _pad5[2]; /* 0x1ed */ /* * The sentinel is set to a nonzero value (0xff) in header.S. * diff --git a/arch/x86/kernel/ioport.c b/arch/x86/kernel/ioport.c index 589b3193f1020b..ab8372443efbf0 100644 --- a/arch/x86/kernel/ioport.c +++ b/arch/x86/kernel/ioport.c @@ -15,6 +15,7 @@ #include #include #include +#include #include /* @@ -28,7 +29,7 @@ asmlinkage long sys_ioperm(unsigned long from, unsigned long num, int turn_on) if ((from + num <= from) || (from + num > IO_BITMAP_BITS)) return -EINVAL; - if (turn_on && !capable(CAP_SYS_RAWIO)) + if (turn_on && (!capable(CAP_SYS_RAWIO) || secure_modules())) return -EPERM; /* @@ -108,7 +109,7 @@ SYSCALL_DEFINE1(iopl, unsigned int, level) return -EINVAL; /* Trying to gain more privileges? */ if (level > old) { - if (!capable(CAP_SYS_RAWIO)) + if (!capable(CAP_SYS_RAWIO) || secure_modules()) return -EPERM; } regs->flags = (regs->flags & ~X86_EFLAGS_IOPL) | diff --git a/arch/x86/kernel/msr.c b/arch/x86/kernel/msr.c index 7f3550acde1b8c..963ba40119234a 100644 --- a/arch/x86/kernel/msr.c +++ b/arch/x86/kernel/msr.c @@ -83,6 +83,9 @@ static ssize_t msr_write(struct file *file, const char __user *buf, int err = 0; ssize_t bytes = 0; + if (secure_modules()) + return -EPERM; + if (count % 8) return -EINVAL; /* Invalid chunk size */ @@ -130,6 +133,10 @@ static long msr_ioctl(struct file *file, unsigned int ioc, unsigned long arg) err = -EBADF; break; } + if (secure_modules()) { + err = -EPERM; + break; + } if (copy_from_user(®s, uregs, sizeof regs)) { err = -EFAULT; break; diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c index c4e7b3991b60d4..a666b6c29c777a 100644 --- a/arch/x86/kernel/setup.c +++ b/arch/x86/kernel/setup.c @@ -1152,6 +1152,14 @@ void __init setup_arch(char **cmdline_p) io_delay_init(); +#ifdef CONFIG_EFI_SECURE_BOOT_SIG_ENFORCE + if (boot_params.secure_boot) { + set_bit(EFI_SECURE_BOOT, &efi.flags); + enforce_signed_modules(); + pr_info("Secure boot enabled\n"); + } +#endif + /* * Parse the ACPI tables for possible boot-time SMP configuration. */ diff --git a/drivers/acpi/custom_method.c b/drivers/acpi/custom_method.c index c68e72414a67a9..4277938af700d1 100644 --- a/drivers/acpi/custom_method.c +++ b/drivers/acpi/custom_method.c @@ -29,6 +29,9 @@ static ssize_t cm_write(struct file *file, const char __user * user_buf, struct acpi_table_header table; acpi_status status; + if (secure_modules()) + return -EPERM; + if (!(*ppos)) { /* parse the table header to get the table length */ if (count <= sizeof(struct acpi_table_header)) diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index b108f1358a32b5..158de7d81bfd8a 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -184,7 +185,7 @@ early_param("acpi_rsdp", setup_acpi_rsdp); acpi_physical_address __init acpi_os_get_root_pointer(void) { #ifdef CONFIG_KEXEC - if (acpi_rsdp) + if (acpi_rsdp && !secure_modules()) return acpi_rsdp; #endif diff --git a/drivers/char/mem.c b/drivers/char/mem.c index 71025c2f6bbb07..3264735e3596be 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c @@ -27,6 +27,7 @@ #include #include #include +#include #include @@ -166,6 +167,9 @@ static ssize_t write_mem(struct file *file, const char __user *buf, if (p != *ppos) return -EFBIG; + if (secure_modules()) + return -EPERM; + if (!valid_phys_addr_range(p, count)) return -EFAULT; @@ -512,6 +516,9 @@ static ssize_t write_kmem(struct file *file, const char __user *buf, char *kbuf; /* k-addr because vwrite() takes vmlist_lock rwlock */ int err = 0; + if (secure_modules()) + return -EPERM; + if (p < (unsigned long) high_memory) { unsigned long to_write = min_t(unsigned long, count, (unsigned long)high_memory - p); @@ -577,6 +584,9 @@ static ssize_t write_port(struct file *file, const char __user *buf, unsigned long i = *ppos; const char __user *tmp = buf; + if (secure_modules()) + return -EPERM; + if (!access_ok(VERIFY_READ, buf, count)) return -EFAULT; while (count-- > 0 && i < 65536) { diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index d319a9ca9b7bf9..6b1884d1beb6e6 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "pci.h" static int sysfs_initialized; /* = 0 */ @@ -711,6 +712,9 @@ static ssize_t pci_write_config(struct file *filp, struct kobject *kobj, loff_t init_off = off; u8 *data = (u8 *) buf; + if (secure_modules()) + return -EPERM; + if (off > dev->cfg_size) return 0; if (off + count > dev->cfg_size) { @@ -1002,6 +1006,9 @@ static int pci_mmap_resource(struct kobject *kobj, struct bin_attribute *attr, resource_size_t start, end; int i; + if (secure_modules()) + return -EPERM; + for (i = 0; i < PCI_ROM_RESOURCE; i++) if (res == &pdev->resource[i]) break; @@ -1101,6 +1108,9 @@ static ssize_t pci_write_resource_io(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, char *buf, loff_t off, size_t count) { + if (secure_modules()) + return -EPERM; + return pci_resource_io(filp, kobj, attr, buf, off, count, true); } diff --git a/drivers/pci/proc.c b/drivers/pci/proc.c index 3f155e78513fd4..4265ea07e3b066 100644 --- a/drivers/pci/proc.c +++ b/drivers/pci/proc.c @@ -116,6 +116,9 @@ static ssize_t proc_bus_pci_write(struct file *file, const char __user *buf, int size = dev->cfg_size; int cnt; + if (secure_modules()) + return -EPERM; + if (pos >= size) return 0; if (nbytes >= size) @@ -195,6 +198,9 @@ static long proc_bus_pci_ioctl(struct file *file, unsigned int cmd, #endif /* HAVE_PCI_MMAP */ int ret = 0; + if (secure_modules()) + return -EPERM; + switch (cmd) { case PCIIOC_CONTROLLER: ret = pci_domain_nr(dev->bus); @@ -233,7 +239,7 @@ static int proc_bus_pci_mmap(struct file *file, struct vm_area_struct *vma) struct pci_filp_private *fpriv = file->private_data; int i, ret; - if (!capable(CAP_SYS_RAWIO)) + if (!capable(CAP_SYS_RAWIO) || secure_modules()) return -EPERM; /* Make sure the caller is mapping a real resource for this device */ diff --git a/drivers/pci/syscall.c b/drivers/pci/syscall.c index b91c4da6836574..98f5637304d1ba 100644 --- a/drivers/pci/syscall.c +++ b/drivers/pci/syscall.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "pci.h" @@ -92,7 +93,7 @@ SYSCALL_DEFINE5(pciconfig_write, unsigned long, bus, unsigned long, dfn, u32 dword; int err = 0; - if (!capable(CAP_SYS_ADMIN)) + if (!capable(CAP_SYS_ADMIN) || secure_modules()) return -EPERM; dev = pci_get_bus_and_slot(bus, dfn); diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index a26dca3640ea75..6ec9cabf5dfa75 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -1872,6 +1872,9 @@ static int show_dsts(struct seq_file *m, void *data) int err; u32 retval = -1; + if (secure_modules()) + return -EPERM; + err = asus_wmi_get_devstate(asus, asus->debug.dev_id, &retval); if (err < 0) @@ -1888,6 +1891,9 @@ static int show_devs(struct seq_file *m, void *data) int err; u32 retval = -1; + if (secure_modules()) + return -EPERM; + err = asus_wmi_set_devstate(asus->debug.dev_id, asus->debug.ctrl_param, &retval); @@ -1912,6 +1918,9 @@ static int show_call(struct seq_file *m, void *data) union acpi_object *obj; acpi_status status; + if (secure_modules()) + return -EPERM; + status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 1, asus->debug.method_id, &input, &output); diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 80aa6f1eb33699..c7ba7b26588b0e 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -102,6 +102,14 @@ int ovl_copy_xattr(struct dentry *old, struct dentry *new) value_size = size; goto retry; } + error = security_inode_copy_up_xattr(old, new, + name, value, &size); + if (error < 0) + break; + if (error == 1) { + error = 0; + continue; /* Discard */ + } error = vfs_setxattr(new, name, value, size, 0); if (error) @@ -265,6 +273,10 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, if (err) goto out2; + err = security_inode_copy_up(lowerpath->dentry, newdentry); + if (err < 0) + goto out_cleanup; + if (S_ISREG(stat->mode)) { struct path upperpath; diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index d1cdc60dd68fa2..a5b149833b444b 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -189,6 +189,9 @@ int ovl_permission(struct inode *inode, int mask) goto out_dput; } + if (!is_upper) + mask |= MAY_OPEN_LOWER; + err = __inode_permission(realinode, mask); out_dput: dput(alias); diff --git a/include/linux/efi.h b/include/linux/efi.h index f196dd0b0f2f6c..3b3909fd6531da 100644 --- a/include/linux/efi.h +++ b/include/linux/efi.h @@ -1062,6 +1062,7 @@ extern int __init efi_setup_pcdp_console(char *); #define EFI_ARCH_1 7 /* First arch-specific bit */ #define EFI_DBG 8 /* Print additional debug info at runtime */ #define EFI_NX_PE_DATA 9 /* Can runtime data regions be mapped non-executable? */ +#define EFI_SECURE_BOOT 10 /* Are we in Secure Boot mode? */ #ifdef CONFIG_EFI /* diff --git a/include/linux/fs.h b/include/linux/fs.h index dd288148a6b15f..59889963bbeb96 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -84,6 +84,7 @@ typedef int (dio_iodone_t)(struct kiocb *iocb, loff_t offset, #define MAY_CHDIR 0x00000040 /* called from RCU mode, don't block */ #define MAY_NOT_BLOCK 0x00000080 +#define MAY_OPEN_LOWER 0x00000100 /* * flags in file.f_mode. Note that FMODE_READ and FMODE_WRITE must correspond diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 7ae397669d8b62..b585466f065ff3 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -401,6 +401,24 @@ * @inode contains a pointer to the inode. * @secid contains a pointer to the location where result will be saved. * In case of failure, @secid will be set to zero. + * @inode_copy_up: + * Appropriately label the destination inode when a unioned file is copied + * up from a lower layer to the union/overlay layer. + * @src indicates the file that is being copied up. + * @dst indicates the file that has being created by the copy up. + * Returns 0 on success or a negative error code on error. + * @inode_copy_up_xattr: + * Filter/modify the xattrs being copied up when a unioned file is copied + * up from a lower layer to the union/overlay layer. + * @src indicates the file that is being copied up. + * @dst indicates the file that has being created by the copy up. + * @name indicates the name of the xattr. + * @value, *@size indicate the payload of the xattr. + * Returns 0 to accept the xattr, 1 to discard the xattr or a negative + * error code to abort the copy up. The xattr buffer must be at least + * XATTR_SIZE_MAX in capacity and the contents may be modified and *@size + * changed appropriately. Note that the caller is responsible for reading + * and writing the xattrs as this hook is merely a filter. * * Security hooks for file operations * @@ -1425,6 +1443,9 @@ union security_list_options { int (*inode_listsecurity)(struct inode *inode, char *buffer, size_t buffer_size); void (*inode_getsecid)(struct inode *inode, u32 *secid); + int (*inode_copy_up) (struct dentry *src, struct dentry *dst); + int (*inode_copy_up_xattr) (struct dentry *src, struct dentry *dst, + const char *name, void *value, size_t *size); int (*file_permission)(struct file *file, int mask); int (*file_alloc_security)(struct file *file); @@ -1696,6 +1717,8 @@ struct security_hook_heads { struct list_head inode_setsecurity; struct list_head inode_listsecurity; struct list_head inode_getsecid; + struct list_head inode_copy_up; + struct list_head inode_copy_up_xattr; struct list_head file_permission; struct list_head file_alloc_security; struct list_head file_free_security; diff --git a/include/linux/module.h b/include/linux/module.h index 3daf2b3a09d29b..fe5c49d6b8573f 100644 --- a/include/linux/module.h +++ b/include/linux/module.h @@ -273,6 +273,12 @@ const struct exception_table_entry *search_exception_tables(unsigned long add); struct notifier_block; +#ifdef CONFIG_MODULE_SIG +extern void enforce_signed_modules(void); +#else +static inline void enforce_signed_modules(void) {}; +#endif + #ifdef CONFIG_MODULES extern int modules_disabled; /* for sysctl */ @@ -643,6 +649,8 @@ static inline bool module_requested_async_probing(struct module *module) return module && module->async_probe_requested; } +extern bool secure_modules(void); + #ifdef CONFIG_LIVEPATCH static inline bool is_livepatch_module(struct module *mod) { @@ -771,6 +779,10 @@ static inline bool module_requested_async_probing(struct module *module) return false; } +static inline bool secure_modules(void) +{ + return false; +} #endif /* CONFIG_MODULES */ #ifdef CONFIG_SYSFS diff --git a/include/linux/security.h b/include/linux/security.h index 14df373ff2caf4..986265b2f5c94f 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -282,6 +282,10 @@ int security_inode_getsecurity(struct inode *inode, const char *name, void **buf int security_inode_setsecurity(struct inode *inode, const char *name, const void *value, size_t size, int flags); int security_inode_listsecurity(struct inode *inode, char *buffer, size_t buffer_size); void security_inode_getsecid(struct inode *inode, u32 *secid); +int security_inode_copy_up(struct dentry *src, struct dentry *dst); +int security_inode_copy_up_xattr(struct dentry *src, struct dentry *dst, + const char *name, void *value, size_t *size); + int security_file_permission(struct file *file, int mask); int security_file_alloc(struct file *file); void security_file_free(struct file *file); @@ -758,6 +762,16 @@ static inline void security_inode_getsecid(struct inode *inode, u32 *secid) *secid = 0; } +static inline int security_inode_copy_up(struct dentry *src, struct dentry *dst) +{ + return 0; +} +static inline int security_inode_copy_up_xattr(struct dentry *src, struct dentry *dst, + const char *name, const void *value, size_t *size) +{ + return 0; +} + static inline int security_file_permission(struct file *file, int mask) { return 0; diff --git a/kernel/kexec.c b/kernel/kexec.c index 4384672d324516..08767837da781a 100644 --- a/kernel/kexec.c +++ b/kernel/kexec.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "kexec_internal.h" @@ -189,7 +190,7 @@ SYSCALL_DEFINE4(kexec_load, unsigned long, entry, unsigned long, nr_segments, int result; /* We only trust the superuser with rebooting the system. */ - if (!capable(CAP_SYS_BOOT) || kexec_load_disabled) + if (!capable(CAP_SYS_BOOT) || kexec_load_disabled || secure_modules()) return -EPERM; /* diff --git a/kernel/module.c b/kernel/module.c index 5f71aa63ed2a55..ea484f3a35b2b8 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -4199,3 +4199,20 @@ void module_layout(struct module *mod, } EXPORT_SYMBOL(module_layout); #endif + +#ifdef CONFIG_MODULE_SIG +void enforce_signed_modules(void) +{ + sig_enforce = true; +} +#endif + +bool secure_modules(void) +{ +#ifdef CONFIG_MODULE_SIG + return (sig_enforce || modules_disabled); +#else + return modules_disabled; +#endif +} +EXPORT_SYMBOL(secure_modules); diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c index fca9254280ee4f..ffd8644078b2f1 100644 --- a/kernel/power/hibernate.c +++ b/kernel/power/hibernate.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include "power.h" @@ -66,7 +67,7 @@ static const struct platform_hibernation_ops *hibernation_ops; bool hibernation_available(void) { - return (nohibernate == 0); + return ((nohibernate == 0) && !secure_modules()); } /** diff --git a/security/security.c b/security/security.c index 709569305d329c..77ec85b4bebae2 100644 --- a/security/security.c +++ b/security/security.c @@ -727,6 +727,19 @@ void security_inode_getsecid(struct inode *inode, u32 *secid) call_void_hook(inode_getsecid, inode, secid); } +int security_inode_copy_up(struct dentry *src, struct dentry *dst) +{ + return call_int_hook(inode_copy_up, 0, src, dst); +} +EXPORT_SYMBOL(security_inode_copy_up); + +int security_inode_copy_up_xattr(struct dentry *src, struct dentry *dst, + const char *name, void *value, size_t *size) +{ + return call_int_hook(inode_copy_up_xattr, 0, src, dst, name, value, size); +} +EXPORT_SYMBOL(security_inode_copy_up_xattr); + int security_file_permission(struct file *file, int mask) { int ret; @@ -1663,6 +1676,10 @@ struct security_hook_heads security_hook_heads = { LIST_HEAD_INIT(security_hook_heads.inode_listsecurity), .inode_getsecid = LIST_HEAD_INIT(security_hook_heads.inode_getsecid), + .inode_copy_up = + LIST_HEAD_INIT(security_hook_heads.inode_copy_up), + .inode_copy_up_xattr = + LIST_HEAD_INIT(security_hook_heads.inode_copy_up_xattr), .file_permission = LIST_HEAD_INIT(security_hook_heads.file_permission), .file_alloc_security = diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index a86d537eb79b14..10081f74460326 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1755,6 +1755,7 @@ static int file_has_perm(const struct cred *cred, struct file *file, u32 av) { + struct inode_security_struct *isec; struct file_security_struct *fsec = file->f_security; struct inode *inode = file_inode(file); struct common_audit_data ad; @@ -1775,8 +1776,15 @@ static int file_has_perm(const struct cred *cred, /* av is zero if only checking access to the descriptor. */ rc = 0; - if (av) - rc = inode_has_perm(cred, inode, av, &ad); + if (av && likely(!IS_PRIVATE(inode))) { + if (fsec->union_isid) { + isec = inode->i_security; + rc = avc_has_perm(sid, fsec->union_isid, isec->sclass, + av, &ad); + } + if (!rc) + rc = inode_has_perm(cred, inode, av, &ad); + } out: return rc; @@ -2973,6 +2981,15 @@ static int selinux_inode_permission(struct inode *inode, int mask) u32 audited, denied; from_access = mask & MAY_ACCESS; + + /* + * If we're trying to open the lower layer of an overlay mount, don't + * worry about write or append permissions - these will be verified + * against the upper context + */ + if (mask & MAY_OPEN_LOWER) + mask &= ~(MAY_WRITE|MAY_APPEND); + mask &= (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND); /* No permission to check. Existence test. */ @@ -3270,6 +3287,24 @@ static void selinux_inode_getsecid(struct inode *inode, u32 *secid) *secid = isec->sid; } +static int selinux_inode_copy_up(struct dentry *src, struct dentry *dst) +{ + return 0; +} + +static int selinux_inode_copy_up_xattr(struct dentry *src, struct dentry *dst, + const char *name, void *value, + size_t *size) +{ + /* The copy_up hook above sets the initial context on an inode, but we + * don't then want to overwrite it by blindly copying all the lower + * xattrs up. Instead, we have to filter out SELinux-related xattrs. + */ + if (strcmp(name, XATTR_NAME_SELINUX) == 0) + return 1; /* Discard */ + return 0; +} + /* file security operations */ static int selinux_revalidate_file_permission(struct file *file, int mask) @@ -3585,10 +3620,72 @@ static int selinux_file_receive(struct file *file) return file_has_perm(cred, file, file_to_av(file)); } +/* + * We have a file opened on a unioned file system that falls through to a file + * on a lower layer. If there is a union inode, we try to get the label from + * that, otherwise we need to get it from the superblock. + * + * file->f_path points to the union layer and file->f_inode points to the lower + * layer. + */ +static int selinux_file_open_union(struct file *file, + struct file_security_struct *fsec, + const struct cred *cred) +{ + const struct superblock_security_struct *sbsec; + const struct inode_security_struct *isec, *dsec, *fisec; + const struct task_security_struct *tsec = current_security(); + struct common_audit_data ad; + struct dentry *union_dentry = file->f_path.dentry; + const struct inode *union_inode = d_inode(union_dentry); + const struct inode *lower_inode = file_inode(file); + struct dentry *dir; + int rc; + + sbsec = union_dentry->d_sb->s_security; + + if (union_inode) { + isec = union_inode->i_security; + fsec->union_isid = isec->sid; + } else if ((sbsec->flags & SE_SBINITIALIZED) && + (sbsec->behavior == SECURITY_FS_USE_MNTPOINT)) { + fsec->union_isid = sbsec->mntpoint_sid; + } else { + dir = dget_parent(union_dentry); + dsec = d_inode(dir)->i_security; + + rc = security_transition_sid( + tsec->sid, dsec->sid, + inode_mode_to_security_class(lower_inode->i_mode), + &union_dentry->d_name, + &fsec->union_isid); + dput(dir); + if (rc) { + pr_warn("%s: security_transition_sid failed, rc=%d (name=%pD)\n", + __func__, -rc, file); + return rc; + } + } + + /* We need to check that the union file is allowed to be opened as well + * as checking that the lower file is allowed to be opened. + */ + if (unlikely(IS_PRIVATE(lower_inode))) + return 0; + + ad.type = LSM_AUDIT_DATA_PATH; + ad.u.path = file->f_path; + + fisec = lower_inode->i_security; + return avc_has_perm(cred_sid(cred), fsec->union_isid, fisec->sclass, + open_file_to_av(file), &ad); +} + static int selinux_file_open(struct file *file, const struct cred *cred) { struct file_security_struct *fsec; struct inode_security_struct *isec; + int rc; fsec = file->f_security; isec = inode_security(file_inode(file)); @@ -3609,6 +3706,13 @@ static int selinux_file_open(struct file *file, const struct cred *cred) * new inode label or new policy. * This check is not redundant - do not remove. */ + + if (d_inode(file->f_path.dentry) != file->f_inode) { + rc = selinux_file_open_union(file, fsec, cred); + if (rc < 0) + return rc; + } + return file_path_has_perm(cred, file, open_file_to_av(file)); } @@ -6056,6 +6160,8 @@ static struct security_hook_list selinux_hooks[] = { LSM_HOOK_INIT(inode_setsecurity, selinux_inode_setsecurity), LSM_HOOK_INIT(inode_listsecurity, selinux_inode_listsecurity), LSM_HOOK_INIT(inode_getsecid, selinux_inode_getsecid), + LSM_HOOK_INIT(inode_copy_up, selinux_inode_copy_up), + LSM_HOOK_INIT(inode_copy_up_xattr, selinux_inode_copy_up_xattr), LSM_HOOK_INIT(file_permission, selinux_file_permission), LSM_HOOK_INIT(file_alloc_security, selinux_file_alloc_security), diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index c21e135460a5e9..1c23b90d21559f 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -59,6 +59,7 @@ struct file_security_struct { u32 sid; /* SID of open file description */ u32 fown_sid; /* SID of file owner (for SIGIO) */ u32 isid; /* SID of inode at the time of file open */ + u32 union_isid; /* SID of would-be inodes in union top (or 0) */ u32 pseqno; /* Policy seqno at the time of file open */ };