Browse Source

Merge branch 'next-lockdown' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/linux-security

Pull kernel lockdown mode from James Morris:
 "This is the latest iteration of the kernel lockdown patchset, from
  Matthew Garrett, David Howells and others.

  From the original description:

    This patchset introduces an optional kernel lockdown feature,
    intended to strengthen the boundary between UID 0 and the kernel.
    When enabled, various pieces of kernel functionality are restricted.
    Applications that rely on low-level access to either hardware or the
    kernel may cease working as a result - therefore this should not be
    enabled without appropriate evaluation beforehand.

    The majority of mainstream distributions have been carrying variants
    of this patchset for many years now, so there's value in providing a
    doesn't meet every distribution requirement, but gets us much closer
    to not requiring external patches.

  There are two major changes since this was last proposed for mainline:

   - Separating lockdown from EFI secure boot. Background discussion is
     covered here: https://lwn.net/Articles/751061/

   -  Implementation as an LSM, with a default stackable lockdown LSM
      module. This allows the lockdown feature to be policy-driven,
      rather than encoding an implicit policy within the mechanism.

  The new locked_down LSM hook is provided to allow LSMs to make a
  policy decision around whether kernel functionality that would allow
  tampering with or examining the runtime state of the kernel should be
  permitted.

  The included lockdown LSM provides an implementation with a simple
  policy intended for general purpose use. This policy provides a coarse
  level of granularity, controllable via the kernel command line:

    lockdown={integrity|confidentiality}

  Enable the kernel lockdown feature. If set to integrity, kernel features
  that allow userland to modify the running kernel are disabled. If set to
  confidentiality, kernel features that allow userland to extract
  confidential information from the kernel are also disabled.

  This may also be controlled via /sys/kernel/security/lockdown and
  overriden by kernel configuration.

  New or existing LSMs may implement finer-grained controls of the
  lockdown features. Refer to the lockdown_reason documentation in
  include/linux/security.h for details.

  The lockdown feature has had signficant design feedback and review
  across many subsystems. This code has been in linux-next for some
  weeks, with a few fixes applied along the way.

  Stephen Rothwell noted that commit 9d1f8be5cf ("bpf: Restrict bpf
  when kernel lockdown is in confidentiality mode") is missing a
  Signed-off-by from its author. Matthew responded that he is providing
  this under category (c) of the DCO"

* 'next-lockdown' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/linux-security: (31 commits)
  kexec: Fix file verification on S390
  security: constify some arrays in lockdown LSM
  lockdown: Print current->comm in restriction messages
  efi: Restrict efivar_ssdt_load when the kernel is locked down
  tracefs: Restrict tracefs when the kernel is locked down
  debugfs: Restrict debugfs when the kernel is locked down
  kexec: Allow kexec_file() with appropriate IMA policy when locked down
  lockdown: Lock down perf when in confidentiality mode
  bpf: Restrict bpf when kernel lockdown is in confidentiality mode
  lockdown: Lock down tracing and perf kprobes when in confidentiality mode
  lockdown: Lock down /proc/kcore
  x86/mmiotrace: Lock down the testmmiotrace module
  lockdown: Lock down module params that specify hardware parameters (eg. ioport)
  lockdown: Lock down TIOCSSERIAL
  lockdown: Prohibit PCMCIA CIS storage when the kernel is locked down
  acpi: Disable ACPI table override if the kernel is locked down
  acpi: Ignore acpi_rsdp kernel param when the kernel has been locked down
  ACPI: Limit access to custom_method when the kernel is locked down
  x86/msr: Restrict MSR access when the kernel is locked down
  x86: Lock down IO port access when the kernel is locked down
  ...
tags/v5.4-rc1
Linus Torvalds 1 month ago
parent
commit
aefcf2f4b5
58 changed files with 861 additions and 76 deletions
  1. 9
    0
      Documentation/admin-guide/kernel-parameters.txt
  2. 3
    3
      arch/arm64/Kconfig
  3. 1
    1
      arch/s390/Kconfig
  4. 2
    2
      arch/s390/kernel/kexec_elf.c
  5. 2
    2
      arch/s390/kernel/kexec_image.c
  6. 2
    2
      arch/s390/kernel/machine_kexec_file.c
  7. 15
    5
      arch/x86/Kconfig
  8. 13
    6
      arch/x86/boot/compressed/acpi.c
  9. 9
    0
      arch/x86/include/asm/acpi.h
  10. 2
    0
      arch/x86/include/asm/x86_init.h
  11. 5
    0
      arch/x86/kernel/acpi/boot.c
  12. 2
    2
      arch/x86/kernel/ima_arch.c
  13. 5
    2
      arch/x86/kernel/ioport.c
  14. 1
    0
      arch/x86/kernel/kexec-bzimage64.c
  15. 8
    0
      arch/x86/kernel/msr.c
  16. 1
    0
      arch/x86/kernel/x86_init.c
  17. 5
    0
      arch/x86/mm/testmmiotrace.c
  18. 3
    1
      crypto/asymmetric_keys/verify_pefile.c
  19. 6
    0
      drivers/acpi/custom_method.c
  20. 13
    1
      drivers/acpi/osl.c
  21. 6
    0
      drivers/acpi/tables.c
  22. 5
    2
      drivers/char/mem.c
  23. 6
    0
      drivers/firmware/efi/efi.c
  24. 16
    0
      drivers/pci/pci-sysfs.c
  25. 12
    2
      drivers/pci/proc.c
  26. 3
    1
      drivers/pci/syscall.c
  27. 5
    0
      drivers/pcmcia/cistpl.c
  28. 5
    0
      drivers/tty/serial/serial_core.c
  29. 30
    0
      fs/debugfs/file.c
  30. 30
    2
      fs/debugfs/inode.c
  31. 6
    0
      fs/proc/kcore.c
  32. 41
    1
      fs/tracefs/inode.c
  33. 7
    1
      include/asm-generic/vmlinux.lds.h
  34. 6
    0
      include/linux/acpi.h
  35. 9
    0
      include/linux/ima.h
  36. 2
    2
      include/linux/kexec.h
  37. 13
    0
      include/linux/lsm_hooks.h
  38. 59
    0
      include/linux/security.h
  39. 5
    0
      init/Kconfig
  40. 1
    0
      init/main.c
  41. 7
    0
      kernel/events/core.c
  42. 8
    0
      kernel/kexec.c
  43. 59
    9
      kernel/kexec_file.c
  44. 30
    7
      kernel/module.c
  45. 16
    5
      kernel/params.c
  46. 2
    1
      kernel/power/hibernate.c
  47. 10
    0
      kernel/trace/bpf_trace.c
  48. 5
    0
      kernel/trace/trace_kprobe.c
  49. 6
    5
      security/Kconfig
  50. 2
    0
      security/Makefile
  51. 1
    1
      security/integrity/ima/Kconfig
  52. 2
    0
      security/integrity/ima/ima.h
  53. 2
    2
      security/integrity/ima/ima_main.c
  54. 50
    0
      security/integrity/ima/ima_policy.c
  55. 47
    0
      security/lockdown/Kconfig
  56. 1
    0
      security/lockdown/Makefile
  57. 191
    0
      security/lockdown/lockdown.c
  58. 48
    8
      security/security.c

+ 9
- 0
Documentation/admin-guide/kernel-parameters.txt View File

@@ -2276,6 +2276,15 @@
lockd.nlm_udpport=M [NFS] Assign UDP port.
Format: <integer>

lockdown= [SECURITY]
{ integrity | confidentiality }
Enable the kernel lockdown feature. If set to
integrity, kernel features that allow userland to
modify the running kernel are disabled. If set to
confidentiality, kernel features that allow userland
to extract confidential information from the kernel
are also disabled.

locktorture.nreaders_stress= [KNL]
Set the number of locking read-acquisition kthreads.
Defaults to being automatically set based on the

+ 3
- 3
arch/arm64/Kconfig View File

@@ -982,7 +982,7 @@ config KEXEC_FILE
for kernel and initramfs as opposed to list of segments as
accepted by previous system call.

config KEXEC_VERIFY_SIG
config KEXEC_SIG
bool "Verify kernel signature during kexec_file_load() syscall"
depends on KEXEC_FILE
help
@@ -997,13 +997,13 @@ config KEXEC_VERIFY_SIG
config KEXEC_IMAGE_VERIFY_SIG
bool "Enable Image signature verification support"
default y
depends on KEXEC_VERIFY_SIG
depends on KEXEC_SIG
depends on EFI && SIGNED_PE_FILE_VERIFICATION
help
Enable Image signature verification support.

comment "Support for PE file signature verification disabled"
depends on KEXEC_VERIFY_SIG
depends on KEXEC_SIG
depends on !EFI || !SIGNED_PE_FILE_VERIFICATION

config CRASH_DUMP

+ 1
- 1
arch/s390/Kconfig View File

@@ -554,7 +554,7 @@ config ARCH_HAS_KEXEC_PURGATORY
def_bool y
depends on KEXEC_FILE

config KEXEC_VERIFY_SIG
config KEXEC_SIG
bool "Verify kernel signature during kexec_file_load() syscall"
depends on KEXEC_FILE && MODULE_SIG_FORMAT
help

+ 2
- 2
arch/s390/kernel/kexec_elf.c View File

@@ -130,7 +130,7 @@ static int s390_elf_probe(const char *buf, unsigned long len)
const struct kexec_file_ops s390_kexec_elf_ops = {
.probe = s390_elf_probe,
.load = s390_elf_load,
#ifdef CONFIG_KEXEC_VERIFY_SIG
#ifdef CONFIG_KEXEC_SIG
.verify_sig = s390_verify_sig,
#endif /* CONFIG_KEXEC_VERIFY_SIG */
#endif /* CONFIG_KEXEC_SIG */
};

+ 2
- 2
arch/s390/kernel/kexec_image.c View File

@@ -59,7 +59,7 @@ static int s390_image_probe(const char *buf, unsigned long len)
const struct kexec_file_ops s390_kexec_image_ops = {
.probe = s390_image_probe,
.load = s390_image_load,
#ifdef CONFIG_KEXEC_VERIFY_SIG
#ifdef CONFIG_KEXEC_SIG
.verify_sig = s390_verify_sig,
#endif /* CONFIG_KEXEC_VERIFY_SIG */
#endif /* CONFIG_KEXEC_SIG */
};

+ 2
- 2
arch/s390/kernel/machine_kexec_file.c View File

@@ -22,7 +22,7 @@ const struct kexec_file_ops * const kexec_file_loaders[] = {
NULL,
};

#ifdef CONFIG_KEXEC_VERIFY_SIG
#ifdef CONFIG_KEXEC_SIG
int s390_verify_sig(const char *kernel, unsigned long kernel_len)
{
const unsigned long marker_len = sizeof(MODULE_SIG_STRING) - 1;
@@ -68,7 +68,7 @@ int s390_verify_sig(const char *kernel, unsigned long kernel_len)
VERIFYING_MODULE_SIGNATURE,
NULL, NULL);
}
#endif /* CONFIG_KEXEC_VERIFY_SIG */
#endif /* CONFIG_KEXEC_SIG */

static int kexec_file_update_purgatory(struct kimage *image,
struct s390_load_data *data)

+ 15
- 5
arch/x86/Kconfig View File

@@ -2031,20 +2031,30 @@ config KEXEC_FILE
config ARCH_HAS_KEXEC_PURGATORY
def_bool KEXEC_FILE

config KEXEC_VERIFY_SIG
config KEXEC_SIG
bool "Verify kernel signature during kexec_file_load() syscall"
depends on KEXEC_FILE
---help---
This option makes kernel signature verification mandatory for
the kexec_file_load() syscall.

In addition to that option, you need to enable signature
This option makes the kexec_file_load() syscall check for a valid
signature of the kernel image. The image can still be loaded without
a valid signature unless you also enable KEXEC_SIG_FORCE, though if
there's a signature that we can check, then it must be valid.

In addition to this option, you need to enable signature
verification for the corresponding kernel image type being
loaded in order for this to work.

config KEXEC_SIG_FORCE
bool "Require a valid signature in kexec_file_load() syscall"
depends on KEXEC_SIG
---help---
This option makes kernel signature verification mandatory for
the kexec_file_load() syscall.

config KEXEC_BZIMAGE_VERIFY_SIG
bool "Enable bzImage signature verification support"
depends on KEXEC_VERIFY_SIG
depends on KEXEC_SIG
depends on SIGNED_PE_FILE_VERIFICATION
select SYSTEM_TRUSTED_KEYRING
---help---

+ 13
- 6
arch/x86/boot/compressed/acpi.c View File

@@ -26,7 +26,7 @@ struct mem_vector immovable_mem[MAX_NUMNODES*2];
*/
#define MAX_ADDR_LEN 19

static acpi_physical_address get_acpi_rsdp(void)
static acpi_physical_address get_cmdline_acpi_rsdp(void)
{
acpi_physical_address addr = 0;

@@ -278,10 +278,7 @@ acpi_physical_address get_rsdp_addr(void)
{
acpi_physical_address pa;

pa = get_acpi_rsdp();

if (!pa)
pa = boot_params->acpi_rsdp_addr;
pa = boot_params->acpi_rsdp_addr;

/*
* Try to get EFI data from setup_data. This can happen when we're a
@@ -311,7 +308,17 @@ static unsigned long get_acpi_srat_table(void)
char arg[10];
u8 *entry;

rsdp = (struct acpi_table_rsdp *)(long)boot_params->acpi_rsdp_addr;
/*
* Check whether we were given an RSDP on the command line. We don't
* stash this in boot params because the kernel itself may have
* different ideas about whether to trust a command-line parameter.
*/
rsdp = (struct acpi_table_rsdp *)get_cmdline_acpi_rsdp();

if (!rsdp)
rsdp = (struct acpi_table_rsdp *)(long)
boot_params->acpi_rsdp_addr;

if (!rsdp)
return 0;


+ 9
- 0
arch/x86/include/asm/acpi.h View File

@@ -117,6 +117,12 @@ static inline bool acpi_has_cpu_in_madt(void)
return !!acpi_lapic;
}

#define ACPI_HAVE_ARCH_SET_ROOT_POINTER
static inline void acpi_arch_set_root_pointer(u64 addr)
{
x86_init.acpi.set_root_pointer(addr);
}

#define ACPI_HAVE_ARCH_GET_ROOT_POINTER
static inline u64 acpi_arch_get_root_pointer(void)
{
@@ -125,6 +131,7 @@ static inline u64 acpi_arch_get_root_pointer(void)

void acpi_generic_reduced_hw_init(void);

void x86_default_set_root_pointer(u64 addr);
u64 x86_default_get_root_pointer(void);

#else /* !CONFIG_ACPI */
@@ -138,6 +145,8 @@ static inline void disable_acpi(void) { }

static inline void acpi_generic_reduced_hw_init(void) { }

static inline void x86_default_set_root_pointer(u64 addr) { }

static inline u64 x86_default_get_root_pointer(void)
{
return 0;

+ 2
- 0
arch/x86/include/asm/x86_init.h View File

@@ -134,10 +134,12 @@ struct x86_hyper_init {

/**
* struct x86_init_acpi - x86 ACPI init functions
* @set_root_poitner: set RSDP address
* @get_root_pointer: get RSDP address
* @reduced_hw_early_init: hardware reduced platform early init
*/
struct x86_init_acpi {
void (*set_root_pointer)(u64 addr);
u64 (*get_root_pointer)(void);
void (*reduced_hw_early_init)(void);
};

+ 5
- 0
arch/x86/kernel/acpi/boot.c View File

@@ -1760,6 +1760,11 @@ void __init arch_reserve_mem_area(acpi_physical_address addr, size_t size)
e820__update_table_print();
}

void x86_default_set_root_pointer(u64 addr)
{
boot_params.acpi_rsdp_addr = addr;
}

u64 x86_default_get_root_pointer(void)
{
return boot_params.acpi_rsdp_addr;

+ 2
- 2
arch/x86/kernel/ima_arch.c View File

@@ -74,9 +74,9 @@ bool arch_ima_get_secureboot(void)

/* secureboot arch rules */
static const char * const sb_arch_rules[] = {
#if !IS_ENABLED(CONFIG_KEXEC_VERIFY_SIG)
#if !IS_ENABLED(CONFIG_KEXEC_SIG)
"appraise func=KEXEC_KERNEL_CHECK appraise_type=imasig",
#endif /* CONFIG_KEXEC_VERIFY_SIG */
#endif /* CONFIG_KEXEC_SIG */
"measure func=KEXEC_KERNEL_CHECK",
#if !IS_ENABLED(CONFIG_MODULE_SIG)
"appraise func=MODULE_CHECK appraise_type=imasig",

+ 5
- 2
arch/x86/kernel/ioport.c View File

@@ -11,6 +11,7 @@
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/ioport.h>
#include <linux/security.h>
#include <linux/smp.h>
#include <linux/stddef.h>
#include <linux/slab.h>
@@ -31,7 +32,8 @@ long ksys_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) ||
security_locked_down(LOCKDOWN_IOPORT)))
return -EPERM;

/*
@@ -126,7 +128,8 @@ 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) ||
security_locked_down(LOCKDOWN_IOPORT))
return -EPERM;
}
regs->flags = (regs->flags & ~X86_EFLAGS_IOPL) |

+ 1
- 0
arch/x86/kernel/kexec-bzimage64.c View File

@@ -180,6 +180,7 @@ setup_efi_state(struct boot_params *params, unsigned long params_load_addr,
if (efi_enabled(EFI_OLD_MEMMAP))
return 0;

params->secure_boot = boot_params.secure_boot;
ei->efi_loader_signature = current_ei->efi_loader_signature;
ei->efi_systab = current_ei->efi_systab;
ei->efi_systab_hi = current_ei->efi_systab_hi;

+ 8
- 0
arch/x86/kernel/msr.c View File

@@ -34,6 +34,7 @@
#include <linux/notifier.h>
#include <linux/uaccess.h>
#include <linux/gfp.h>
#include <linux/security.h>

#include <asm/cpufeature.h>
#include <asm/msr.h>
@@ -79,6 +80,10 @@ static ssize_t msr_write(struct file *file, const char __user *buf,
int err = 0;
ssize_t bytes = 0;

err = security_locked_down(LOCKDOWN_MSR);
if (err)
return err;

if (count % 8)
return -EINVAL; /* Invalid chunk size */

@@ -130,6 +135,9 @@ static long msr_ioctl(struct file *file, unsigned int ioc, unsigned long arg)
err = -EFAULT;
break;
}
err = security_locked_down(LOCKDOWN_MSR);
if (err)
break;
err = wrmsr_safe_regs_on_cpu(cpu, regs);
if (err)
break;

+ 1
- 0
arch/x86/kernel/x86_init.c View File

@@ -95,6 +95,7 @@ struct x86_init_ops x86_init __initdata = {
},

.acpi = {
.set_root_pointer = x86_default_set_root_pointer,
.get_root_pointer = x86_default_get_root_pointer,
.reduced_hw_early_init = acpi_generic_reduced_hw_init,
},

+ 5
- 0
arch/x86/mm/testmmiotrace.c View File

@@ -8,6 +8,7 @@
#include <linux/module.h>
#include <linux/io.h>
#include <linux/mmiotrace.h>
#include <linux/security.h>

static unsigned long mmio_address;
module_param_hw(mmio_address, ulong, iomem, 0);
@@ -115,6 +116,10 @@ static void do_test_bulk_ioremapping(void)
static int __init init(void)
{
unsigned long size = (read_far) ? (8 << 20) : (16 << 10);
int ret = security_locked_down(LOCKDOWN_MMIOTRACE);

if (ret)
return ret;

if (mmio_address == 0) {
pr_err("you have to use the module argument mmio_address.\n");

+ 3
- 1
crypto/asymmetric_keys/verify_pefile.c View File

@@ -96,7 +96,7 @@ static int pefile_parse_binary(const void *pebuf, unsigned int pelen,

if (!ddir->certs.virtual_address || !ddir->certs.size) {
pr_debug("Unsigned PE binary\n");
return -EKEYREJECTED;
return -ENODATA;
}

chkaddr(ctx->header_size, ddir->certs.virtual_address,
@@ -403,6 +403,8 @@ error_no_desc:
* (*) 0 if at least one signature chain intersects with the keys in the trust
* keyring, or:
*
* (*) -ENODATA if there is no signature present.
*
* (*) -ENOPKG if a suitable crypto module couldn't be found for a check on a
* chain.
*

+ 6
- 0
drivers/acpi/custom_method.c View File

@@ -9,6 +9,7 @@
#include <linux/uaccess.h>
#include <linux/debugfs.h>
#include <linux/acpi.h>
#include <linux/security.h>

#include "internal.h"

@@ -29,6 +30,11 @@ static ssize_t cm_write(struct file *file, const char __user * user_buf,

struct acpi_table_header table;
acpi_status status;
int ret;

ret = security_locked_down(LOCKDOWN_ACPI_TABLES);
if (ret)
return ret;

if (!(*ppos)) {
/* parse the table header to get the table length */

+ 13
- 1
drivers/acpi/osl.c View File

@@ -27,6 +27,7 @@
#include <linux/list.h>
#include <linux/jiffies.h>
#include <linux/semaphore.h>
#include <linux/security.h>

#include <asm/io.h>
#include <linux/uaccess.h>
@@ -182,8 +183,19 @@ acpi_physical_address __init acpi_os_get_root_pointer(void)
acpi_physical_address pa;

#ifdef CONFIG_KEXEC
if (acpi_rsdp)
/*
* We may have been provided with an RSDP on the command line,
* but if a malicious user has done so they may be pointing us
* at modified ACPI tables that could alter kernel behaviour -
* so, we check the lockdown status before making use of
* it. If we trust it then also stash it in an architecture
* specific location (if appropriate) so it can be carried
* over further kexec()s.
*/
if (acpi_rsdp && !security_locked_down(LOCKDOWN_ACPI_TABLES)) {
acpi_arch_set_root_pointer(acpi_rsdp);
return acpi_rsdp;
}
#endif
pa = acpi_arch_get_root_pointer();
if (pa)

+ 6
- 0
drivers/acpi/tables.c View File

@@ -20,6 +20,7 @@
#include <linux/memblock.h>
#include <linux/earlycpio.h>
#include <linux/initrd.h>
#include <linux/security.h>
#include "internal.h"

#ifdef CONFIG_ACPI_CUSTOM_DSDT
@@ -578,6 +579,11 @@ void __init acpi_table_upgrade(void)
if (table_nr == 0)
return;

if (security_locked_down(LOCKDOWN_ACPI_TABLES)) {
pr_notice("kernel is locked down, ignoring table override\n");
return;
}

acpi_tables_addr =
memblock_find_in_range(0, ACPI_TABLE_UPGRADE_MAX_PHYS,
all_tables_size, PAGE_SIZE);

+ 5
- 2
drivers/char/mem.c View File

@@ -29,8 +29,8 @@
#include <linux/export.h>
#include <linux/io.h>
#include <linux/uio.h>

#include <linux/uaccess.h>
#include <linux/security.h>

#ifdef CONFIG_IA64
# include <linux/efi.h>
@@ -807,7 +807,10 @@ static loff_t memory_lseek(struct file *file, loff_t offset, int orig)

static int open_port(struct inode *inode, struct file *filp)
{
return capable(CAP_SYS_RAWIO) ? 0 : -EPERM;
if (!capable(CAP_SYS_RAWIO))
return -EPERM;

return security_locked_down(LOCKDOWN_DEV_MEM);
}

#define zero_lseek null_lseek

+ 6
- 0
drivers/firmware/efi/efi.c View File

@@ -30,6 +30,7 @@
#include <linux/acpi.h>
#include <linux/ucs2_string.h>
#include <linux/memblock.h>
#include <linux/security.h>

#include <asm/early_ioremap.h>

@@ -221,6 +222,11 @@ static void generic_ops_unregister(void)
static char efivar_ssdt[EFIVAR_SSDT_NAME_MAX] __initdata;
static int __init efivar_ssdt_setup(char *str)
{
int ret = security_locked_down(LOCKDOWN_ACPI_TABLES);

if (ret)
return ret;

if (strlen(str) < sizeof(efivar_ssdt))
memcpy(efivar_ssdt, str, strlen(str));
else

+ 16
- 0
drivers/pci/pci-sysfs.c View File

@@ -755,6 +755,11 @@ static ssize_t pci_write_config(struct file *filp, struct kobject *kobj,
unsigned int size = count;
loff_t init_off = off;
u8 *data = (u8 *) buf;
int ret;

ret = security_locked_down(LOCKDOWN_PCI_ACCESS);
if (ret)
return ret;

if (off > dev->cfg_size)
return 0;
@@ -1016,6 +1021,11 @@ static int pci_mmap_resource(struct kobject *kobj, struct bin_attribute *attr,
int bar = (unsigned long)attr->private;
enum pci_mmap_state mmap_type;
struct resource *res = &pdev->resource[bar];
int ret;

ret = security_locked_down(LOCKDOWN_PCI_ACCESS);
if (ret)
return ret;

if (res->flags & IORESOURCE_MEM && iomem_is_exclusive(res->start))
return -EINVAL;
@@ -1092,6 +1102,12 @@ 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)
{
int ret;

ret = security_locked_down(LOCKDOWN_PCI_ACCESS);
if (ret)
return ret;

return pci_resource_io(filp, kobj, attr, buf, off, count, true);
}


+ 12
- 2
drivers/pci/proc.c View File

@@ -13,6 +13,7 @@
#include <linux/seq_file.h>
#include <linux/capability.h>
#include <linux/uaccess.h>
#include <linux/security.h>
#include <asm/byteorder.h>
#include "pci.h"

@@ -115,7 +116,11 @@ static ssize_t proc_bus_pci_write(struct file *file, const char __user *buf,
struct pci_dev *dev = PDE_DATA(ino);
int pos = *ppos;
int size = dev->cfg_size;
int cnt;
int cnt, ret;

ret = security_locked_down(LOCKDOWN_PCI_ACCESS);
if (ret)
return ret;

if (pos >= size)
return 0;
@@ -196,6 +201,10 @@ static long proc_bus_pci_ioctl(struct file *file, unsigned int cmd,
#endif /* HAVE_PCI_MMAP */
int ret = 0;

ret = security_locked_down(LOCKDOWN_PCI_ACCESS);
if (ret)
return ret;

switch (cmd) {
case PCIIOC_CONTROLLER:
ret = pci_domain_nr(dev->bus);
@@ -238,7 +247,8 @@ 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, write_combine = 0, res_bit = IORESOURCE_MEM;

if (!capable(CAP_SYS_RAWIO))
if (!capable(CAP_SYS_RAWIO) ||
security_locked_down(LOCKDOWN_PCI_ACCESS))
return -EPERM;

if (fpriv->mmap_state == pci_mmap_io) {

+ 3
- 1
drivers/pci/syscall.c View File

@@ -7,6 +7,7 @@

#include <linux/errno.h>
#include <linux/pci.h>
#include <linux/security.h>
#include <linux/syscalls.h>
#include <linux/uaccess.h>
#include "pci.h"
@@ -90,7 +91,8 @@ 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) ||
security_locked_down(LOCKDOWN_PCI_ACCESS))
return -EPERM;

dev = pci_get_domain_bus_and_slot(0, bus, dfn);

+ 5
- 0
drivers/pcmcia/cistpl.c View File

@@ -21,6 +21,7 @@
#include <linux/pci.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/security.h>
#include <asm/byteorder.h>
#include <asm/unaligned.h>

@@ -1575,6 +1576,10 @@ static ssize_t pccard_store_cis(struct file *filp, struct kobject *kobj,
struct pcmcia_socket *s;
int error;

error = security_locked_down(LOCKDOWN_PCMCIA_CIS);
if (error)
return error;

s = to_socket(container_of(kobj, struct device, kobj));

if (off)

+ 5
- 0
drivers/tty/serial/serial_core.c View File

@@ -22,6 +22,7 @@
#include <linux/serial_core.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/security.h>

#include <linux/irq.h>
#include <linux/uaccess.h>
@@ -862,6 +863,10 @@ static int uart_set_info(struct tty_struct *tty, struct tty_port *port,
goto check_and_exit;
}

retval = security_locked_down(LOCKDOWN_TIOCSSERIAL);
if (retval && (change_irq || change_port))
goto exit;

/*
* Ask the low level driver to verify the settings.
*/

+ 30
- 0
fs/debugfs/file.c View File

@@ -19,6 +19,7 @@
#include <linux/atomic.h>
#include <linux/device.h>
#include <linux/poll.h>
#include <linux/security.h>

#include "internal.h"

@@ -136,6 +137,25 @@ void debugfs_file_put(struct dentry *dentry)
}
EXPORT_SYMBOL_GPL(debugfs_file_put);

/*
* Only permit access to world-readable files when the kernel is locked down.
* We also need to exclude any file that has ways to write or alter it as root
* can bypass the permissions check.
*/
static bool debugfs_is_locked_down(struct inode *inode,
struct file *filp,
const struct file_operations *real_fops)
{
if ((inode->i_mode & 07777) == 0444 &&
!(filp->f_mode & FMODE_WRITE) &&
!real_fops->unlocked_ioctl &&
!real_fops->compat_ioctl &&
!real_fops->mmap)
return false;

return security_locked_down(LOCKDOWN_DEBUGFS);
}

static int open_proxy_open(struct inode *inode, struct file *filp)
{
struct dentry *dentry = F_DENTRY(filp);
@@ -147,6 +167,11 @@ static int open_proxy_open(struct inode *inode, struct file *filp)
return r == -EIO ? -ENOENT : r;

real_fops = debugfs_real_fops(filp);

r = debugfs_is_locked_down(inode, filp, real_fops);
if (r)
goto out;

real_fops = fops_get(real_fops);
if (!real_fops) {
/* Huh? Module did not clean up after itself at exit? */
@@ -272,6 +297,11 @@ static int full_proxy_open(struct inode *inode, struct file *filp)
return r == -EIO ? -ENOENT : r;

real_fops = debugfs_real_fops(filp);

r = debugfs_is_locked_down(inode, filp, real_fops);
if (r)
goto out;

real_fops = fops_get(real_fops);
if (!real_fops) {
/* Huh? Module did not cleanup after itself at exit? */

+ 30
- 2
fs/debugfs/inode.c View File

@@ -26,6 +26,7 @@
#include <linux/parser.h>
#include <linux/magic.h>
#include <linux/slab.h>
#include <linux/security.h>

#include "internal.h"

@@ -35,6 +36,32 @@ static struct vfsmount *debugfs_mount;
static int debugfs_mount_count;
static bool debugfs_registered;

/*
* Don't allow access attributes to be changed whilst the kernel is locked down
* so that we can use the file mode as part of a heuristic to determine whether
* to lock down individual files.
*/
static int debugfs_setattr(struct dentry *dentry, struct iattr *ia)
{
int ret = security_locked_down(LOCKDOWN_DEBUGFS);

if (ret && (ia->ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID)))
return ret;
return simple_setattr(dentry, ia);
}

static const struct inode_operations debugfs_file_inode_operations = {
.setattr = debugfs_setattr,
};
static const struct inode_operations debugfs_dir_inode_operations = {
.lookup = simple_lookup,
.setattr = debugfs_setattr,
};
static const struct inode_operations debugfs_symlink_inode_operations = {
.get_link = simple_get_link,
.setattr = debugfs_setattr,
};

static struct inode *debugfs_get_inode(struct super_block *sb)
{
struct inode *inode = new_inode(sb);
@@ -369,6 +396,7 @@ static struct dentry *__debugfs_create_file(const char *name, umode_t mode,
inode->i_mode = mode;
inode->i_private = data;

inode->i_op = &debugfs_file_inode_operations;
inode->i_fop = proxy_fops;
dentry->d_fsdata = (void *)((unsigned long)real_fops |
DEBUGFS_FSDATA_IS_REAL_FOPS_BIT);
@@ -532,7 +560,7 @@ struct dentry *debugfs_create_dir(const char *name, struct dentry *parent)
}

inode->i_mode = S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO;
inode->i_op = &simple_dir_inode_operations;
inode->i_op = &debugfs_dir_inode_operations;
inode->i_fop = &simple_dir_operations;

/* directory inodes start off with i_nlink == 2 (for "." entry) */
@@ -632,7 +660,7 @@ struct dentry *debugfs_create_symlink(const char *name, struct dentry *parent,
return failed_creating(dentry);
}
inode->i_mode = S_IFLNK | S_IRWXUGO;
inode->i_op = &simple_symlink_inode_operations;
inode->i_op = &debugfs_symlink_inode_operations;
inode->i_link = link;
d_instantiate(dentry, inode);
return end_creating(dentry);

+ 6
- 0
fs/proc/kcore.c View File

@@ -31,6 +31,7 @@
#include <linux/ioport.h>
#include <linux/memory.h>
#include <linux/sched/task.h>
#include <linux/security.h>
#include <asm/sections.h>
#include "internal.h"

@@ -545,9 +546,14 @@ out:

static int open_kcore(struct inode *inode, struct file *filp)
{
int ret = security_locked_down(LOCKDOWN_KCORE);

if (!capable(CAP_SYS_RAWIO))
return -EPERM;

if (ret)
return ret;

filp->private_data = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!filp->private_data)
return -ENOMEM;

+ 41
- 1
fs/tracefs/inode.c View File

@@ -20,6 +20,7 @@
#include <linux/parser.h>
#include <linux/magic.h>
#include <linux/slab.h>
#include <linux/security.h>

#define TRACEFS_DEFAULT_MODE 0700

@@ -27,6 +28,25 @@ static struct vfsmount *tracefs_mount;
static int tracefs_mount_count;
static bool tracefs_registered;

static int default_open_file(struct inode *inode, struct file *filp)
{
struct dentry *dentry = filp->f_path.dentry;
struct file_operations *real_fops;
int ret;

if (!dentry)
return -EINVAL;

ret = security_locked_down(LOCKDOWN_TRACEFS);
if (ret)
return ret;

real_fops = dentry->d_fsdata;
if (!real_fops->open)
return 0;
return real_fops->open(inode, filp);
}

static ssize_t default_read_file(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
@@ -221,6 +241,12 @@ static int tracefs_apply_options(struct super_block *sb)
return 0;
}

static void tracefs_destroy_inode(struct inode *inode)
{
if (S_ISREG(inode->i_mode))
kfree(inode->i_fop);
}

static int tracefs_remount(struct super_block *sb, int *flags, char *data)
{
int err;
@@ -257,6 +283,7 @@ static int tracefs_show_options(struct seq_file *m, struct dentry *root)
static const struct super_operations tracefs_super_operations = {
.statfs = simple_statfs,
.remount_fs = tracefs_remount,
.destroy_inode = tracefs_destroy_inode,
.show_options = tracefs_show_options,
};

@@ -387,6 +414,7 @@ struct dentry *tracefs_create_file(const char *name, umode_t mode,
struct dentry *parent, void *data,
const struct file_operations *fops)
{
struct file_operations *proxy_fops;
struct dentry *dentry;
struct inode *inode;

@@ -402,8 +430,20 @@ struct dentry *tracefs_create_file(const char *name, umode_t mode,
if (unlikely(!inode))
return failed_creating(dentry);

proxy_fops = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
if (unlikely(!proxy_fops)) {
iput(inode);
return failed_creating(dentry);
}

if (!fops)
fops = &tracefs_file_operations;

dentry->d_fsdata = (void *)fops;
memcpy(proxy_fops, fops, sizeof(*proxy_fops));
proxy_fops->open = default_open_file;
inode->i_mode = mode;
inode->i_fop = fops ? fops : &tracefs_file_operations;
inode->i_fop = proxy_fops;
inode->i_private = data;
d_instantiate(dentry, inode);
fsnotify_create(dentry->d_parent->d_inode, dentry);

+ 7
- 1
include/asm-generic/vmlinux.lds.h View File

@@ -215,8 +215,13 @@
__start_lsm_info = .; \
KEEP(*(.lsm_info.init)) \
__end_lsm_info = .;
#define EARLY_LSM_TABLE() . = ALIGN(8); \
__start_early_lsm_info = .; \
KEEP(*(.early_lsm_info.init)) \
__end_early_lsm_info = .;
#else
#define LSM_TABLE()
#define EARLY_LSM_TABLE()
#endif

#define ___OF_TABLE(cfg, name) _OF_TABLE_##cfg(name)
@@ -627,7 +632,8 @@
ACPI_PROBE_TABLE(timer) \
THERMAL_TABLE(governor) \
EARLYCON_TABLE() \
LSM_TABLE()
LSM_TABLE() \
EARLY_LSM_TABLE()

#define INIT_TEXT \
*(.init.text .init.text.*) \

+ 6
- 0
include/linux/acpi.h View File

@@ -643,6 +643,12 @@ bool acpi_gtdt_c3stop(int type);
int acpi_arch_timer_mem_init(struct arch_timer_mem *timer_mem, int *timer_count);
#endif

#ifndef ACPI_HAVE_ARCH_SET_ROOT_POINTER
static inline void acpi_arch_set_root_pointer(u64 addr)
{
}
#endif

#ifndef ACPI_HAVE_ARCH_GET_ROOT_POINTER
static inline u64 acpi_arch_get_root_pointer(void)
{

+ 9
- 0
include/linux/ima.h View File

@@ -131,4 +131,13 @@ static inline int ima_inode_removexattr(struct dentry *dentry,
return 0;
}
#endif /* CONFIG_IMA_APPRAISE */

#if defined(CONFIG_IMA_APPRAISE) && defined(CONFIG_INTEGRITY_TRUSTED_KEYRING)
extern bool ima_appraise_signature(enum kernel_read_file_id func);
#else
static inline bool ima_appraise_signature(enum kernel_read_file_id func)
{
return false;
}
#endif /* CONFIG_IMA_APPRAISE && CONFIG_INTEGRITY_TRUSTED_KEYRING */
#endif /* _LINUX_IMA_H */

+ 2
- 2
include/linux/kexec.h View File

@@ -125,7 +125,7 @@ typedef void *(kexec_load_t)(struct kimage *image, char *kernel_buf,
unsigned long cmdline_len);
typedef int (kexec_cleanup_t)(void *loader_data);

#ifdef CONFIG_KEXEC_VERIFY_SIG
#ifdef CONFIG_KEXEC_SIG
typedef int (kexec_verify_sig_t)(const char *kernel_buf,
unsigned long kernel_len);
#endif
@@ -134,7 +134,7 @@ struct kexec_file_ops {
kexec_probe_t *probe;
kexec_load_t *load;
kexec_cleanup_t *cleanup;
#ifdef CONFIG_KEXEC_VERIFY_SIG
#ifdef CONFIG_KEXEC_SIG
kexec_verify_sig_t *verify_sig;
#endif
};

+ 13
- 0
include/linux/lsm_hooks.h View File

@@ -1449,6 +1449,11 @@
* @bpf_prog_free_security:
* Clean up the security information stored inside bpf prog.
*
* @locked_down
* Determine whether a kernel feature that potentially enables arbitrary
* code execution in kernel space should be permitted.
*
* @what: kernel feature being accessed
*/
union security_list_options {
int (*binder_set_context_mgr)(struct task_struct *mgr);
@@ -1812,6 +1817,7 @@ union security_list_options {
int (*bpf_prog_alloc_security)(struct bpf_prog_aux *aux);
void (*bpf_prog_free_security)(struct bpf_prog_aux *aux);
#endif /* CONFIG_BPF_SYSCALL */
int (*locked_down)(enum lockdown_reason what);
};

struct security_hook_heads {
@@ -2053,6 +2059,7 @@ struct security_hook_heads {
struct hlist_head bpf_prog_alloc_security;
struct hlist_head bpf_prog_free_security;
#endif /* CONFIG_BPF_SYSCALL */
struct hlist_head locked_down;
} __randomize_layout;

/*
@@ -2111,12 +2118,18 @@ struct lsm_info {
};

extern struct lsm_info __start_lsm_info[], __end_lsm_info[];
extern struct lsm_info __start_early_lsm_info[], __end_early_lsm_info[];

#define DEFINE_LSM(lsm) \
static struct lsm_info __lsm_##lsm \
__used __section(.lsm_info.init) \
__aligned(sizeof(unsigned long))

#define DEFINE_EARLY_LSM(lsm) \
static struct lsm_info __early_lsm_##lsm \
__used __section(.early_lsm_info.init) \
__aligned(sizeof(unsigned long))

#ifdef CONFIG_SECURITY_SELINUX_DISABLE
/*
* Assuring the safety of deleting a security module is up to

+ 59
- 0
include/linux/security.h View File

@@ -77,6 +77,54 @@ enum lsm_event {
LSM_POLICY_CHANGE,
};

/*
* These are reasons that can be passed to the security_locked_down()
* LSM hook. Lockdown reasons that protect kernel integrity (ie, the
* ability for userland to modify kernel code) are placed before
* LOCKDOWN_INTEGRITY_MAX. Lockdown reasons that protect kernel
* confidentiality (ie, the ability for userland to extract
* information from the running kernel that would otherwise be
* restricted) are placed before LOCKDOWN_CONFIDENTIALITY_MAX.
*
* LSM authors should note that the semantics of any given lockdown
* reason are not guaranteed to be stable - the same reason may block
* one set of features in one kernel release, and a slightly different
* set of features in a later kernel release. LSMs that seek to expose
* lockdown policy at any level of granularity other than "none",
* "integrity" or "confidentiality" are responsible for either
* ensuring that they expose a consistent level of functionality to
* userland, or ensuring that userland is aware that this is
* potentially a moving target. It is easy to misuse this information
* in a way that could break userspace. Please be careful not to do
* so.
*
* If you add to this, remember to extend lockdown_reasons in
* security/lockdown/lockdown.c.
*/
enum lockdown_reason {
LOCKDOWN_NONE,
LOCKDOWN_MODULE_SIGNATURE,
LOCKDOWN_DEV_MEM,
LOCKDOWN_KEXEC,
LOCKDOWN_HIBERNATION,
LOCKDOWN_PCI_ACCESS,
LOCKDOWN_IOPORT,
LOCKDOWN_MSR,
LOCKDOWN_ACPI_TABLES,
LOCKDOWN_PCMCIA_CIS,
LOCKDOWN_TIOCSSERIAL,
LOCKDOWN_MODULE_PARAMETERS,
LOCKDOWN_MMIOTRACE,
LOCKDOWN_DEBUGFS,
LOCKDOWN_INTEGRITY_MAX,
LOCKDOWN_KCORE,
LOCKDOWN_KPROBES,
LOCKDOWN_BPF_READ,
LOCKDOWN_PERF,
LOCKDOWN_TRACEFS,
LOCKDOWN_CONFIDENTIALITY_MAX,
};

/* These functions are in security/commoncap.c */
extern int cap_capable(const struct cred *cred, struct user_namespace *ns,
int cap, unsigned int opts);
@@ -195,6 +243,7 @@ int unregister_blocking_lsm_notifier(struct notifier_block *nb);

/* prototypes */
extern int security_init(void);
extern int early_security_init(void);

/* Security operations */
int security_binder_set_context_mgr(struct task_struct *mgr);
@@ -392,6 +441,7 @@ void security_inode_invalidate_secctx(struct inode *inode);
int security_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen);
int security_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen);
int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen);
int security_locked_down(enum lockdown_reason what);
#else /* CONFIG_SECURITY */

static inline int call_blocking_lsm_notifier(enum lsm_event event, void *data)
@@ -423,6 +473,11 @@ static inline int security_init(void)
return 0;
}

static inline int early_security_init(void)
{
return 0;
}

static inline int security_binder_set_context_mgr(struct task_struct *mgr)
{
return 0;
@@ -1210,6 +1265,10 @@ static inline int security_inode_getsecctx(struct inode *inode, void **ctx, u32
{
return -EOPNOTSUPP;
}
static inline int security_locked_down(enum lockdown_reason what)
{
return 0;
}
#endif /* CONFIG_SECURITY */

#ifdef CONFIG_SECURITY_NETWORK

+ 5
- 0
init/Kconfig View File

@@ -2061,6 +2061,11 @@ config MODULE_SIG
kernel build dependency so that the signing tool can use its crypto
library.

You should enable this option if you wish to use either
CONFIG_SECURITY_LOCKDOWN_LSM or lockdown functionality imposed via
another LSM - otherwise unsigned modules will be loadable regardless
of the lockdown policy.

!!!WARNING!!! If you enable this option, you MUST make sure that the
module DOES NOT get stripped after being signed. This includes the
debuginfo strip done by some packagers (such as rpmbuild) and

+ 1
- 0
init/main.c View File

@@ -593,6 +593,7 @@ asmlinkage __visible void __init start_kernel(void)
boot_cpu_init();
page_address_init();
pr_notice("%s", linux_banner);
early_security_init();
setup_arch(&command_line);
setup_command_line(command_line);
setup_nr_cpu_ids();

+ 7
- 0
kernel/events/core.c View File

@@ -10917,6 +10917,13 @@ SYSCALL_DEFINE5(perf_event_open,
perf_paranoid_kernel() && !capable(CAP_SYS_ADMIN))
return -EACCES;

err = security_locked_down(LOCKDOWN_PERF);
if (err && (attr.sample_type & PERF_SAMPLE_REGS_INTR))
/* REGS_INTR can leak data, lockdown must prevent this */
return err;

err = 0;

/*
* In cgroup mode, the pid argument is used to pass the fd
* opened to the cgroup directory in cgroupfs. The cpu argument

+ 8
- 0
kernel/kexec.c View File

@@ -205,6 +205,14 @@ static inline int kexec_load_check(unsigned long nr_segments,
if (result < 0)
return result;

/*
* kexec can be used to circumvent module loading restrictions, so
* prevent loading in that case
*/
result = security_locked_down(LOCKDOWN_KEXEC);
if (result)
return result;

/*
* Verify we have a legal set of flags
* This leaves us room for future extensions.

+ 59
- 9
kernel/kexec_file.c View File

@@ -88,7 +88,7 @@ int __weak arch_kimage_file_post_load_cleanup(struct kimage *image)
return kexec_image_post_load_cleanup_default(image);
}

#ifdef CONFIG_KEXEC_VERIFY_SIG
#ifdef CONFIG_KEXEC_SIG
static int kexec_image_verify_sig_default(struct kimage *image, void *buf,
unsigned long buf_len)
{
@@ -177,6 +177,59 @@ void kimage_file_post_load_cleanup(struct kimage *image)
image->image_loader_data = NULL;
}

#ifdef CONFIG_KEXEC_SIG
static int
kimage_validate_signature(struct kimage *image)
{
const char *reason;
int ret;

ret = arch_kexec_kernel_verify_sig(image, image->kernel_buf,
image->kernel_buf_len);
switch (ret) {
case 0:
break;

/* Certain verification errors are non-fatal if we're not
* checking errors, provided we aren't mandating that there
* must be a valid signature.
*/
case -ENODATA:
reason = "kexec of unsigned image";
goto decide;
case -ENOPKG:
reason = "kexec of image with unsupported crypto";
goto decide;
case -ENOKEY:
reason = "kexec of image with unavailable key";
decide:
if (IS_ENABLED(CONFIG_KEXEC_SIG_FORCE)) {
pr_notice("%s rejected\n", reason);
return ret;
}

/* If IMA is guaranteed to appraise a signature on the kexec
* image, permit it even if the kernel is otherwise locked
* down.
*/
if (!ima_appraise_signature(READING_KEXEC_IMAGE) &&
security_locked_down(LOCKDOWN_KEXEC))
return -EPERM;

return 0;

/* All other errors are fatal, including nomem, unparseable
* signatures and signature check failures - even if signatures
* aren't required.
*/
default:
pr_notice("kernel signature verification failed (%d).\n", ret);
}

return ret;
}
#endif

/*
* In file mode list of segments is prepared by kernel. Copy relevant
* data from user space, do error checking, prepare segment list
@@ -186,7 +239,7 @@ kimage_file_prepare_segments(struct kimage *image, int kernel_fd, int initrd_fd,
const char __user *cmdline_ptr,
unsigned long cmdline_len, unsigned flags)
{
int ret = 0;
int ret;
void *ldata;
loff_t size;

@@ -202,14 +255,11 @@ kimage_file_prepare_segments(struct kimage *image, int kernel_fd, int initrd_fd,
if (ret)
goto out;

#ifdef CONFIG_KEXEC_VERIFY_SIG
ret = arch_kexec_kernel_verify_sig(image, image->kernel_buf,
image->kernel_buf_len);
if (ret) {
pr_debug("kernel signature verification failed.\n");
#ifdef CONFIG_KEXEC_SIG
ret = kimage_validate_signature(image);

if (ret)
goto out;
}
pr_debug("kernel signature verification successful.\n");
#endif
/* It is possible that there no initramfs is being loaded */
if (!(flags & KEXEC_FILE_NO_INITRAMFS)) {

+ 30
- 7
kernel/module.c View File

@@ -2839,8 +2839,9 @@ static inline void kmemleak_load_module(const struct module *mod,
#ifdef CONFIG_MODULE_SIG
static int module_sig_check(struct load_info *info, int flags)
{
int err = -ENOKEY;
int err = -ENODATA;
const unsigned long markerlen = sizeof(MODULE_SIG_STRING) - 1;
const char *reason;
const void *mod = info->hdr;

/*
@@ -2855,16 +2856,38 @@ static int module_sig_check(struct load_info *info, int flags)
err = mod_verify_sig(mod, info);
}

if (!err) {
switch (err) {
case 0:
info->sig_ok = true;
return 0;
}

/* Not having a signature is only an error if we're strict. */
if (err == -ENOKEY && !is_module_sig_enforced())
err = 0;
/* We don't permit modules to be loaded into trusted kernels
* without a valid signature on them, but if we're not
* enforcing, certain errors are non-fatal.
*/
case -ENODATA:
reason = "Loading of unsigned module";
goto decide;
case -ENOPKG:
reason = "Loading of module with unsupported crypto";
goto decide;
case -ENOKEY:
reason = "Loading of module with unavailable key";
decide:
if (is_module_sig_enforced()) {
pr_notice("%s is rejected\n", reason);
return -EKEYREJECTED;
}

return err;
return security_locked_down(LOCKDOWN_MODULE_SIGNATURE);

/* All other errors are fatal, including nomem, unparseable
* signatures and signature check failures - even if signatures
* aren't required.
*/
default:
return err;
}
}
#else /* !CONFIG_MODULE_SIG */
static int module_sig_check(struct load_info *info, int flags)

+ 16
- 5
kernel/params.c View File

@@ -12,6 +12,7 @@
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/ctype.h>
#include <linux/security.h>

#ifdef CONFIG_SYSFS
/* Protects all built-in parameters, modules use their own param_lock */
@@ -96,13 +97,19 @@ bool parameq(const char *a, const char *b)
return parameqn(a, b, strlen(a)+1);
}

static void param_check_unsafe(const struct kernel_param *kp)
static bool param_check_unsafe(const struct kernel_param *kp)
{
if (kp->flags & KERNEL_PARAM_FL_HWPARAM &&
security_locked_down(LOCKDOWN_MODULE_PARAMETERS))
return false;

if (kp->flags & KERNEL_PARAM_FL_UNSAFE) {
pr_notice("Setting dangerous option %s - tainting kernel\n",
kp->name);
add_taint(TAINT_USER, LOCKDEP_STILL_OK);
}

return true;
}

static int parse_one(char *param,
@@ -132,8 +139,10 @@ static int parse_one(char *param,
pr_debug("handling %s with %p\n", param,
params[i].ops->set);
kernel_param_lock(params[i].mod);
param_check_unsafe(&params[i]);
err = params[i].ops->set(val, &params[i]);
if (param_check_unsafe(&params[i]))
err = params[i].ops->set(val, &params[i]);
else
err = -EPERM;
kernel_param_unlock(params[i].mod);
return err;
}
@@ -553,8 +562,10 @@ static ssize_t param_attr_store(struct module_attribute *mattr,
return -EPERM;

kernel_param_lock(mk->mod);
param_check_unsafe(attribute->param);
err = attribute->param->ops->set(buf, attribute->param);
if (param_check_unsafe(attribute->param))
err = attribute->param->ops->set(buf, attribute->param);
else
err = -EPERM;
kernel_param_unlock(mk->mod);
if (!err)
return len;

+ 2
- 1
kernel/power/hibernate.c View File

@@ -30,6 +30,7 @@
#include <linux/ctype.h>
#include <linux/genhd.h>
#include <linux/ktime.h>
#include <linux/security.h>
#include <trace/events/power.h>

#include "power.h"
@@ -68,7 +69,7 @@ static const struct platform_hibernation_ops *hibernation_ops;

bool hibernation_available(void)
{
return (nohibernate == 0);
return nohibernate == 0 && !security_locked_down(LOCKDOWN_HIBERNATION);
}

/**

+ 10
- 0
kernel/trace/bpf_trace.c View File

@@ -142,8 +142,13 @@ BPF_CALL_3(bpf_probe_read, void *, dst, u32, size, const void *, unsafe_ptr)
{
int ret;

ret = security_locked_down(LOCKDOWN_BPF_READ);
if (ret < 0)
goto out;

ret = probe_kernel_read(dst, unsafe_ptr, size);
if (unlikely(ret < 0))
out:
memset(dst, 0, size);

return ret;
@@ -569,6 +574,10 @@ BPF_CALL_3(bpf_probe_read_str, void *, dst, u32, size,
{
int ret;

ret = security_locked_down(LOCKDOWN_BPF_READ);
if (ret < 0)
goto out;

/*
* The strncpy_from_unsafe() call will likely not fill the entire
* buffer, but that's okay in this circumstance as we're probing
@@ -580,6 +589,7 @@ BPF_CALL_3(bpf_probe_read_str, void *, dst, u32, size,
*/
ret = strncpy_from_unsafe(dst, unsafe_ptr, size);
if (unlikely(ret < 0))
out:
memset(dst, 0, size);

return ret;

+ 5
- 0
kernel/trace/trace_kprobe.c View File

@@ -11,6 +11,7 @@
#include <linux/uaccess.h>
#include <linux/rculist.h>
#include <linux/error-injection.h>
#include <linux/security.h>

#include <asm/setup.h> /* for COMMAND_LINE_SIZE */

@@ -460,6 +461,10 @@ static int __register_trace_kprobe(struct trace_kprobe *tk)
{
int i, ret;

ret = security_locked_down(LOCKDOWN_KPROBES);
if (ret)
return ret;

if (trace_kprobe_is_registered(tk))
return -EINVAL;


+ 6
- 5
security/Kconfig View File

@@ -237,6 +237,7 @@ source "security/apparmor/Kconfig"
source "security/loadpin/Kconfig"
source "security/yama/Kconfig"
source "security/safesetid/Kconfig"
source "security/lockdown/Kconfig"

source "security/integrity/Kconfig"

@@ -276,11 +277,11 @@ endchoice

config LSM
string "Ordered list of enabled LSMs"
default "yama,loadpin,safesetid,integrity,smack,selinux,tomoyo,apparmor" if DEFAULT_SECURITY_SMACK
default "yama,loadpin,safesetid,integrity,apparmor,selinux,smack,tomoyo" if DEFAULT_SECURITY_APPARMOR
default "yama,loadpin,safesetid,integrity,tomoyo" if DEFAULT_SECURITY_TOMOYO
default "yama,loadpin,safesetid,integrity" if DEFAULT_SECURITY_DAC
default "yama,loadpin,safesetid,integrity,selinux,smack,tomoyo,apparmor"
default "lockdown,yama,loadpin,safesetid,integrity,smack,selinux,tomoyo,apparmor" if DEFAULT_SECURITY_SMACK
default "lockdown,yama,loadpin,safesetid,integrity,apparmor,selinux,smack,tomoyo" if DEFAULT_SECURITY_APPARMOR
default "lockdown,yama,loadpin,safesetid,integrity,tomoyo" if DEFAULT_SECURITY_TOMOYO
default "lockdown,yama,loadpin,safesetid,integrity" if DEFAULT_SECURITY_DAC
default "lockdown,yama,loadpin,safesetid,integrity,selinux,smack,tomoyo,apparmor"
help
A comma-separated list of LSMs, in initialization order.
Any LSMs left off this list will be ignored. This can be

+ 2
- 0
security/Makefile View File

@@ -11,6 +11,7 @@ subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor
subdir-$(CONFIG_SECURITY_YAMA) += yama
subdir-$(CONFIG_SECURITY_LOADPIN) += loadpin
subdir-$(CONFIG_SECURITY_SAFESETID) += safesetid
subdir-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown

# always enable default capabilities
obj-y += commoncap.o
@@ -27,6 +28,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/
obj-$(CONFIG_SECURITY_YAMA) += yama/
obj-$(CONFIG_SECURITY_LOADPIN) += loadpin/
obj-$(CONFIG_SECURITY_SAFESETID) += safesetid/
obj-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown/
obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o

# Object integrity file lists

+ 1
- 1
security/integrity/ima/Kconfig View File

@@ -160,7 +160,7 @@ config IMA_APPRAISE

config IMA_ARCH_POLICY
bool "Enable loading an IMA architecture specific policy"
depends on (KEXEC_VERIFY_SIG && IMA) || IMA_APPRAISE \
depends on (KEXEC_SIG && IMA) || IMA_APPRAISE \
&& INTEGRITY_ASYMMETRIC_KEYS
default n
help

+ 2
- 0
security/integrity/ima/ima.h View File

@@ -114,6 +114,8 @@ struct ima_kexec_hdr {
u64 count;
};

extern const int read_idmap[];

#ifdef CONFIG_HAVE_IMA_KEXEC
void ima_load_kexec_buffer(void);
#else

+ 2
- 2
security/integrity/ima/ima_main.c View File

@@ -518,7 +518,7 @@ int ima_read_file(struct file *file, enum kernel_read_file_id read_id)
return 0;
}

static const int read_idmap[READING_MAX_ID] = {
const int read_idmap[READING_MAX_ID] = {
[READING_FIRMWARE] = FIRMWARE_CHECK,
[READING_FIRMWARE_PREALLOC_BUFFER] = FIRMWARE_CHECK,
[READING_MODULE] = MODULE_CHECK,
@@ -590,7 +590,7 @@ int ima_load_data(enum kernel_load_data_id id)

switch (id) {
case LOADING_KEXEC_IMAGE:
if (IS_ENABLED(CONFIG_KEXEC_VERIFY_SIG)
if (IS_ENABLED(CONFIG_KEXEC_SIG)
&& arch_ima_get_secureboot()) {
pr_err("impossible to appraise a kernel image without a file descriptor; try using kexec_file_load syscall.\n");
return -EACCES;

+ 50
- 0
security/integrity/ima/ima_policy.c View File

@@ -1507,3 +1507,53 @@ int ima_policy_show(struct seq_file *m, void *v)
return 0;
}
#endif /* CONFIG_IMA_READ_POLICY */

#if defined(CONFIG_IMA_APPRAISE) && defined(CONFIG_INTEGRITY_TRUSTED_KEYRING)
/*
* ima_appraise_signature: whether IMA will appraise a given function using
* an IMA digital signature. This is restricted to cases where the kernel
* has a set of built-in trusted keys in order to avoid an attacker simply
* loading additional keys.
*/
bool ima_appraise_signature(enum kernel_read_file_id id)
{
struct ima_rule_entry *entry;
bool found = false;
enum ima_hooks func;

if (id >= READING_MAX_ID)
return false;

func = read_idmap[id] ?: FILE_CHECK;

rcu_read_lock();
list_for_each_entry_rcu(entry, ima_rules, list) {
if (entry->action != APPRAISE)
continue;

/*
* A generic entry will match, but otherwise require that it
* match the func we're looking for
*/
if (entry->func && entry->func != func)
continue;

/*
* We require this to be a digital signature, not a raw IMA
* hash.
*/
if (entry->flags & IMA_DIGSIG_REQUIRED)
found = true;

/*
* We've found a rule that matches, so break now even if it
* didn't require a digital signature - a later rule that does
* won't override it, so would be a false positive.
*/
break;
}

rcu_read_unlock();
return found;
}
#endif /* CONFIG_IMA_APPRAISE && CONFIG_INTEGRITY_TRUSTED_KEYRING */

+ 47
- 0
security/lockdown/Kconfig View File

@@ -0,0 +1,47 @@
config SECURITY_LOCKDOWN_LSM
bool "Basic module for enforcing kernel lockdown"
depends on SECURITY
select MODULE_SIG if MODULES
help
Build support for an LSM that enforces a coarse kernel lockdown
behaviour.

config SECURITY_LOCKDOWN_LSM_EARLY
bool "Enable lockdown LSM early in init"
depends on SECURITY_LOCKDOWN_LSM
help
Enable the lockdown LSM early in boot. This is necessary in order
to ensure that lockdown enforcement can be carried out on kernel
boot parameters that are otherwise parsed before the security
subsystem is fully initialised. If enabled, lockdown will
unconditionally be called before any other LSMs.

choice
prompt "Kernel default lockdown mode"
default LOCK_DOWN_KERNEL_FORCE_NONE
depends on SECURITY_LOCKDOWN_LSM
help
The kernel can be configured to default to differing levels of
lockdown.

config LOCK_DOWN_KERNEL_FORCE_NONE
bool "None"
help
No lockdown functionality is enabled by default. Lockdown may be
enabled via the kernel commandline or /sys/kernel/security/lockdown.

config LOCK_DOWN_KERNEL_FORCE_INTEGRITY
bool "Integrity"
help
The kernel runs in integrity mode by default. Features that allow
the kernel to be modified at runtime are disabled.

config LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITY
bool "Confidentiality"
help
The kernel runs in confidentiality mode by default. Features that
allow the kernel to be modified at runtime or that permit userland
code to read confidential material held inside the kernel are
disabled.

endchoice

+ 1
- 0
security/lockdown/Makefile View File

@@ -0,0 +1 @@
obj-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown.o

+ 191
- 0
security/lockdown/lockdown.c View File

@@ -0,0 +1,191 @@
// SPDX-License-Identifier: GPL-2.0
/* Lock down the kernel
*
* Copyright (C) 2016 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public Licence
* as published by the Free Software Foundation; either version
* 2 of the Licence, or (at your option) any later version.
*/

#include <linux/security.h>
#include <linux/export.h>
#include <linux/lsm_hooks.h>

static enum lockdown_reason kernel_locked_down;

static const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX+1] = {
[LOCKDOWN_NONE] = "none",
[LOCKDOWN_MODULE_SIGNATURE] = "unsigned module loading",
[LOCKDOWN_DEV_MEM] = "/dev/mem,kmem,port",
[LOCKDOWN_KEXEC] = "kexec of unsigned images",
[LOCKDOWN_HIBERNATION] = "hibernation",
[LOCKDOWN_PCI_ACCESS] = "direct PCI access",
[LOCKDOWN_IOPORT] = "raw io port access",
[LOCKDOWN_MSR] = "raw MSR access",
[LOCKDOWN_ACPI_TABLES] = "modifying ACPI tables",
[LOCKDOWN_PCMCIA_CIS] = "direct PCMCIA CIS storage",
[LOCKDOWN_TIOCSSERIAL] = "reconfiguration of serial port IO",
[LOCKDOWN_MODULE_PARAMETERS] = "unsafe module parameters",
[LOCKDOWN_MMIOTRACE] = "unsafe mmio",
[LOCKDOWN_DEBUGFS] = "debugfs access",
[LOCKDOWN_INTEGRITY_MAX] = "integrity",
[LOCKDOWN_KCORE] = "/proc/kcore access",
[LOCKDOWN_KPROBES] = "use of kprobes",
[LOCKDOWN_BPF_READ] = "use of bpf to read kernel RAM",
[LOCKDOWN_PERF] = "unsafe use of perf",
[LOCKDOWN_TRACEFS] = "use of tracefs",
[LOCKDOWN_CONFIDENTIALITY_MAX] = "confidentiality",
};

static const enum lockdown_reason lockdown_levels[] = {LOCKDOWN_NONE,
LOCKDOWN_INTEGRITY_MAX,
LOCKDOWN_CONFIDENTIALITY_MAX};

/*
* Put the kernel into lock-down mode.
*/
static int lock_kernel_down(const char *where, enum lockdown_reason level)
{
if (kernel_locked_down >= level)
return -EPERM;

kernel_locked_down = level;
pr_notice("Kernel is locked down from %s; see man kernel_lockdown.7\n",
where);
return 0;
}

static int __init lockdown_param(char *level)
{
if (!level)
return -EINVAL;

if (strcmp(level, "integrity") == 0)
lock_kernel_down("command line", LOCKDOWN_INTEGRITY_MAX);
else if (strcmp(level, "confidentiality") == 0)
lock_kernel_down("command line", LOCKDOWN_CONFIDENTIALITY_MAX);
else
return -EINVAL;

return 0;
}

early_param("lockdown", lockdown_param);

/**
* lockdown_is_locked_down - Find out if the kernel is locked down
* @what: Tag to use in notice generated if lockdown is in effect
*/
static int lockdown_is_locked_down(enum lockdown_reason what)
{
if (WARN(what >= LOCKDOWN_CONFIDENTIALITY_MAX,
"Invalid lockdown reason"))
return -EPERM;

if (kernel_locked_down >= what) {
if (lockdown_reasons[what])
pr_notice("Lockdown: %s: %s is restricted; see man kernel_lockdown.7\n",
current->comm, lockdown_reasons[what]);
return -EPERM;
}

return 0;
}

static struct security_hook_list lockdown_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(locked_down, lockdown_is_locked_down),
};

static int __init lockdown_lsm_init(void)
{
#if defined(CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY)
lock_kernel_down("Kernel configuration", LOCKDOWN_INTEGRITY_MAX);
#elif defined(CONFIG_LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITY)
lock_kernel_down("Kernel configuration", LOCKDOWN_CONFIDENTIALITY_MAX);
#endif
security_add_hooks(lockdown_hooks, ARRAY_SIZE(lockdown_hooks),
"lockdown");
return 0;
}

static ssize_t lockdown_read(struct file *filp, char __user *buf, size_t count,
loff_t *ppos)
{
char temp[80];
int i, offset = 0;

for (i = 0; i < ARRAY_SIZE(lockdown_levels); i++) {
enum lockdown_reason level = lockdown_levels[i];

if (lockdown_reasons[level]) {
const char *label = lockdown_reasons[level];

if (kernel_locked_down == level)
offset += sprintf(temp+offset, "[%s] ", label);
else
offset += sprintf(temp+offset, "%s ", label);
}
}

/* Convert the last space to a newline if needed. */
if (offset > 0)
temp[offset-1] = '\n';

return simple_read_from_buffer(buf, count, ppos, temp, strlen(temp));
}

static ssize_t lockdown_write(struct file *file, const char __user *buf,
size_t n, loff_t *ppos)
{
char *state;
int i, len, err = -EINVAL;

state = memdup_user_nul(buf, n);
if (IS_ERR(state))
return PTR_ERR(state);

len = strlen(state);
if (len && state[len-1] == '\n') {
state[len-1] = '\0';
len--;
}

for (i = 0; i < ARRAY_SIZE(lockdown_levels); i++) {
enum lockdown_reason level = lockdown_levels[i];
const char *label = lockdown_reasons[level];

if (label && !strcmp(state, label))
err = lock_kernel_down("securityfs", level);
}

kfree(state);
return err ? err : n;
}

static const struct file_operations lockdown_ops = {
.read = lockdown_read,
.write = lockdown_write,
};

static int __init lockdown_secfs_init(void)
{
struct dentry *dentry;

dentry = securityfs_create_file("lockdown", 0600, NULL, NULL,
&lockdown_ops);
return PTR_ERR_OR_ZERO(dentry);
}

core_initcall(lockdown_secfs_init);

#ifdef CONFIG_SECURITY_LOCKDOWN_LSM_EARLY
DEFINE_EARLY_LSM(lockdown) = {
#else
DEFINE_LSM(lockdown) = {
#endif
.name = "lockdown",
.init = lockdown_lsm_init,
};

+ 48
- 8
security/security.c View File

@@ -33,6 +33,7 @@

/* How many LSMs were built into the kernel? */
#define LSM_COUNT (__end_lsm_info - __start_lsm_info)
#define EARLY_LSM_COUNT (__end_early_lsm_info - __start_early_lsm_info)

struct security_hook_heads security_hook_heads __lsm_ro_after_init;
static BLOCKING_NOTIFIER_HEAD(blocking_lsm_notifier_chain);
@@ -277,6 +278,8 @@ static void __init ordered_lsm_parse(const char *order, const char *origin)
static void __init lsm_early_cred(struct cred *cred);
static void __init lsm_early_task(struct task_struct *task);

static int lsm_append(const char *new, char **result);

static void __init ordered_lsm_init(void)
{
struct lsm_info **lsm;
@@ -323,6 +326,26 @@ static void __init ordered_lsm_init(void)
kfree(ordered_lsms);
}

int __init early_security_init(void)
{
int i;
struct hlist_head *list = (struct hlist_head *) &security_hook_heads;
struct lsm_info *lsm;

for (i = 0; i < sizeof(security_hook_heads) / sizeof(struct hlist_head);
i++)
INIT_HLIST_HEAD(&list[i]);

for (lsm = __start_early_lsm_info; lsm < __end_early_lsm_info; lsm++) {
if (!lsm->enabled)
lsm->enabled = &lsm_enabled_true;
prepare_lsm(lsm);
initialize_lsm(lsm);
}

return 0;
}

/**
* security_init - initializes the security framework
*
@@ -330,14 +353,18 @@ static void __init ordered_lsm_init(void)
*/
int __init security_init(void)
{
int i;
struct hlist_head *list = (struct hlist_head *) &security_hook_heads;
struct lsm_info *lsm;

pr_info("Security Framework initializing\n");

for (i = 0; i < sizeof(security_hook_heads) / sizeof(struct hlist_head);
i++)
INIT_HLIST_HEAD(&list[i]);
/*
* Append the names of the early LSM modules now that kmalloc() is
* available
*/
for (lsm = __start_early_lsm_info; lsm < __end_early_lsm_info; lsm++) {
if (lsm->enabled)
lsm_append(lsm->name, &lsm_names);
}

/* Load LSMs in specified order. */
ordered_lsm_init();
@@ -384,7 +411,7 @@ static bool match_last_lsm(const char *list, const char *lsm)
return !strcmp(last, lsm);
}

static int lsm_append(char *new, char **result)
static int lsm_append(const char *new, char **result)
{
char *cp;

@@ -422,8 +449,15 @@ void __init security_add_hooks(struct security_hook_list *hooks, int count,
hooks[i].lsm = lsm;
hlist_add_tail_rcu(&hooks[i].list, hooks[i].head);
}
if (lsm_append(lsm, &lsm_names) < 0)
panic("%s - Cannot get early memory.\n", __func__);

/*
* Don't try to append during early_security_init(), we'll come back
* and fix this up afterwards.
*/
if (slab_is_available()) {
if (lsm_append(lsm, &lsm_names) < 0)
panic("%s - Cannot get early memory.\n", __func__);
}
}

int call_blocking_lsm_notifier(enum lsm_event event, void *data)
@@ -2364,3 +2398,9 @@ void security_bpf_prog_free(struct bpf_prog_aux *aux)
call_void_hook(bpf_prog_free_security, aux);
}
#endif /* CONFIG_BPF_SYSCALL */

int security_locked_down(enum lockdown_reason what)
{
return call_int_hook(locked_down, 0, what);
}
EXPORT_SYMBOL(security_locked_down);

Loading…
Cancel
Save