* [PATCH v27 10/22] x86/sgx: Linux Enclave Driver
From: Jarkko Sakkinen @ 2020-02-23 17:25 UTC (permalink / raw)
To: linux-kernel, x86, linux-sgx
Cc: akpm, dave.hansen, sean.j.christopherson, nhorman, npmccallum,
haitao.huang, andriy.shevchenko, tglx, kai.svahn, bp, josh, luto,
kai.huang, rientjes, cedric.xing, puiterwijk, Jarkko Sakkinen,
linux-security-module, Suresh Siddha, Haitao Huang,
Jethro Beekman
In-Reply-To: <20200223172559.6912-1-jarkko.sakkinen@linux.intel.com>
Intel Software Guard eXtensions (SGX) is a set of CPU instructions that
can be used by applications to set aside private regions of code and
data. The code outside the SGX hosted software entity is disallowed to
access the memory inside the enclave enforced by the CPU. We call these
entities as enclaves.
This commit implements a driver that provides an ioctl API to construct
and run enclaves. Enclaves are constructed from pages residing in
reserved physical memory areas. The contents of these pages can only be
accessed when they are mapped as part of an enclave, by a hardware
thread running inside the enclave.
The starting state of an enclave consists of a fixed measured set of
pages that are copied to the EPC during the construction process by
using ENCLS leaf functions and Software Enclave Control Structure (SECS)
that defines the enclave properties.
Enclave are constructed by using ENCLS leaf functions ECREATE, EADD and
EINIT. ECREATE initializes SECS, EADD copies pages from system memory to
the EPC and EINIT check a given signed measurement and moves the enclave
into a state ready for execution.
An initialized enclave can only be accessed through special Thread Control
Structure (TCS) pages by using ENCLU (ring-3 only) leaf EENTER. This leaf
function converts a thread into enclave mode and continues the execution in
the offset defined by the TCS provided to EENTER. An enclave is exited
through syscall, exception, interrupts or by explicitly calling another
ENCLU leaf EEXIT.
The permissions, which enclave page is added will set the limit for maximum
permissions that can be set for mmap() and mprotect(). This will
effectively allow to build different security schemes between producers and
consumers of enclaves. Later on we can increase granularity with LSM hooks
for page addition (i.e. for producers) and mapping of the enclave (i.e. for
consumers)
Cc: linux-security-module@vger.kernel.org
Co-developed-by: Sean Christopherson <sean.j.christopherson@intel.com>
Signed-off-by: Sean Christopherson <sean.j.christopherson@intel.com>
Co-developed-by: Suresh Siddha <suresh.b.siddha@intel.com>
Signed-off-by: Suresh Siddha <suresh.b.siddha@intel.com>
Tested-by: Haitao Huang <haitao.huang@linux.intel.com>
Tested-by: Jethro Beekman <jethro@fortanix.com>
Signed-off-by: Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com>
---
.../userspace-api/ioctl/ioctl-number.rst | 1 +
arch/x86/include/uapi/asm/sgx.h | 66 ++
arch/x86/kernel/cpu/sgx/Makefile | 3 +
arch/x86/kernel/cpu/sgx/driver.c | 194 +++++
arch/x86/kernel/cpu/sgx/driver.h | 30 +
arch/x86/kernel/cpu/sgx/encl.c | 336 +++++++++
arch/x86/kernel/cpu/sgx/encl.h | 87 +++
arch/x86/kernel/cpu/sgx/ioctl.c | 697 ++++++++++++++++++
arch/x86/kernel/cpu/sgx/main.c | 12 +-
arch/x86/kernel/cpu/sgx/reclaim.c | 1 +
10 files changed, 1426 insertions(+), 1 deletion(-)
create mode 100644 arch/x86/include/uapi/asm/sgx.h
create mode 100644 arch/x86/kernel/cpu/sgx/driver.c
create mode 100644 arch/x86/kernel/cpu/sgx/driver.h
create mode 100644 arch/x86/kernel/cpu/sgx/encl.c
create mode 100644 arch/x86/kernel/cpu/sgx/encl.h
create mode 100644 arch/x86/kernel/cpu/sgx/ioctl.c
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
index 2e91370dc159..1c54dd2704db 100644
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
@@ -321,6 +321,7 @@ Code Seq# Include File Comments
<mailto:tlewis@mindspring.com>
0xA3 90-9F linux/dtlk.h
0xA4 00-1F uapi/linux/tee.h Generic TEE subsystem
+0xA4 00-1F uapi/asm/sgx.h Intel SGX subsystem (a legit conflict as TEE and SGX do not co-exist)
0xAA 00-3F linux/uapi/linux/userfaultfd.h
0xAB 00-1F linux/nbd.h
0xAC 00-1F linux/raw.h
diff --git a/arch/x86/include/uapi/asm/sgx.h b/arch/x86/include/uapi/asm/sgx.h
new file mode 100644
index 000000000000..5edb08ab8fd0
--- /dev/null
+++ b/arch/x86/include/uapi/asm/sgx.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) WITH Linux-syscall-note */
+/*
+ * Copyright(c) 2016-19 Intel Corporation.
+ */
+#ifndef _UAPI_ASM_X86_SGX_H
+#define _UAPI_ASM_X86_SGX_H
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+/**
+ * enum sgx_epage_flags - page control flags
+ * %SGX_PAGE_MEASURE: Measure the page contents with a sequence of
+ * ENCLS[EEXTEND] operations.
+ */
+enum sgx_page_flags {
+ SGX_PAGE_MEASURE = 0x01,
+};
+
+#define SGX_MAGIC 0xA4
+
+#define SGX_IOC_ENCLAVE_CREATE \
+ _IOW(SGX_MAGIC, 0x00, struct sgx_enclave_create)
+#define SGX_IOC_ENCLAVE_ADD_PAGES \
+ _IOWR(SGX_MAGIC, 0x01, struct sgx_enclave_add_pages)
+#define SGX_IOC_ENCLAVE_INIT \
+ _IOW(SGX_MAGIC, 0x02, struct sgx_enclave_init)
+
+/**
+ * struct sgx_enclave_create - parameter structure for the
+ * %SGX_IOC_ENCLAVE_CREATE ioctl
+ * @src: address for the SECS page data
+ */
+struct sgx_enclave_create {
+ __u64 src;
+};
+
+/**
+ * struct sgx_enclave_add_pages - parameter structure for the
+ * %SGX_IOC_ENCLAVE_ADD_PAGE ioctl
+ * @src: start address for the page data
+ * @offset: starting page offset
+ * @length: length of the data (multiple of the page size)
+ * @secinfo: address for the SECINFO data
+ * @flags: page control flags
+ * @count: number of bytes added (multiple of the page size)
+ */
+struct sgx_enclave_add_pages {
+ __u64 src;
+ __u64 offset;
+ __u64 length;
+ __u64 secinfo;
+ __u64 flags;
+ __u64 count;
+};
+
+/**
+ * struct sgx_enclave_init - parameter structure for the
+ * %SGX_IOC_ENCLAVE_INIT ioctl
+ * @sigstruct: address for the SIGSTRUCT data
+ */
+struct sgx_enclave_init {
+ __u64 sigstruct;
+};
+
+#endif /* _UAPI_ASM_X86_SGX_H */
diff --git a/arch/x86/kernel/cpu/sgx/Makefile b/arch/x86/kernel/cpu/sgx/Makefile
index 2dec75916a5e..f8d32da3a67a 100644
--- a/arch/x86/kernel/cpu/sgx/Makefile
+++ b/arch/x86/kernel/cpu/sgx/Makefile
@@ -1,3 +1,6 @@
obj-y += \
+ driver.o \
+ encl.o \
+ ioctl.o \
main.o \
reclaim.o
diff --git a/arch/x86/kernel/cpu/sgx/driver.c b/arch/x86/kernel/cpu/sgx/driver.c
new file mode 100644
index 000000000000..b4aa7b9f8376
--- /dev/null
+++ b/arch/x86/kernel/cpu/sgx/driver.c
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2016-18 Intel Corporation.
+
+#include <linux/acpi.h>
+#include <linux/miscdevice.h>
+#include <linux/mman.h>
+#include <linux/security.h>
+#include <linux/suspend.h>
+#include <asm/traps.h>
+#include "driver.h"
+#include "encl.h"
+
+MODULE_DESCRIPTION("Intel SGX Enclave Driver");
+MODULE_AUTHOR("Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com>");
+MODULE_LICENSE("Dual BSD/GPL");
+
+u64 sgx_encl_size_max_32;
+u64 sgx_encl_size_max_64;
+u32 sgx_misc_reserved_mask;
+u64 sgx_attributes_reserved_mask;
+u64 sgx_xfrm_reserved_mask = ~0x3;
+u32 sgx_xsave_size_tbl[64];
+
+static int sgx_open(struct inode *inode, struct file *file)
+{
+ struct sgx_encl *encl;
+ int ret;
+
+ encl = kzalloc(sizeof(*encl), GFP_KERNEL);
+ if (!encl)
+ return -ENOMEM;
+
+ atomic_set(&encl->flags, 0);
+ kref_init(&encl->refcount);
+ INIT_RADIX_TREE(&encl->page_tree, GFP_KERNEL);
+ mutex_init(&encl->lock);
+ INIT_LIST_HEAD(&encl->mm_list);
+ spin_lock_init(&encl->mm_lock);
+
+ ret = init_srcu_struct(&encl->srcu);
+ if (ret) {
+ kfree(encl);
+ return ret;
+ }
+
+ file->private_data = encl;
+
+ return 0;
+}
+
+static int sgx_release(struct inode *inode, struct file *file)
+{
+ struct sgx_encl *encl = file->private_data;
+ struct sgx_encl_mm *encl_mm;
+
+ for ( ; ; ) {
+ spin_lock(&encl->mm_lock);
+
+ if (list_empty(&encl->mm_list)) {
+ encl_mm = NULL;
+ } else {
+ encl_mm = list_first_entry(&encl->mm_list,
+ struct sgx_encl_mm, list);
+ list_del_rcu(&encl_mm->list);
+ }
+
+ spin_unlock(&encl->mm_lock);
+
+ /* The list is empty, ready to go. */
+ if (!encl_mm)
+ break;
+
+ synchronize_srcu(&encl->srcu);
+ mmu_notifier_unregister(&encl_mm->mmu_notifier, encl_mm->mm);
+ kfree(encl_mm);
+ };
+
+ mutex_lock(&encl->lock);
+ atomic_or(SGX_ENCL_DEAD, &encl->flags);
+ mutex_unlock(&encl->lock);
+
+ kref_put(&encl->refcount, sgx_encl_release);
+ return 0;
+}
+
+#ifdef CONFIG_COMPAT
+static long sgx_compat_ioctl(struct file *filep, unsigned int cmd,
+ unsigned long arg)
+{
+ return sgx_ioctl(filep, cmd, arg);
+}
+#endif
+
+static int sgx_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct sgx_encl *encl = file->private_data;
+ int ret;
+
+ ret = sgx_encl_may_map(encl, vma->vm_start, vma->vm_end,
+ vma->vm_flags & (VM_READ | VM_WRITE | VM_EXEC));
+ if (ret)
+ return ret;
+
+ ret = sgx_encl_mm_add(encl, vma->vm_mm);
+ if (ret)
+ return ret;
+
+ vma->vm_ops = &sgx_vm_ops;
+ vma->vm_flags |= VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP | VM_IO;
+ vma->vm_private_data = encl;
+
+ return 0;
+}
+
+static unsigned long sgx_get_unmapped_area(struct file *file,
+ unsigned long addr,
+ unsigned long len,
+ unsigned long pgoff,
+ unsigned long flags)
+{
+ if (flags & MAP_PRIVATE)
+ return -EINVAL;
+
+ if (flags & MAP_FIXED)
+ return addr;
+
+ return current->mm->get_unmapped_area(file, addr, len, pgoff, flags);
+}
+
+static const struct file_operations sgx_encl_fops = {
+ .owner = THIS_MODULE,
+ .open = sgx_open,
+ .release = sgx_release,
+ .unlocked_ioctl = sgx_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = sgx_compat_ioctl,
+#endif
+ .mmap = sgx_mmap,
+ .get_unmapped_area = sgx_get_unmapped_area,
+};
+
+const struct file_operations sgx_provision_fops = {
+ .owner = THIS_MODULE,
+};
+
+static struct miscdevice sgx_dev_enclave = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "enclave",
+ .nodename = "sgx/enclave",
+ .fops = &sgx_encl_fops,
+};
+
+int __init sgx_drv_init(void)
+{
+ unsigned int eax, ebx, ecx, edx;
+ u64 attr_mask, xfrm_mask;
+ int ret;
+ int i;
+
+ if (!boot_cpu_has(X86_FEATURE_SGX_LC)) {
+ pr_info("The public key MSRs are not writable.\n");
+ return -ENODEV;
+ }
+
+ cpuid_count(SGX_CPUID, 0, &eax, &ebx, &ecx, &edx);
+ sgx_misc_reserved_mask = ~ebx | SGX_MISC_RESERVED_MASK;
+ sgx_encl_size_max_64 = 1ULL << ((edx >> 8) & 0xFF);
+ sgx_encl_size_max_32 = 1ULL << (edx & 0xFF);
+
+ cpuid_count(SGX_CPUID, 1, &eax, &ebx, &ecx, &edx);
+
+ attr_mask = (((u64)ebx) << 32) + (u64)eax;
+ sgx_attributes_reserved_mask = ~attr_mask | SGX_ATTR_RESERVED_MASK;
+
+ if (boot_cpu_has(X86_FEATURE_OSXSAVE)) {
+ xfrm_mask = (((u64)edx) << 32) + (u64)ecx;
+
+ for (i = 2; i < 64; i++) {
+ cpuid_count(0x0D, i, &eax, &ebx, &ecx, &edx);
+ if ((1 << i) & xfrm_mask)
+ sgx_xsave_size_tbl[i] = eax + ebx;
+ }
+
+ sgx_xfrm_reserved_mask = ~xfrm_mask;
+ }
+
+ ret = misc_register(&sgx_dev_enclave);
+ if (ret) {
+ pr_err("Creating /dev/sgx/enclave failed with %d.\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
diff --git a/arch/x86/kernel/cpu/sgx/driver.h b/arch/x86/kernel/cpu/sgx/driver.h
new file mode 100644
index 000000000000..e4063923115b
--- /dev/null
+++ b/arch/x86/kernel/cpu/sgx/driver.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+#ifndef __ARCH_SGX_DRIVER_H__
+#define __ARCH_SGX_DRIVER_H__
+
+#include <crypto/hash.h>
+#include <linux/kref.h>
+#include <linux/mmu_notifier.h>
+#include <linux/radix-tree.h>
+#include <linux/rwsem.h>
+#include <linux/sched.h>
+#include <linux/workqueue.h>
+#include <uapi/asm/sgx.h>
+#include "sgx.h"
+
+#define SGX_EINIT_SPIN_COUNT 20
+#define SGX_EINIT_SLEEP_COUNT 50
+#define SGX_EINIT_SLEEP_TIME 20
+
+extern u64 sgx_encl_size_max_32;
+extern u64 sgx_encl_size_max_64;
+extern u32 sgx_misc_reserved_mask;
+extern u64 sgx_attributes_reserved_mask;
+extern u64 sgx_xfrm_reserved_mask;
+extern u32 sgx_xsave_size_tbl[64];
+
+long sgx_ioctl(struct file *filep, unsigned int cmd, unsigned long arg);
+
+int sgx_drv_init(void);
+
+#endif /* __ARCH_X86_SGX_DRIVER_H__ */
diff --git a/arch/x86/kernel/cpu/sgx/encl.c b/arch/x86/kernel/cpu/sgx/encl.c
new file mode 100644
index 000000000000..f349697c7508
--- /dev/null
+++ b/arch/x86/kernel/cpu/sgx/encl.c
@@ -0,0 +1,336 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2016-18 Intel Corporation.
+
+#include <linux/lockdep.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+#include <linux/shmem_fs.h>
+#include <linux/suspend.h>
+#include <linux/sched/mm.h>
+#include "arch.h"
+#include "encl.h"
+#include "sgx.h"
+
+static struct sgx_encl_page *sgx_encl_load_page(struct sgx_encl *encl,
+ unsigned long addr)
+{
+ struct sgx_encl_page *entry;
+ unsigned int flags;
+
+ /* If process was forked, VMA is still there but vm_private_data is set
+ * to NULL.
+ */
+ if (!encl)
+ return ERR_PTR(-EFAULT);
+
+ flags = atomic_read(&encl->flags);
+
+ if ((flags & SGX_ENCL_DEAD) || !(flags & SGX_ENCL_INITIALIZED))
+ return ERR_PTR(-EFAULT);
+
+ entry = radix_tree_lookup(&encl->page_tree, addr >> PAGE_SHIFT);
+ if (!entry)
+ return ERR_PTR(-EFAULT);
+
+ /* Page is already resident in the EPC. */
+ if (entry->epc_page)
+ return entry;
+
+ return ERR_PTR(-EFAULT);
+}
+
+static void sgx_mmu_notifier_release(struct mmu_notifier *mn,
+ struct mm_struct *mm)
+{
+ struct sgx_encl_mm *encl_mm =
+ container_of(mn, struct sgx_encl_mm, mmu_notifier);
+ struct sgx_encl_mm *tmp = NULL;
+
+ /*
+ * The enclave itself can remove encl_mm. Note, objects can't be moved
+ * off an RCU protected list, but deletion is ok.
+ */
+ spin_lock(&encl_mm->encl->mm_lock);
+ list_for_each_entry(tmp, &encl_mm->encl->mm_list, list) {
+ if (tmp == encl_mm) {
+ list_del_rcu(&encl_mm->list);
+ break;
+ }
+ }
+ spin_unlock(&encl_mm->encl->mm_lock);
+
+ if (tmp == encl_mm) {
+ synchronize_srcu(&encl_mm->encl->srcu);
+ mmu_notifier_put(mn);
+ }
+}
+
+static void sgx_mmu_notifier_free(struct mmu_notifier *mn)
+{
+ struct sgx_encl_mm *encl_mm =
+ container_of(mn, struct sgx_encl_mm, mmu_notifier);
+
+ kfree(encl_mm);
+}
+
+static const struct mmu_notifier_ops sgx_mmu_notifier_ops = {
+ .release = sgx_mmu_notifier_release,
+ .free_notifier = sgx_mmu_notifier_free,
+};
+
+static struct sgx_encl_mm *sgx_encl_find_mm(struct sgx_encl *encl,
+ struct mm_struct *mm)
+{
+ struct sgx_encl_mm *encl_mm = NULL;
+ struct sgx_encl_mm *tmp;
+ int idx;
+
+ idx = srcu_read_lock(&encl->srcu);
+
+ list_for_each_entry_rcu(tmp, &encl->mm_list, list) {
+ if (tmp->mm == mm) {
+ encl_mm = tmp;
+ break;
+ }
+ }
+
+ srcu_read_unlock(&encl->srcu, idx);
+
+ return encl_mm;
+}
+
+int sgx_encl_mm_add(struct sgx_encl *encl, struct mm_struct *mm)
+{
+ struct sgx_encl_mm *encl_mm;
+ int ret;
+
+ if (atomic_read(&encl->flags) & SGX_ENCL_DEAD)
+ return -EINVAL;
+
+ /*
+ * mm_structs are kept on mm_list until the mm or the enclave dies,
+ * i.e. once an mm is off the list, it's gone for good, therefore it's
+ * impossible to get a false positive on @mm due to a stale mm_list.
+ */
+ if (sgx_encl_find_mm(encl, mm))
+ return 0;
+
+ encl_mm = kzalloc(sizeof(*encl_mm), GFP_KERNEL);
+ if (!encl_mm)
+ return -ENOMEM;
+
+ encl_mm->encl = encl;
+ encl_mm->mm = mm;
+ encl_mm->mmu_notifier.ops = &sgx_mmu_notifier_ops;
+
+ ret = __mmu_notifier_register(&encl_mm->mmu_notifier, mm);
+ if (ret) {
+ kfree(encl_mm);
+ return ret;
+ }
+
+ spin_lock(&encl->mm_lock);
+ list_add_rcu(&encl_mm->list, &encl->mm_list);
+ spin_unlock(&encl->mm_lock);
+
+ synchronize_srcu(&encl->srcu);
+
+ return 0;
+}
+
+static void sgx_vma_open(struct vm_area_struct *vma)
+{
+ struct sgx_encl *encl = vma->vm_private_data;
+
+ if (!encl)
+ return;
+
+ if (sgx_encl_mm_add(encl, vma->vm_mm))
+ vma->vm_private_data = NULL;
+}
+
+static unsigned int sgx_vma_fault(struct vm_fault *vmf)
+{
+ unsigned long addr = (unsigned long)vmf->address;
+ struct vm_area_struct *vma = vmf->vma;
+ struct sgx_encl *encl = vma->vm_private_data;
+ struct sgx_encl_page *entry;
+ int ret = VM_FAULT_NOPAGE;
+ unsigned long pfn;
+
+ if (!encl)
+ return VM_FAULT_SIGBUS;
+
+ mutex_lock(&encl->lock);
+
+ entry = sgx_encl_load_page(encl, addr);
+ if (IS_ERR(entry)) {
+ if (unlikely(PTR_ERR(entry) != -EBUSY))
+ ret = VM_FAULT_SIGBUS;
+
+ goto out;
+ }
+
+ if (!follow_pfn(vma, addr, &pfn))
+ goto out;
+
+ ret = vmf_insert_pfn(vma, addr, PFN_DOWN(entry->epc_page->desc));
+ if (ret != VM_FAULT_NOPAGE) {
+ ret = VM_FAULT_SIGBUS;
+ goto out;
+ }
+
+out:
+ mutex_unlock(&encl->lock);
+ return ret;
+}
+
+/**
+ * sgx_encl_may_map() - Check if a requested VMA mapping is allowed
+ * @encl: an enclave
+ * @start: lower bound of the address range, inclusive
+ * @end: upper bound of the address range, exclusive
+ * @vm_prot_bits: requested protections of the address range
+ *
+ * Iterate through the enclave pages contained within [@start, @end) to verify
+ * the permissions requested by @vm_prot_bits do not exceed that of any enclave
+ * page to be mapped. Page addresses that do not have an associated enclave
+ * page are interpreted to zero permissions.
+ *
+ * Return:
+ * 0 on success,
+ * -EACCES if VMA permissions exceed enclave page permissions
+ */
+int sgx_encl_may_map(struct sgx_encl *encl, unsigned long start,
+ unsigned long end, unsigned long vm_prot_bits)
+{
+ unsigned long idx, idx_start, idx_end;
+ struct sgx_encl_page *page;
+
+ /*
+ * Disallow RIE tasks as their VMA permissions might conflict with the
+ * enclave page permissions.
+ */
+ if (!!(current->personality & READ_IMPLIES_EXEC))
+ return -EACCES;
+
+ /* PROT_NONE always succeeds. */
+ if (!vm_prot_bits)
+ return 0;
+
+ idx_start = PFN_DOWN(start);
+ idx_end = PFN_DOWN(end - 1);
+
+ for (idx = idx_start; idx <= idx_end; ++idx) {
+ mutex_lock(&encl->lock);
+ page = radix_tree_lookup(&encl->page_tree, idx);
+ mutex_unlock(&encl->lock);
+
+ if (!page || (~page->vm_max_prot_bits & vm_prot_bits))
+ return -EACCES;
+ }
+
+ return 0;
+}
+
+static int sgx_vma_mprotect(struct vm_area_struct *vma, unsigned long start,
+ unsigned long end, unsigned long prot)
+{
+ return sgx_encl_may_map(vma->vm_private_data, start, end,
+ calc_vm_prot_bits(prot, 0));
+}
+
+const struct vm_operations_struct sgx_vm_ops = {
+ .open = sgx_vma_open,
+ .fault = sgx_vma_fault,
+ .may_mprotect = sgx_vma_mprotect,
+};
+
+/**
+ * sgx_encl_find - find an enclave
+ * @mm: mm struct of the current process
+ * @addr: address in the ELRANGE
+ * @vma: the resulting VMA
+ *
+ * Find an enclave identified by the given address. Give back a VMA that is
+ * part of the enclave and located in that address. The VMA is given back if it
+ * is a proper enclave VMA even if an &sgx_encl instance does not exist yet
+ * (enclave creation has not been performed).
+ *
+ * Return:
+ * 0 on success,
+ * -EINVAL if an enclave was not found,
+ * -ENOENT if the enclave has not been created yet
+ */
+int sgx_encl_find(struct mm_struct *mm, unsigned long addr,
+ struct vm_area_struct **vma)
+{
+ struct vm_area_struct *result;
+ struct sgx_encl *encl;
+
+ result = find_vma(mm, addr);
+ if (!result || result->vm_ops != &sgx_vm_ops || addr < result->vm_start)
+ return -EINVAL;
+
+ encl = result->vm_private_data;
+ *vma = result;
+
+ return encl ? 0 : -ENOENT;
+}
+
+/**
+ * sgx_encl_destroy() - destroy enclave resources
+ * @encl: an &sgx_encl instance
+ */
+void sgx_encl_destroy(struct sgx_encl *encl)
+{
+ struct sgx_encl_page *entry;
+ struct radix_tree_iter iter;
+ void **slot;
+
+ atomic_or(SGX_ENCL_DEAD, &encl->flags);
+
+ radix_tree_for_each_slot(slot, &encl->page_tree, &iter, 0) {
+ entry = *slot;
+
+ if (entry->epc_page) {
+ sgx_free_page(entry->epc_page);
+ encl->secs_child_cnt--;
+ entry->epc_page = NULL;
+ }
+
+ radix_tree_delete(&entry->encl->page_tree,
+ PFN_DOWN(entry->desc));
+ kfree(entry);
+ }
+
+ if (!encl->secs_child_cnt && encl->secs.epc_page) {
+ sgx_free_page(encl->secs.epc_page);
+ encl->secs.epc_page = NULL;
+ }
+}
+
+/**
+ * sgx_encl_release - Destroy an enclave instance
+ * @kref: address of a kref inside &sgx_encl
+ *
+ * Used together with kref_put(). Frees all the resources associated with the
+ * enclave and the instance itself.
+ */
+void sgx_encl_release(struct kref *ref)
+{
+ struct sgx_encl *encl = container_of(ref, struct sgx_encl, refcount);
+
+ sgx_encl_destroy(encl);
+
+ if (encl->backing)
+ fput(encl->backing);
+
+ WARN_ON_ONCE(!list_empty(&encl->mm_list));
+
+ /* Detect EPC page leak's. */
+ WARN_ON_ONCE(encl->secs_child_cnt);
+ WARN_ON_ONCE(encl->secs.epc_page);
+
+ kfree(encl);
+}
diff --git a/arch/x86/kernel/cpu/sgx/encl.h b/arch/x86/kernel/cpu/sgx/encl.h
new file mode 100644
index 000000000000..1d1bc5d590ee
--- /dev/null
+++ b/arch/x86/kernel/cpu/sgx/encl.h
@@ -0,0 +1,87 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/**
+ * Copyright(c) 2016-19 Intel Corporation.
+ */
+#ifndef _X86_ENCL_H
+#define _X86_ENCL_H
+
+#include <linux/cpumask.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/mm_types.h>
+#include <linux/mmu_notifier.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/radix-tree.h>
+#include <linux/srcu.h>
+#include <linux/workqueue.h>
+#include "sgx.h"
+
+/**
+ * enum sgx_encl_page_desc - defines bits for an enclave page's descriptor
+ * %SGX_ENCL_PAGE_ADDR_MASK: Holds the virtual address of the page.
+ *
+ * The page address for SECS is zero and is used by the subsystem to recognize
+ * the SECS page.
+ */
+enum sgx_encl_page_desc {
+ /* Bits 11:3 are available when the page is not swapped. */
+ SGX_ENCL_PAGE_ADDR_MASK = PAGE_MASK,
+};
+
+#define SGX_ENCL_PAGE_ADDR(page) \
+ ((page)->desc & SGX_ENCL_PAGE_ADDR_MASK)
+
+struct sgx_encl_page {
+ unsigned long desc;
+ unsigned long vm_max_prot_bits;
+ struct sgx_epc_page *epc_page;
+ struct sgx_encl *encl;
+};
+
+enum sgx_encl_flags {
+ SGX_ENCL_CREATED = BIT(0),
+ SGX_ENCL_INITIALIZED = BIT(1),
+ SGX_ENCL_DEBUG = BIT(2),
+ SGX_ENCL_DEAD = BIT(3),
+ SGX_ENCL_IOCTL = BIT(4),
+};
+
+struct sgx_encl_mm {
+ struct sgx_encl *encl;
+ struct mm_struct *mm;
+ struct list_head list;
+ struct mmu_notifier mmu_notifier;
+};
+
+struct sgx_encl {
+ atomic_t flags;
+ u64 secs_attributes;
+ u64 allowed_attributes;
+ unsigned int page_cnt;
+ unsigned int secs_child_cnt;
+ struct mutex lock;
+ struct list_head mm_list;
+ spinlock_t mm_lock;
+ struct file *backing;
+ struct kref refcount;
+ struct srcu_struct srcu;
+ unsigned long base;
+ unsigned long size;
+ unsigned long ssaframesize;
+ struct radix_tree_root page_tree;
+ struct sgx_encl_page secs;
+ cpumask_t cpumask;
+};
+
+extern const struct vm_operations_struct sgx_vm_ops;
+
+int sgx_encl_find(struct mm_struct *mm, unsigned long addr,
+ struct vm_area_struct **vma);
+void sgx_encl_destroy(struct sgx_encl *encl);
+void sgx_encl_release(struct kref *ref);
+int sgx_encl_mm_add(struct sgx_encl *encl, struct mm_struct *mm);
+int sgx_encl_may_map(struct sgx_encl *encl, unsigned long start,
+ unsigned long end, unsigned long vm_prot_bits);
+
+#endif /* _X86_ENCL_H */
diff --git a/arch/x86/kernel/cpu/sgx/ioctl.c b/arch/x86/kernel/cpu/sgx/ioctl.c
new file mode 100644
index 000000000000..83513cdfd1c0
--- /dev/null
+++ b/arch/x86/kernel/cpu/sgx/ioctl.c
@@ -0,0 +1,697 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2016-19 Intel Corporation.
+
+#include <asm/mman.h>
+#include <linux/mman.h>
+#include <linux/delay.h>
+#include <linux/file.h>
+#include <linux/hashtable.h>
+#include <linux/highmem.h>
+#include <linux/ratelimit.h>
+#include <linux/sched/signal.h>
+#include <linux/shmem_fs.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include "driver.h"
+#include "encl.h"
+#include "encls.h"
+
+/* A per-cpu cache for the last known values of IA32_SGXLEPUBKEYHASHx MSRs. */
+static DEFINE_PER_CPU(u64 [4], sgx_lepubkeyhash_cache);
+
+static u32 sgx_calc_ssaframesize(u32 miscselect, u64 xfrm)
+{
+ u32 size_max = PAGE_SIZE;
+ u32 size;
+ int i;
+
+ for (i = 2; i < 64; i++) {
+ if (!((1 << i) & xfrm))
+ continue;
+
+ size = SGX_SSA_GPRS_SIZE + sgx_xsave_size_tbl[i];
+ if (miscselect & SGX_MISC_EXINFO)
+ size += SGX_SSA_MISC_EXINFO_SIZE;
+
+ if (size > size_max)
+ size_max = size;
+ }
+
+ return PFN_UP(size_max);
+}
+
+static int sgx_validate_secs(const struct sgx_secs *secs,
+ unsigned long ssaframesize)
+{
+ if (secs->size < (2 * PAGE_SIZE) || !is_power_of_2(secs->size))
+ return -EINVAL;
+
+ if (secs->base & (secs->size - 1))
+ return -EINVAL;
+
+ if (secs->miscselect & sgx_misc_reserved_mask ||
+ secs->attributes & sgx_attributes_reserved_mask ||
+ secs->xfrm & sgx_xfrm_reserved_mask)
+ return -EINVAL;
+
+ if (secs->attributes & SGX_ATTR_MODE64BIT) {
+ if (secs->size > sgx_encl_size_max_64)
+ return -EINVAL;
+ } else if (secs->size > sgx_encl_size_max_32)
+ return -EINVAL;
+
+ if (!(secs->xfrm & XFEATURE_MASK_FP) ||
+ !(secs->xfrm & XFEATURE_MASK_SSE) ||
+ (((secs->xfrm >> XFEATURE_BNDREGS) & 1) !=
+ ((secs->xfrm >> XFEATURE_BNDCSR) & 1)))
+ return -EINVAL;
+
+ if (!secs->ssa_frame_size || ssaframesize > secs->ssa_frame_size)
+ return -EINVAL;
+
+ if (memchr_inv(secs->reserved1, 0, sizeof(secs->reserved1)) ||
+ memchr_inv(secs->reserved2, 0, sizeof(secs->reserved2)) ||
+ memchr_inv(secs->reserved3, 0, sizeof(secs->reserved3)) ||
+ memchr_inv(secs->reserved4, 0, sizeof(secs->reserved4)))
+ return -EINVAL;
+
+ return 0;
+}
+
+static struct sgx_encl_page *sgx_encl_page_alloc(struct sgx_encl *encl,
+ unsigned long offset,
+ u64 secinfo_flags)
+{
+ struct sgx_encl_page *encl_page;
+ unsigned long prot;
+
+ encl_page = kzalloc(sizeof(*encl_page), GFP_KERNEL);
+ if (!encl_page)
+ return ERR_PTR(-ENOMEM);
+
+ encl_page->desc = encl->base + offset;
+ encl_page->encl = encl;
+
+ prot = _calc_vm_trans(secinfo_flags, SGX_SECINFO_R, PROT_READ) |
+ _calc_vm_trans(secinfo_flags, SGX_SECINFO_W, PROT_WRITE) |
+ _calc_vm_trans(secinfo_flags, SGX_SECINFO_X, PROT_EXEC);
+
+ /*
+ * TCS pages must always RW set for CPU access while the SECINFO
+ * permissions are *always* zero - the CPU ignores the user provided
+ * values and silently overwrites them with zero permissions.
+ */
+ if ((secinfo_flags & SGX_SECINFO_PAGE_TYPE_MASK) == SGX_SECINFO_TCS)
+ prot |= PROT_READ | PROT_WRITE;
+
+ /* Calculate maximum of the VM flags for the page. */
+ encl_page->vm_max_prot_bits = calc_vm_prot_bits(prot, 0);
+
+ return encl_page;
+}
+
+static int sgx_encl_create(struct sgx_encl *encl, struct sgx_secs *secs)
+{
+ unsigned long encl_size = secs->size + PAGE_SIZE;
+ struct sgx_epc_page *secs_epc;
+ unsigned long ssaframesize;
+ struct sgx_pageinfo pginfo;
+ struct sgx_secinfo secinfo;
+ struct file *backing;
+ long ret;
+
+ if (atomic_read(&encl->flags) & SGX_ENCL_CREATED)
+ return -EINVAL;
+
+ ssaframesize = sgx_calc_ssaframesize(secs->miscselect, secs->xfrm);
+ if (sgx_validate_secs(secs, ssaframesize)) {
+ pr_debug("invalid SECS\n");
+ return -EINVAL;
+ }
+
+ backing = shmem_file_setup("SGX backing", encl_size + (encl_size >> 5),
+ VM_NORESERVE);
+ if (IS_ERR(backing))
+ return PTR_ERR(backing);
+
+ encl->backing = backing;
+
+ secs_epc = sgx_try_alloc_page();
+ if (IS_ERR(secs_epc)) {
+ ret = PTR_ERR(secs_epc);
+ goto err_out_backing;
+ }
+
+ encl->secs.epc_page = secs_epc;
+
+ pginfo.addr = 0;
+ pginfo.contents = (unsigned long)secs;
+ pginfo.metadata = (unsigned long)&secinfo;
+ pginfo.secs = 0;
+ memset(&secinfo, 0, sizeof(secinfo));
+
+ ret = __ecreate((void *)&pginfo, sgx_epc_addr(secs_epc));
+ if (ret) {
+ pr_debug("ECREATE returned %ld\n", ret);
+ goto err_out;
+ }
+
+ if (secs->attributes & SGX_ATTR_DEBUG)
+ atomic_or(SGX_ENCL_DEBUG, &encl->flags);
+
+ encl->secs.encl = encl;
+ encl->secs_attributes = secs->attributes;
+ encl->allowed_attributes |= SGX_ATTR_ALLOWED_MASK;
+ encl->base = secs->base;
+ encl->size = secs->size;
+ encl->ssaframesize = secs->ssa_frame_size;
+
+ /*
+ * Set SGX_ENCL_CREATED only after the enclave is fully prepped. This
+ * allows setting and checking enclave creation without having to take
+ * encl->lock.
+ */
+ atomic_or(SGX_ENCL_CREATED, &encl->flags);
+
+ return 0;
+
+err_out:
+ sgx_free_page(encl->secs.epc_page);
+ encl->secs.epc_page = NULL;
+
+err_out_backing:
+ fput(encl->backing);
+ encl->backing = NULL;
+
+ return ret;
+}
+
+/**
+ * sgx_ioc_enclave_create - handler for %SGX_IOC_ENCLAVE_CREATE
+ * @filep: open file to /dev/sgx
+ * @arg: userspace pointer to a struct sgx_enclave_create instance
+ *
+ * Allocate kernel data structures for a new enclave and execute ECREATE after
+ * verifying the correctness of the provided SECS.
+ *
+ * Note, enforcement of restricted and disallowed attributes is deferred until
+ * sgx_ioc_enclave_init(), only the architectural correctness of the SECS is
+ * checked by sgx_ioc_enclave_create().
+ *
+ * Return:
+ * 0 on success,
+ * -errno otherwise
+ */
+static long sgx_ioc_enclave_create(struct sgx_encl *encl, void __user *arg)
+{
+ struct sgx_enclave_create ecreate;
+ struct page *secs_page;
+ struct sgx_secs *secs;
+ int ret;
+
+ if (copy_from_user(&ecreate, arg, sizeof(ecreate)))
+ return -EFAULT;
+
+ secs_page = alloc_page(GFP_KERNEL);
+ if (!secs_page)
+ return -ENOMEM;
+
+ secs = kmap(secs_page);
+ if (copy_from_user(secs, (void __user *)ecreate.src, sizeof(*secs))) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ ret = sgx_encl_create(encl, secs);
+
+out:
+ kunmap(secs_page);
+ __free_page(secs_page);
+ return ret;
+}
+
+static int sgx_validate_secinfo(struct sgx_secinfo *secinfo)
+{
+ u64 perm = secinfo->flags & SGX_SECINFO_PERMISSION_MASK;
+ u64 pt = secinfo->flags & SGX_SECINFO_PAGE_TYPE_MASK;
+
+ if (pt != SGX_SECINFO_REG && pt != SGX_SECINFO_TCS)
+ return -EINVAL;
+
+ if ((perm & SGX_SECINFO_W) && !(perm & SGX_SECINFO_R))
+ return -EINVAL;
+
+ /*
+ * CPU will silently overwrite the permissions as zero, which means
+ * that we need to validate it ourselves.
+ */
+ if (pt == SGX_SECINFO_TCS && perm)
+ return -EINVAL;
+
+ if (secinfo->flags & SGX_SECINFO_RESERVED_MASK)
+ return -EINVAL;
+
+ if (memchr_inv(secinfo->reserved, 0, sizeof(secinfo->reserved)))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int __sgx_encl_add_page(struct sgx_encl *encl,
+ struct sgx_encl_page *encl_page,
+ struct sgx_epc_page *epc_page,
+ struct sgx_secinfo *secinfo, unsigned long src)
+{
+ struct sgx_pageinfo pginfo;
+ struct vm_area_struct *vma;
+ struct page *src_page;
+ int ret;
+
+ /* Query vma's VM_MAYEXEC as an indirect path_noexec() check. */
+ if (encl_page->vm_max_prot_bits & VM_EXEC) {
+ vma = find_vma(current->mm, src);
+ if (!vma)
+ return -EFAULT;
+
+ if (!(vma->vm_flags & VM_MAYEXEC))
+ return -EACCES;
+ }
+
+ ret = get_user_pages(src, 1, 0, &src_page, NULL);
+ if (ret < 1)
+ return ret;
+
+ pginfo.secs = (unsigned long)sgx_epc_addr(encl->secs.epc_page);
+ pginfo.addr = SGX_ENCL_PAGE_ADDR(encl_page);
+ pginfo.metadata = (unsigned long)secinfo;
+ pginfo.contents = (unsigned long)kmap_atomic(src_page);
+
+ ret = __eadd(&pginfo, sgx_epc_addr(epc_page));
+
+ kunmap_atomic((void *)pginfo.contents);
+ put_page(src_page);
+
+ return ret ? -EIO : 0;
+}
+
+static int __sgx_encl_extend(struct sgx_encl *encl,
+ struct sgx_epc_page *epc_page)
+{
+ int ret;
+ int i;
+
+ for (i = 0; i < 16; i++) {
+ ret = __eextend(sgx_epc_addr(encl->secs.epc_page),
+ sgx_epc_addr(epc_page) + (i * 0x100));
+ if (ret) {
+ if (encls_failed(ret))
+ ENCLS_WARN(ret, "EEXTEND");
+ return -EIO;
+ }
+ }
+
+ return 0;
+}
+
+static int sgx_encl_add_page(struct sgx_encl *encl, unsigned long src,
+ unsigned long offset, unsigned long length,
+ struct sgx_secinfo *secinfo, unsigned long flags)
+{
+ struct sgx_encl_page *encl_page;
+ struct sgx_epc_page *epc_page;
+ int ret;
+
+ encl_page = sgx_encl_page_alloc(encl, offset, secinfo->flags);
+ if (IS_ERR(encl_page))
+ return PTR_ERR(encl_page);
+
+ epc_page = sgx_try_alloc_page();
+ if (IS_ERR(epc_page)) {
+ kfree(encl_page);
+ return PTR_ERR(epc_page);
+ }
+
+ if (atomic_read(&encl->flags) &
+ (SGX_ENCL_INITIALIZED | SGX_ENCL_DEAD)) {
+ ret = -EFAULT;
+ goto err_out_free;
+ }
+
+ down_read(¤t->mm->mmap_sem);
+ mutex_lock(&encl->lock);
+
+ /*
+ * Insert prior to EADD in case of OOM. EADD modifies MRENCLAVE, i.e.
+ * can't be gracefully unwound, while failure on EADD/EXTEND is limited
+ * to userspace errors (or kernel/hardware bugs).
+ */
+ ret = radix_tree_insert(&encl->page_tree, PFN_DOWN(encl_page->desc),
+ encl_page);
+ if (ret)
+ goto err_out_unlock;
+
+ ret = __sgx_encl_add_page(encl, encl_page, epc_page, secinfo,
+ src);
+ if (ret)
+ goto err_out;
+
+ /*
+ * Complete the "add" before doing the "extend" so that the "add"
+ * isn't in a half-baked state in the extremely unlikely scenario the
+ * the enclave will be destroyed in response to EEXTEND failure.
+ */
+ encl_page->encl = encl;
+ encl_page->epc_page = epc_page;
+ encl->secs_child_cnt++;
+
+ if (flags & SGX_PAGE_MEASURE) {
+ ret = __sgx_encl_extend(encl, epc_page);
+ if (ret)
+ goto err_out;
+ }
+
+ mutex_unlock(&encl->lock);
+ up_read(¤t->mm->mmap_sem);
+ return ret;
+
+err_out:
+ radix_tree_delete(&encl_page->encl->page_tree,
+ PFN_DOWN(encl_page->desc));
+
+err_out_unlock:
+ mutex_unlock(&encl->lock);
+ up_read(¤t->mm->mmap_sem);
+
+err_out_free:
+ sgx_free_page(epc_page);
+ kfree(encl_page);
+
+ /*
+ * Destroy enclave on ENCLS failure as this means that EPC has been
+ * invalidated.
+ */
+ if (ret == -EIO)
+ sgx_encl_destroy(encl);
+
+ return ret;
+}
+
+/**
+ * sgx_ioc_enclave_add_pages() - The handler for %SGX_IOC_ENCLAVE_ADD_PAGES
+ * @encl: pointer to an enclave instance (via ioctl() file pointer)
+ * @arg: a user pointer to a struct sgx_enclave_add_pages instance
+ *
+ * Add one or more pages to an uninitialized enclave, and optionally extend the
+ * measurement with the contents of the page. The address range of pages must
+ * be contiguous. The SECINFO and measurement mask are applied to all pages.
+ *
+ * A SECINFO for a TCS is required to always contain zero permissions because
+ * CPU silently zeros them. Allowing anything else would cause a mismatch in
+ * the measurement.
+ *
+ * mmap()'s protection bits are capped by the page permissions. For each page
+ * address, the maximum protection bits are computed with the following
+ * heuristics:
+ *
+ * 1. A regular page: PROT_R, PROT_W and PROT_X match the SECINFO permissions.
+ * 2. A TCS page: PROT_R | PROT_W.
+ * 3. No page: PROT_NONE.
+ *
+ * mmap() is not allowed to surpass the minimum of the maximum protection bits
+ * within the given address range.
+ *
+ * As stated above, a non-existent page is interpreted as a page with no
+ * permissions. In effect, this allows mmap() with PROT_NONE to be used to seek
+ * an address range for the enclave that can be then populated into SECS.
+ *
+ * If ENCLS opcode fails, that effectively means that EPC has been invalidated.
+ * When this happens the enclave is destroyed and -EIO is returned to the
+ * caller.
+ *
+ * Return:
+ * 0 on success,
+ * -EACCES if an executable source page is located in a noexec partition,
+ * -EIO if either ENCLS[EADD] or ENCLS[EEXTEND] fails
+ * -errno otherwise
+ */
+static long sgx_ioc_enclave_add_pages(struct sgx_encl *encl, void __user *arg)
+{
+ struct sgx_enclave_add_pages addp;
+ struct sgx_secinfo secinfo;
+ unsigned long c;
+ int ret;
+
+ if (!(atomic_read(&encl->flags) & SGX_ENCL_CREATED))
+ return -EINVAL;
+
+ if (copy_from_user(&addp, arg, sizeof(addp)))
+ return -EFAULT;
+
+ if (!IS_ALIGNED(addp.offset, PAGE_SIZE) ||
+ !IS_ALIGNED(addp.src, PAGE_SIZE))
+ return -EINVAL;
+
+ if (!(access_ok(addp.src, PAGE_SIZE)))
+ return -EFAULT;
+
+ if (addp.length & (PAGE_SIZE - 1))
+ return -EINVAL;
+
+ if (addp.offset + addp.length - PAGE_SIZE >= encl->size)
+ return -EINVAL;
+
+ if (copy_from_user(&secinfo, (void __user *)addp.secinfo,
+ sizeof(secinfo)))
+ return -EFAULT;
+
+ if (sgx_validate_secinfo(&secinfo))
+ return -EINVAL;
+
+ for (c = 0 ; c < addp.length; c += PAGE_SIZE) {
+ if (signal_pending(current)) {
+ ret = -EINTR;
+ break;
+ }
+
+ if (need_resched())
+ cond_resched();
+
+ ret = sgx_encl_add_page(encl, addp.src + c, addp.offset + c,
+ addp.length - c, &secinfo, addp.flags);
+ if (ret)
+ break;
+ }
+
+ addp.count = c;
+
+ if (copy_to_user(arg, &addp, sizeof(addp)))
+ return -EFAULT;
+
+ return ret;
+}
+
+static int __sgx_get_key_hash(struct crypto_shash *tfm, const void *modulus,
+ void *hash)
+{
+ SHASH_DESC_ON_STACK(shash, tfm);
+
+ shash->tfm = tfm;
+
+ return crypto_shash_digest(shash, modulus, SGX_MODULUS_SIZE, hash);
+}
+
+static int sgx_get_key_hash(const void *modulus, void *hash)
+{
+ struct crypto_shash *tfm;
+ int ret;
+
+ tfm = crypto_alloc_shash("sha256", 0, CRYPTO_ALG_ASYNC);
+ if (IS_ERR(tfm))
+ return PTR_ERR(tfm);
+
+ ret = __sgx_get_key_hash(tfm, modulus, hash);
+
+ crypto_free_shash(tfm);
+ return ret;
+}
+
+static void sgx_update_lepubkeyhash_msrs(u64 *lepubkeyhash, bool enforce)
+{
+ u64 *cache;
+ int i;
+
+ cache = per_cpu(sgx_lepubkeyhash_cache, smp_processor_id());
+ for (i = 0; i < 4; i++) {
+ if (enforce || (lepubkeyhash[i] != cache[i])) {
+ wrmsrl(MSR_IA32_SGXLEPUBKEYHASH0 + i, lepubkeyhash[i]);
+ cache[i] = lepubkeyhash[i];
+ }
+ }
+}
+
+static int sgx_einit(struct sgx_sigstruct *sigstruct,
+ struct sgx_einittoken *token,
+ struct sgx_epc_page *secs, u64 *lepubkeyhash)
+{
+ int ret;
+
+ if (!boot_cpu_has(X86_FEATURE_SGX_LC))
+ return __einit(sigstruct, token, sgx_epc_addr(secs));
+
+ preempt_disable();
+ sgx_update_lepubkeyhash_msrs(lepubkeyhash, false);
+ ret = __einit(sigstruct, token, sgx_epc_addr(secs));
+ if (ret == SGX_INVALID_EINITTOKEN) {
+ sgx_update_lepubkeyhash_msrs(lepubkeyhash, true);
+ ret = __einit(sigstruct, token, sgx_epc_addr(secs));
+ }
+ preempt_enable();
+ return ret;
+}
+
+static int sgx_encl_init(struct sgx_encl *encl, struct sgx_sigstruct *sigstruct,
+ struct sgx_einittoken *token)
+{
+ u64 mrsigner[4];
+ int ret;
+ int i;
+ int j;
+
+ /* Check that the required attributes have been authorized. */
+ if (encl->secs_attributes & ~encl->allowed_attributes)
+ return -EACCES;
+
+ ret = sgx_get_key_hash(sigstruct->modulus, mrsigner);
+ if (ret)
+ return ret;
+
+ mutex_lock(&encl->lock);
+
+ if (atomic_read(&encl->flags) & SGX_ENCL_INITIALIZED) {
+ ret = -EFAULT;
+ goto err_out;
+ }
+
+ for (i = 0; i < SGX_EINIT_SLEEP_COUNT; i++) {
+ for (j = 0; j < SGX_EINIT_SPIN_COUNT; j++) {
+ ret = sgx_einit(sigstruct, token, encl->secs.epc_page,
+ mrsigner);
+ if (ret == SGX_UNMASKED_EVENT)
+ continue;
+ else
+ break;
+ }
+
+ if (ret != SGX_UNMASKED_EVENT)
+ break;
+
+ msleep_interruptible(SGX_EINIT_SLEEP_TIME);
+
+ if (signal_pending(current)) {
+ ret = -ERESTARTSYS;
+ goto err_out;
+ }
+ }
+
+ if (ret & ENCLS_FAULT_FLAG) {
+ if (encls_failed(ret))
+ ENCLS_WARN(ret, "EINIT");
+
+ sgx_encl_destroy(encl);
+ ret = -EFAULT;
+ } else if (ret) {
+ pr_debug("EINIT returned %d\n", ret);
+ ret = -EPERM;
+ } else {
+ atomic_or(SGX_ENCL_INITIALIZED, &encl->flags);
+ }
+
+err_out:
+ mutex_unlock(&encl->lock);
+ return ret;
+}
+
+/**
+ * sgx_ioc_enclave_init - handler for %SGX_IOC_ENCLAVE_INIT
+ *
+ * @filep: open file to /dev/sgx
+ * @arg: userspace pointer to a struct sgx_enclave_init instance
+ *
+ * Flush any outstanding enqueued EADD operations and perform EINIT. The
+ * Launch Enclave Public Key Hash MSRs are rewritten as necessary to match
+ * the enclave's MRSIGNER, which is caculated from the provided sigstruct.
+ *
+ * Return:
+ * 0 on success,
+ * SGX error code on EINIT failure,
+ * -errno otherwise
+ */
+static long sgx_ioc_enclave_init(struct sgx_encl *encl, void __user *arg)
+{
+ struct sgx_einittoken *einittoken;
+ struct sgx_sigstruct *sigstruct;
+ struct sgx_enclave_init einit;
+ struct page *initp_page;
+ int ret;
+
+ if (!(atomic_read(&encl->flags) & SGX_ENCL_CREATED))
+ return -EINVAL;
+
+ if (copy_from_user(&einit, arg, sizeof(einit)))
+ return -EFAULT;
+
+ initp_page = alloc_page(GFP_KERNEL);
+ if (!initp_page)
+ return -ENOMEM;
+
+ sigstruct = kmap(initp_page);
+ einittoken = (struct sgx_einittoken *)
+ ((unsigned long)sigstruct + PAGE_SIZE / 2);
+ memset(einittoken, 0, sizeof(*einittoken));
+
+ if (copy_from_user(sigstruct, (void __user *)einit.sigstruct,
+ sizeof(*sigstruct))) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ ret = sgx_encl_init(encl, sigstruct, einittoken);
+
+out:
+ kunmap(initp_page);
+ __free_page(initp_page);
+ return ret;
+}
+
+
+long sgx_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
+{
+ struct sgx_encl *encl = filep->private_data;
+ int ret, encl_flags;
+
+ encl_flags = atomic_fetch_or(SGX_ENCL_IOCTL, &encl->flags);
+ if (encl_flags & SGX_ENCL_IOCTL)
+ return -EBUSY;
+
+ if (encl_flags & SGX_ENCL_DEAD)
+ return -EFAULT;
+
+ switch (cmd) {
+ case SGX_IOC_ENCLAVE_CREATE:
+ ret = sgx_ioc_enclave_create(encl, (void __user *)arg);
+ break;
+ case SGX_IOC_ENCLAVE_ADD_PAGES:
+ ret = sgx_ioc_enclave_add_pages(encl, (void __user *)arg);
+ break;
+ case SGX_IOC_ENCLAVE_INIT:
+ ret = sgx_ioc_enclave_init(encl, (void __user *)arg);
+ break;
+ default:
+ ret = -ENOIOCTLCMD;
+ break;
+ }
+
+ atomic_andnot(SGX_ENCL_IOCTL, &encl->flags);
+
+ return ret;
+}
diff --git a/arch/x86/kernel/cpu/sgx/main.c b/arch/x86/kernel/cpu/sgx/main.c
index 60d82e7537c8..842f9abba1c0 100644
--- a/arch/x86/kernel/cpu/sgx/main.c
+++ b/arch/x86/kernel/cpu/sgx/main.c
@@ -8,6 +8,7 @@
#include <linux/ratelimit.h>
#include <linux/sched/signal.h>
#include <linux/slab.h>
+#include "driver.h"
#include "encls.h"
struct sgx_epc_section sgx_epc_sections[SGX_MAX_EPC_SECTIONS];
@@ -193,6 +194,8 @@ static bool __init sgx_page_cache_init(void)
static void __init sgx_init(void)
{
+ int ret;
+
if (!boot_cpu_has(X86_FEATURE_SGX))
return;
@@ -202,10 +205,17 @@ static void __init sgx_init(void)
if (!sgx_page_reclaimer_init())
goto err_page_cache;
+ ret = sgx_drv_init();
+ if (ret)
+ goto err_kthread;
+
return;
+err_kthread:
+ kthread_stop(ksgxswapd_tsk);
+
err_page_cache:
sgx_page_cache_teardown();
}
-arch_initcall(sgx_init);
+device_initcall(sgx_init);
diff --git a/arch/x86/kernel/cpu/sgx/reclaim.c b/arch/x86/kernel/cpu/sgx/reclaim.c
index 215371588a25..9e6d3e147aa2 100644
--- a/arch/x86/kernel/cpu/sgx/reclaim.c
+++ b/arch/x86/kernel/cpu/sgx/reclaim.c
@@ -10,6 +10,7 @@
#include <linux/sched/mm.h>
#include <linux/sched/signal.h>
#include "encls.h"
+#include "driver.h"
struct task_struct *ksgxswapd_tsk;
--
2.20.1
^ permalink raw reply related
* [PATCH v27 13/22] x86/sgx: Add provisioning
From: Jarkko Sakkinen @ 2020-02-23 17:25 UTC (permalink / raw)
To: linux-kernel, x86, linux-sgx
Cc: akpm, dave.hansen, sean.j.christopherson, nhorman, npmccallum,
haitao.huang, andriy.shevchenko, tglx, kai.svahn, bp, josh, luto,
kai.huang, rientjes, cedric.xing, puiterwijk, Jarkko Sakkinen,
linux-security-module
In-Reply-To: <20200223172559.6912-1-jarkko.sakkinen@linux.intel.com>
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=a, Size: 5616 bytes --]
In order to provide a mechanism for devilering provisoning rights:
1. Add a new device file /dev/sgx/provision that works as a token for
allowing an enclave to have the provisioning privileges.
2. Add a new ioctl called SGX_IOC_ENCLAVE_SET_ATTRIBUTE that accepts the
following data structure:
struct sgx_enclave_set_attribute {
__u64 addr;
__u64 attribute_fd;
};
A daemon could sit on top of /dev/sgx/provision and send a file
descriptor of this file to a process that needs to be able to provision
enclaves.
The way this API is used is straight-forward. Lets assume that dev_fd is
a handle to /dev/sgx/enclave and prov_fd is a handle to
/dev/sgx/provision. You would allow SGX_IOC_ENCLAVE_CREATE to
initialize an enclave with the PROVISIONKEY attribute by
params.addr = <enclave address>;
params.token_fd = prov_fd;
ioctl(dev_fd, SGX_IOC_ENCLAVE_SET_ATTRIBUTE, ¶ms);
Cc: linux-security-module@vger.kernel.org
Suggested-by: Andy Lutomirski <luto@kernel.org>
Signed-off-by: Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com>
---
arch/x86/include/uapi/asm/sgx.h | 11 ++++++++
arch/x86/kernel/cpu/sgx/driver.c | 14 ++++++++++
arch/x86/kernel/cpu/sgx/driver.h | 2 ++
arch/x86/kernel/cpu/sgx/ioctl.c | 47 ++++++++++++++++++++++++++++++++
4 files changed, 74 insertions(+)
diff --git a/arch/x86/include/uapi/asm/sgx.h b/arch/x86/include/uapi/asm/sgx.h
index 5edb08ab8fd0..57d0d30c79b3 100644
--- a/arch/x86/include/uapi/asm/sgx.h
+++ b/arch/x86/include/uapi/asm/sgx.h
@@ -25,6 +25,8 @@ enum sgx_page_flags {
_IOWR(SGX_MAGIC, 0x01, struct sgx_enclave_add_pages)
#define SGX_IOC_ENCLAVE_INIT \
_IOW(SGX_MAGIC, 0x02, struct sgx_enclave_init)
+#define SGX_IOC_ENCLAVE_SET_ATTRIBUTE \
+ _IOW(SGX_MAGIC, 0x03, struct sgx_enclave_set_attribute)
/**
* struct sgx_enclave_create - parameter structure for the
@@ -63,4 +65,13 @@ struct sgx_enclave_init {
__u64 sigstruct;
};
+/**
+ * struct sgx_enclave_set_attribute - parameter structure for the
+ * %SGX_IOC_ENCLAVE_SET_ATTRIBUTE ioctl
+ * @attribute_fd: file handle of the attribute file in the securityfs
+ */
+struct sgx_enclave_set_attribute {
+ __u64 attribute_fd;
+};
+
#endif /* _UAPI_ASM_X86_SGX_H */
diff --git a/arch/x86/kernel/cpu/sgx/driver.c b/arch/x86/kernel/cpu/sgx/driver.c
index b4aa7b9f8376..d90114cec1c3 100644
--- a/arch/x86/kernel/cpu/sgx/driver.c
+++ b/arch/x86/kernel/cpu/sgx/driver.c
@@ -150,6 +150,13 @@ static struct miscdevice sgx_dev_enclave = {
.fops = &sgx_encl_fops,
};
+static struct miscdevice sgx_dev_provision = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "provision",
+ .nodename = "sgx/provision",
+ .fops = &sgx_provision_fops,
+};
+
int __init sgx_drv_init(void)
{
unsigned int eax, ebx, ecx, edx;
@@ -190,5 +197,12 @@ int __init sgx_drv_init(void)
return ret;
}
+ ret = misc_register(&sgx_dev_provision);
+ if (ret) {
+ pr_err("Creating /dev/sgx/provision failed with %d.\n", ret);
+ misc_deregister(&sgx_dev_enclave);
+ return ret;
+ }
+
return 0;
}
diff --git a/arch/x86/kernel/cpu/sgx/driver.h b/arch/x86/kernel/cpu/sgx/driver.h
index e4063923115b..72747d01c046 100644
--- a/arch/x86/kernel/cpu/sgx/driver.h
+++ b/arch/x86/kernel/cpu/sgx/driver.h
@@ -23,6 +23,8 @@ extern u64 sgx_attributes_reserved_mask;
extern u64 sgx_xfrm_reserved_mask;
extern u32 sgx_xsave_size_tbl[64];
+extern const struct file_operations sgx_provision_fops;
+
long sgx_ioctl(struct file *filep, unsigned int cmd, unsigned long arg);
int sgx_drv_init(void);
diff --git a/arch/x86/kernel/cpu/sgx/ioctl.c b/arch/x86/kernel/cpu/sgx/ioctl.c
index 83513cdfd1c0..262001df3ae4 100644
--- a/arch/x86/kernel/cpu/sgx/ioctl.c
+++ b/arch/x86/kernel/cpu/sgx/ioctl.c
@@ -663,6 +663,50 @@ static long sgx_ioc_enclave_init(struct sgx_encl *encl, void __user *arg)
return ret;
}
+/**
+ * sgx_ioc_enclave_set_attribute - handler for %SGX_IOC_ENCLAVE_SET_ATTRIBUTE
+ * @filep: open file to /dev/sgx
+ * @arg: userspace pointer to a struct sgx_enclave_set_attribute instance
+ *
+ * Mark the enclave as being allowed to access a restricted attribute bit.
+ * The requested attribute is specified via the attribute_fd field in the
+ * provided struct sgx_enclave_set_attribute. The attribute_fd must be a
+ * handle to an SGX attribute file, e.g. “/dev/sgx/provision".
+ *
+ * Failure to explicitly request access to a restricted attribute will cause
+ * sgx_ioc_enclave_init() to fail. Currently, the only restricted attribute
+ * is access to the PROVISION_KEY.
+ *
+ * Note, access to the EINITTOKEN_KEY is disallowed entirely.
+ *
+ * Return: 0 on success, -errno otherwise
+ */
+static long sgx_ioc_enclave_set_attribute(struct sgx_encl *encl,
+ void __user *arg)
+{
+ struct sgx_enclave_set_attribute params;
+ struct file *attribute_file;
+ int ret;
+
+ if (copy_from_user(¶ms, arg, sizeof(params)))
+ return -EFAULT;
+
+ attribute_file = fget(params.attribute_fd);
+ if (!attribute_file)
+ return -EINVAL;
+
+ if (attribute_file->f_op != &sgx_provision_fops) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ encl->allowed_attributes |= SGX_ATTR_PROVISIONKEY;
+ ret = 0;
+
+out:
+ fput(attribute_file);
+ return ret;
+}
long sgx_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
@@ -686,6 +730,9 @@ long sgx_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
case SGX_IOC_ENCLAVE_INIT:
ret = sgx_ioc_enclave_init(encl, (void __user *)arg);
break;
+ case SGX_IOC_ENCLAVE_SET_ATTRIBUTE:
+ ret = sgx_ioc_enclave_set_attribute(encl, (void __user *)arg);
+ break;
default:
ret = -ENOIOCTLCMD;
break;
--
2.20.1
^ permalink raw reply related
* Re: [PATCH bpf-next v4 3/8] bpf: lsm: provide attachment points for BPF LSM programs
From: Alexei Starovoitov @ 2020-02-23 22:08 UTC (permalink / raw)
To: Kees Cook
Cc: Casey Schaufler, KP Singh, LKML, Linux Security Module list,
Alexei Starovoitov, James Morris, bpf, netdev
In-Reply-To: <202002211946.A23A987@keescook>
On Fri, Feb 21, 2020 at 08:22:59PM -0800, Kees Cook wrote:
>
> If I'm understanding this correctly, there are two issues:
>
> 1- BPF needs to be run last due to fexit trampolines (?)
no.
The placement of nop call can be anywhere.
BPF trampoline is automagically converting nop call into a sequence
of directly invoked BPF programs.
No link list traversals and no indirect calls in run-time.
> 2- BPF hooks don't know what may be attached at any given time, so
> ALL LSM hooks need to be universally hooked. THIS turns out to create
> a measurable performance problem in that the cost of the indirect call
> on the (mostly/usually) empty BPF policy is too high.
also no.
> So, trying to avoid the indirect calls is, as you say, an optimization,
> but it might be a needed one due to the other limitations.
I'm convinced that avoiding the cost of retpoline in critical path is a
requirement for any new infrastructure in the kernel.
Not only for security, but for any new infra.
Networking stack converted all such places to conditional calls.
In BPF land we converted indirect calls to direct jumps and direct calls.
It took two years to do so. Adding new indirect calls is not an option.
I'm eagerly waiting for Peter's static_call patches to land to convert
a lot more indirect calls. May be existing LSMs will take advantage
of static_call patches too, but static_call is not an option for BPF.
That's why we introduced BPF trampoline in the last kernel release.
> b) Would there actually be a global benefit to using the static keys
> optimization for other LSMs?
Yes. Just compiling with CONFIG_SECURITY adds "if (hlist_empty)" check
for every hook. Some of those hooks are in critical path. This load+cmp
can be avoided with static_key optimization. I think it's worth doing.
> If static keys are justified for KRSI
I really like that KRSI costs absolutely zero when it's not enabled.
Attaching BPF prog to one hook preserves zero cost for all other hooks.
And when one hook is BPF powered it's using direct call instead of
super expensive retpoline.
Overall this patch set looks good to me. There was a minor issue with prog
accounting. I expect only that bit to be fixed in v5.
^ permalink raw reply
* Re: [PATCH bpf-next v4 3/8] bpf: lsm: provide attachment points for BPF LSM programs
From: Casey Schaufler @ 2020-02-24 16:09 UTC (permalink / raw)
To: Kees Cook
Cc: KP Singh, LKML, Linux Security Module list, Alexei Starovoitov,
James Morris, Casey Schaufler
In-Reply-To: <202002211946.A23A987@keescook>
On 2/21/2020 8:22 PM, Kees Cook wrote:
> On Thu, Feb 20, 2020 at 03:49:05PM -0800, Casey Schaufler wrote:
>> On 2/20/2020 9:52 AM, KP Singh wrote:
>>> From: KP Singh <kpsingh@google.com>
>> Sorry about the heavy list pruning - the original set
>> blows thunderbird up.
> (I've added some people back; I had to dig this thread back out of lkml
> since I didn't get a direct copy...)
>
>>> The BPF LSM programs are implemented as fexit trampolines to avoid the
>>> overhead of retpolines. These programs cannot be attached to security_*
>>> wrappers as there are quite a few security_* functions that do more than
>>> just calling the LSM callbacks.
>>>
>>> This was discussed on the lists in:
>>>
>>> https://lore.kernel.org/bpf/20200123152440.28956-1-kpsingh@chromium.org/T/#m068becce588a0cdf01913f368a97aea4c62d8266
>>>
>>> Adding a NOP callback after all the static LSM callbacks are called has
>>> the following benefits:
>>>
>>> - The BPF programs run at the right stage of the security_* wrappers.
>>> - They run after all the static LSM hooks allowed the operation,
>>> therefore cannot allow an action that was already denied.
>> I still say that the special call-out to BPF is unnecessary.
>> I remain unconvinced by the arguments. You aren't doing anything
>> so special that the general mechanism won't work.
> If I'm understanding this correctly, there are two issues:
>
> 1- BPF needs to be run last due to fexit trampolines (?)
That's my understanding. As you mention below, there are many
ways to skin that cat.
> 2- BPF hooks don't know what may be attached at any given time, so
> ALL LSM hooks need to be universally hooked.
Right. But that's exactly what we had before we switched to
the hook lists for stacking. It was perfectly acceptable, and
was accepted that way, for years. People even objected to it
being changed.
> THIS turns out to create
> a measurable performance problem in that the cost of the indirect call
> on the (mostly/usually) empty BPF policy is too high.
Right. Except that it was deemed acceptable back before stacking.
What has changed?
>
> "1" can be solved a lot of ways, and doesn't seem to be a debated part
> of this series.
>
> "2" is interesting -- it creates a performance problem for EVERYONE that
> builds in this kernel feature, regardless of them using it. Excepting
> SELinux, "traditional" LSMs tends to be relatively sparse in their hooking:
>
> $ grep '^ struct hlist_head' include/linux/lsm_hooks.h | wc -l
> 230
> $ for i in apparmor loadpin lockdown safesetid selinux smack tomoyo yama ; \
> do echo -n "$i " && (cd $i && git grep LSM_HOOK_INIT | wc -l) ; done
> apparmor 68
> loadpin 3
> lockdown 1
> safesetid 2
> selinux 202
> smack 108
> tomoyo 28
> yama 4
>
> So, trying to avoid the indirect calls is, as you say, an optimization,
> but it might be a needed one due to the other limitations.
>
> To me, some questions present themselves:
>
> a) What, exactly, are the performance characteristics of:
> "before"
> "with indirect calls"
> "with static keys optimization"
>
> b) Would there actually be a global benefit to using the static keys
> optimization for other LSMs? (Especially given that they're already
> sparsely populated and policy likely determines utility -- all the
> LSMs would just turn ON all their static keys or turn off ALL their
> static keys depending on having policy loaded.)
>
> If static keys are justified for KRSI (by "a") then it seems the approach
> here should stand. If "b" is also true, then we need an additional
> series to apply this optimization for the other LSMs (but that seems
> distinctly separate from THIS series).
>
^ permalink raw reply
* [RFC PATCH v14 05/10] fs,landlock: Support filesystem access-control
From: Mickaël Salaün @ 2020-02-24 16:02 UTC (permalink / raw)
To: linux-kernel
Cc: Mickaël Salaün, Al Viro, Andy Lutomirski, Arnd Bergmann,
Casey Schaufler, Greg Kroah-Hartman, James Morris, Jann Horn,
Jonathan Corbet, Kees Cook, Michael Kerrisk,
Mickaël Salaün, Serge E . Hallyn, Shuah Khan,
Vincent Dagonneau, kernel-hardening, linux-api, linux-arch,
linux-doc, linux-fsdevel, linux-kselftest, linux-security-module,
x86
In-Reply-To: <20200224160215.4136-1-mic@digikod.net>
Thanks to the Landlock objects and ruleset, it is possible to identify
inodes according to a process' domain. To enable an unprivileged
process to express a file hierarchy, it first needs to open a directory
(or a file) and pass this file descriptor to the kernel through
landlock(2). When checking if a file access request is allowed, we walk
from the requested dentry to the real root, following the different
mount layers. The access to each "tagged" inodes are collected and
ANDed to create an access to the requested file hierarchy. This makes
possible to identify a lot of files without tagging every inodes nor
modifying the filesystem, while still following the view and
understanding the user has from the filesystem.
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: James Morris <jmorris@namei.org>
Cc: Kees Cook <keescook@chromium.org>
Cc: Serge E. Hallyn <serge@hallyn.com>
---
Changes since v11:
* Add back, revamp and make a fully working filesystem access-control
based on paths and inodes.
* Remove the eBPF dependency.
Previous version:
https://lore.kernel.org/lkml/20190721213116.23476-6-mic@digikod.net/
---
MAINTAINERS | 1 +
fs/super.c | 2 +
include/linux/landlock.h | 22 ++
security/landlock/Kconfig | 1 +
security/landlock/Makefile | 2 +-
security/landlock/fs.c | 591 +++++++++++++++++++++++++++++++++++++
security/landlock/fs.h | 42 +++
security/landlock/object.c | 2 +
security/landlock/setup.c | 6 +
security/landlock/setup.h | 2 +
10 files changed, 670 insertions(+), 1 deletion(-)
create mode 100644 include/linux/landlock.h
create mode 100644 security/landlock/fs.c
create mode 100644 security/landlock/fs.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 937257925e65..0c8c2c651b96 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9366,6 +9366,7 @@ L: linux-security-module@vger.kernel.org
W: https://landlock.io
T: git https://github.com/landlock-lsm/linux.git
S: Supported
+F: include/linux/landlock.h
F: include/uapi/linux/landlock.h
F: security/landlock/
K: landlock
diff --git a/fs/super.c b/fs/super.c
index cd352530eca9..4ad6a64a1706 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -34,6 +34,7 @@
#include <linux/cleancache.h>
#include <linux/fscrypt.h>
#include <linux/fsnotify.h>
+#include <linux/landlock.h>
#include <linux/lockdep.h>
#include <linux/user_namespace.h>
#include <linux/fs_context.h>
@@ -454,6 +455,7 @@ void generic_shutdown_super(struct super_block *sb)
evict_inodes(sb);
/* only nonzero refcount inodes can have marks */
fsnotify_sb_delete(sb);
+ landlock_release_inodes(sb);
if (sb->s_dio_done_wq) {
destroy_workqueue(sb->s_dio_done_wq);
diff --git a/include/linux/landlock.h b/include/linux/landlock.h
new file mode 100644
index 000000000000..0fb16d130b0a
--- /dev/null
+++ b/include/linux/landlock.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Landlock LSM - public kernel headers
+ *
+ * Copyright © 2016-2019 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2019 ANSSI
+ */
+
+#ifndef _LINUX_LANDLOCK_H
+#define _LINUX_LANDLOCK_H
+
+#include <linux/fs.h>
+
+#ifdef CONFIG_SECURITY_LANDLOCK
+extern void landlock_release_inodes(struct super_block *sb);
+#else
+static inline void landlock_release_inodes(struct super_block *sb)
+{
+}
+#endif
+
+#endif /* _LINUX_LANDLOCK_H */
diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig
index 4a321d5b3f67..af0593c2a9e5 100644
--- a/security/landlock/Kconfig
+++ b/security/landlock/Kconfig
@@ -3,6 +3,7 @@
config SECURITY_LANDLOCK
bool "Landlock support"
depends on SECURITY
+ select SECURITY_PATH
default n
help
This selects Landlock, a safe sandboxing mechanism. It enables to
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index f1d1eb72fa76..92e3d80ab8ed 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -1,4 +1,4 @@
obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
landlock-y := setup.o object.o ruleset.o \
- cred.o ptrace.o
+ cred.o ptrace.o fs.o
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
new file mode 100644
index 000000000000..7f3bd4fd04bb
--- /dev/null
+++ b/security/landlock/fs.c
@@ -0,0 +1,591 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Filesystem management and hooks
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ */
+
+#include <linux/compiler_types.h>
+#include <linux/dcache.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/landlock.h>
+#include <linux/lsm_hooks.h>
+#include <linux/mman.h>
+#include <linux/mm_types.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+#include <linux/path.h>
+#include <linux/rcupdate.h>
+#include <linux/spinlock.h>
+#include <linux/stat.h>
+#include <linux/types.h>
+#include <linux/uidgid.h>
+#include <linux/workqueue.h>
+#include <uapi/linux/landlock.h>
+
+#include "cred.h"
+#include "fs.h"
+#include "object.h"
+#include "ruleset.h"
+#include "setup.h"
+
+/* Underlying object management */
+
+void landlock_release_inode(struct inode *inode, struct landlock_object *object)
+{
+ /*
+ * A call to landlock_put_object() or release_object() might sleep, but
+ * landlock_release_object() can not sleep because it is called with a
+ * reference to the inode. However, we can still mark this function as
+ * such because this should not bother landlock_release_object()
+ * callers (e.g. landlock_release_inodes()).
+ */
+ might_sleep();
+ /*
+ * We must check that no one else replaced the pinned object in the
+ * window between the reset of @object->underlying_object and now.
+ */
+ spin_lock(&inode->i_lock);
+ if (rcu_access_pointer(inode_landlock(inode)->object) == object)
+ rcu_assign_pointer(inode_landlock(inode)->object, NULL);
+ spin_unlock(&inode->i_lock);
+ /*
+ * Because we first NULL the reference to the object, calling iput()
+ * won't trigger a call to landlock_put_object() (via
+ * put_underlying_object).
+ */
+ iput(inode);
+}
+
+/*
+ * Release the inodes used in a security policy.
+ *
+ * It is much more clean to have a dedicated call in generic_shutdown_super()
+ * than a hacky sb_free_security hook, especially with the locked sb_lock.
+ *
+ * Cf. fsnotify_unmount_inodes()
+ */
+void landlock_release_inodes(struct super_block *sb)
+{
+ struct inode *inode, *next, *iput_inode = NULL;
+
+ if (!landlock_initialized)
+ return;
+
+ spin_lock(&sb->s_inode_list_lock);
+ list_for_each_entry_safe(inode, next, &sb->s_inodes, i_sb_list) {
+ spin_lock(&inode->i_lock);
+ if (inode->i_state & (I_FREEING | I_WILL_FREE | I_NEW)) {
+ spin_unlock(&inode->i_lock);
+ continue;
+ }
+ if (!atomic_read(&inode->i_count)) {
+ spin_unlock(&inode->i_lock);
+ continue;
+ }
+ __iget(inode);
+ spin_unlock(&inode->i_lock);
+ spin_unlock(&sb->s_inode_list_lock);
+ /*
+ * We can now actually put the previous inode, which is not
+ * needed anymore for the loop walk. Because this inode should
+ * only be referenced by Landlock for this super block, iput()
+ * should trigger a call to hook_inode_free_security().
+ */
+ if (iput_inode)
+ iput(iput_inode);
+
+ landlock_release_object(inode_landlock(inode)->object);
+
+ iput_inode = inode;
+ spin_lock(&sb->s_inode_list_lock);
+ }
+ spin_unlock(&sb->s_inode_list_lock);
+ if (iput_inode)
+ iput(iput_inode);
+}
+
+/* Ruleset management */
+
+static struct landlock_object *get_inode_object(struct inode *inode)
+ __acquires(object->usage)
+{
+ struct landlock_object *object, *new_object;
+
+ /* Let's first try a lockless access. */
+ rcu_read_lock();
+ object = landlock_get_object(rcu_dereference(
+ inode_landlock(inode)->object));
+ rcu_read_unlock();
+ if (object)
+ return object;
+
+ __release(object->usage);
+ /*
+ * If there is no object tied to @inode, then create a new one (outside
+ * of a locked block).
+ */
+ new_object = landlock_create_object(LANDLOCK_OBJECT_INODE, inode);
+
+ spin_lock(&inode->i_lock);
+ object = landlock_get_object(rcu_dereference_protected(
+ inode_landlock(inode)->object,
+ lockdep_is_held(&inode->i_lock)));
+ if (unlikely(object)) {
+ /*
+ * Do not try to iput(inode) because it is not held yet.
+ */
+ landlock_drop_object(new_object);
+ } else {
+ __release(object->usage);
+ object = landlock_get_object(new_object);
+ rcu_assign_pointer(inode_landlock(inode)->object, object);
+ /*
+ * @inode will be released by landlock_release_inodes() on its
+ * super-block shutdown.
+ */
+ ihold(inode);
+ }
+ spin_unlock(&inode->i_lock);
+ return object;
+}
+
+/*
+ * @path: Should have been checked by get_path_from_fd().
+ */
+int landlock_append_fs_rule(struct landlock_ruleset *ruleset,
+ struct path *path, u64 access_hierarchy)
+{
+ int err;
+ struct landlock_access access;
+ struct landlock_object *object;
+
+ /*
+ * Checks that @access_hierarchy matches the @ruleset constraints, but
+ * allow empty @access_hierarchy i.e., deny @ruleset->fs_access_mask .
+ */
+ if ((ruleset->fs_access_mask | access_hierarchy) !=
+ ruleset->fs_access_mask)
+ return -EINVAL;
+ /* Transforms relative access rights to absolute ones. */
+ access_hierarchy |= _LANDLOCK_ACCESS_FS_MASK &
+ ~ruleset->fs_access_mask;
+ access.self = access_hierarchy;
+ access.beneath = access_hierarchy;
+ object = get_inode_object(d_backing_inode(path->dentry));
+ mutex_lock(&ruleset->lock);
+ err = landlock_insert_ruleset_rule(ruleset, object, &access, NULL);
+ mutex_unlock(&ruleset->lock);
+ /*
+ * No need to check for an error because landlock_put_object() handles
+ * empty object and will terminate it if necessary.
+ */
+ landlock_put_object(object);
+ return err;
+}
+
+/* Access-control management */
+
+static bool check_access_path_continue(
+ const struct landlock_ruleset *domain,
+ const struct path *path, u32 access_request,
+ const bool check_self, bool *allow)
+{
+ const struct landlock_access *access;
+ bool next = true;
+
+ rcu_read_lock();
+ access = landlock_find_access(domain, rcu_dereference(inode_landlock(
+ d_backing_inode(path->dentry))->object));
+ if (access) {
+ next = ((check_self ? access->self : access->beneath) &
+ access_request) == access_request;
+ *allow = next;
+ }
+ rcu_read_unlock();
+ return next;
+}
+
+static int check_access_path(const struct landlock_ruleset *domain,
+ const struct path *path, u32 access_request)
+{
+ bool allow = false;
+ struct path walker_path;
+
+ if (WARN_ON_ONCE(!path))
+ return 0;
+ /* An access request not handled by the domain should be allowed. */
+ access_request &= domain->fs_access_mask;
+ if (access_request == 0)
+ return 0;
+ walker_path = *path;
+ path_get(&walker_path);
+ if (check_access_path_continue(domain, &walker_path, access_request,
+ true, &allow)) {
+ /*
+ * We need to walk through all the hierarchy to not miss any
+ * relevant restriction. This could be optimized with a future
+ * commit.
+ */
+ do {
+ struct dentry *parent_dentry;
+
+jump_up:
+ /*
+ * Does not work with orphaned/private mounts like
+ * overlayfs layers for now (cf. ovl_path_real() and
+ * ovl_path_open()).
+ */
+ if (walker_path.dentry == walker_path.mnt->mnt_root) {
+ if (follow_up(&walker_path))
+ /* Ignores hidden mount points. */
+ goto jump_up;
+ else
+ /* Stops at the real root. */
+ break;
+ }
+ parent_dentry = dget_parent(walker_path.dentry);
+ dput(walker_path.dentry);
+ walker_path.dentry = parent_dentry;
+ } while (check_access_path_continue(domain, &walker_path,
+ access_request, false, &allow));
+ }
+ path_put(&walker_path);
+ return allow ? 0 : -EACCES;
+}
+
+static inline int current_check_access_path(const struct path *path,
+ u32 access_request)
+{
+ struct landlock_ruleset *dom;
+
+ dom = landlock_get_current_domain();
+ if (!dom)
+ return 0;
+ return check_access_path(dom, path, access_request);
+}
+
+/* Super-block hooks */
+
+/*
+ * Because a Landlock security policy is defined according to the filesystem
+ * layout (i.e. the mount namespace), changing it may grant access to files not
+ * previously allowed.
+ *
+ * To make it simple, deny any filesystem layout modification by landlocked
+ * processes. Non-landlocked processes may still change the namespace of a
+ * landlocked process, but this kind of threat must be handled by a system-wide
+ * access-control security policy.
+ *
+ * This could be lifted in the future if Landlock can safely handle mount
+ * namespace updates requested by a landlocked process. Indeed, we could
+ * update the current domain (which is currently read-only) by taking into
+ * account the accesses of the source and the destination of a new mount point.
+ * However, it would also require to make all the child domains dynamically
+ * inherit these new constraints. Anyway, for backward compatibility reasons,
+ * a dedicated user space option would be required (e.g. as a ruleset command
+ * option).
+ */
+static int hook_sb_mount(const char *dev_name, const struct path *path,
+ const char *type, unsigned long flags, void *data)
+{
+ if (!landlock_get_current_domain())
+ return 0;
+ return -EPERM;
+}
+
+static int hook_move_mount(const struct path *from_path,
+ const struct path *to_path)
+{
+ if (!landlock_get_current_domain())
+ return 0;
+ return -EPERM;
+}
+
+/*
+ * Removing a mount point may reveal a previously hidden file hierarchy, which
+ * may then grant access to files, which may have previously been forbidden.
+ */
+static int hook_sb_umount(struct vfsmount *mnt, int flags)
+{
+ if (!landlock_get_current_domain())
+ return 0;
+ return -EPERM;
+}
+
+static int hook_sb_remount(struct super_block *sb, void *mnt_opts)
+{
+ if (!landlock_get_current_domain())
+ return 0;
+ return -EPERM;
+}
+
+/*
+ * pivot_root(2), like mount(2), changes the current mount namespace. It must
+ * then be forbidden for a landlocked process.
+ *
+ * However, chroot(2) may be allowed because it only changes the relative root
+ * directory of the current process.
+ */
+static int hook_sb_pivotroot(const struct path *old_path,
+ const struct path *new_path)
+{
+ if (!landlock_get_current_domain())
+ return 0;
+ return -EPERM;
+}
+
+/* Path hooks */
+
+static int hook_path_link(struct dentry *old_dentry,
+ const struct path *new_dir, struct dentry *new_dentry)
+{
+ return current_check_access_path(new_dir, LANDLOCK_ACCESS_FS_LINK_TO);
+}
+
+static int hook_path_mkdir(const struct path *dir, struct dentry *dentry,
+ umode_t mode)
+{
+ return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_DIR);
+}
+
+static inline u32 get_mode_access(umode_t mode)
+{
+ switch (mode & S_IFMT) {
+ case S_IFLNK:
+ return LANDLOCK_ACCESS_FS_MAKE_SYM;
+ case S_IFREG:
+ return LANDLOCK_ACCESS_FS_MAKE_REG;
+ case S_IFDIR:
+ return LANDLOCK_ACCESS_FS_MAKE_DIR;
+ case S_IFCHR:
+ return LANDLOCK_ACCESS_FS_MAKE_CHAR;
+ case S_IFBLK:
+ return LANDLOCK_ACCESS_FS_MAKE_BLOCK;
+ case S_IFIFO:
+ return LANDLOCK_ACCESS_FS_MAKE_FIFO;
+ case S_IFSOCK:
+ return LANDLOCK_ACCESS_FS_MAKE_SOCK;
+ default:
+ WARN_ON_ONCE(1);
+ return 0;
+ }
+}
+
+static int hook_path_mknod(const struct path *dir, struct dentry *dentry,
+ umode_t mode, unsigned int dev)
+{
+ return current_check_access_path(dir, get_mode_access(mode));
+}
+
+static int hook_path_symlink(const struct path *dir, struct dentry *dentry,
+ const char *old_name)
+{
+ return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_SYM);
+}
+
+static int hook_path_truncate(const struct path *path)
+{
+ return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE);
+}
+
+static int hook_path_unlink(const struct path *dir, struct dentry *dentry)
+{
+ return current_check_access_path(dir, LANDLOCK_ACCESS_FS_UNLINK);
+}
+
+static int hook_path_rmdir(const struct path *dir, struct dentry *dentry)
+{
+ return current_check_access_path(dir, LANDLOCK_ACCESS_FS_RMDIR);
+}
+
+static int hook_path_rename(const struct path *old_dir,
+ struct dentry *old_dentry, const struct path *new_dir,
+ struct dentry *new_dentry)
+{
+ struct landlock_ruleset *dom;
+ int err;
+
+ dom = landlock_get_current_domain();
+ if (!dom)
+ return 0;
+ err = check_access_path(dom, old_dir, LANDLOCK_ACCESS_FS_RENAME_FROM);
+ if (err)
+ return err;
+ return check_access_path(dom, new_dir, LANDLOCK_ACCESS_FS_RENAME_TO);
+}
+
+static int hook_path_chmod(const struct path *path, umode_t mode)
+{
+ return current_check_access_path(path, LANDLOCK_ACCESS_FS_CHMOD);
+}
+
+static int hook_path_chown(const struct path *path, kuid_t uid, kgid_t gid)
+{
+ struct landlock_ruleset *dom;
+ int err;
+
+ dom = landlock_get_current_domain();
+ if (!dom)
+ return 0;
+ if (uid_valid(uid)) {
+ err = check_access_path(dom, path, LANDLOCK_ACCESS_FS_CHOWN);
+ if (err)
+ return err;
+ }
+ if (gid_valid(gid)) {
+ err = check_access_path(dom, path, LANDLOCK_ACCESS_FS_CHGRP);
+ if (err)
+ return err;
+ }
+ return 0;
+}
+
+static int hook_path_chroot(const struct path *path)
+{
+ return current_check_access_path(path, LANDLOCK_ACCESS_FS_CHROOT);
+}
+
+/* Inode hooks */
+
+static int hook_inode_alloc_security(struct inode *inode)
+{
+ inode_landlock(inode)->object = NULL;
+ return 0;
+}
+
+static void hook_inode_free_security(struct inode *inode)
+{
+ WARN_ON_ONCE(rcu_access_pointer(inode_landlock(inode)->object));
+}
+
+static int hook_inode_getattr(const struct path *path)
+{
+ return current_check_access_path(path, LANDLOCK_ACCESS_FS_GETATTR);
+}
+
+/* File hooks */
+
+static inline u32 get_file_access(const struct file *file)
+{
+ u32 access = 0;
+
+ if (file->f_mode & FMODE_READ) {
+ /* A directory can only be opened in read mode. */
+ if (S_ISDIR(file_inode(file)->i_mode))
+ access |= LANDLOCK_ACCESS_FS_READDIR;
+ else
+ access |= LANDLOCK_ACCESS_FS_READ;
+ }
+ /*
+ * A LANDLOCK_ACCESS_FS_APPEND could be added be we also need to check
+ * fcntl(2).
+ */
+ if (file->f_mode & FMODE_WRITE)
+ access |= LANDLOCK_ACCESS_FS_WRITE;
+ /* __FMODE_EXEC is indeed part of f_flags, not f_mode. */
+ if (file->f_flags & __FMODE_EXEC)
+ access |= LANDLOCK_ACCESS_FS_EXECUTE;
+ return access;
+}
+
+static int hook_file_open(struct file *file)
+{
+ if (WARN_ON_ONCE(!file))
+ return 0;
+ if (!file_inode(file))
+ return -ENOENT;
+ return current_check_access_path(&file->f_path,
+ LANDLOCK_ACCESS_FS_OPEN | get_file_access(file));
+}
+
+static inline u32 get_mem_access(unsigned long prot, bool private)
+{
+ u32 access = LANDLOCK_ACCESS_FS_MAP;
+
+ /* Private mapping do not write to files. */
+ if (!private && (prot & PROT_WRITE))
+ access |= LANDLOCK_ACCESS_FS_WRITE;
+ if (prot & PROT_READ)
+ access |= LANDLOCK_ACCESS_FS_READ;
+ if (prot & PROT_EXEC)
+ access |= LANDLOCK_ACCESS_FS_EXECUTE;
+ return access;
+}
+
+static int hook_mmap_file(struct file *file, unsigned long reqprot,
+ unsigned long prot, unsigned long flags)
+{
+ /* @file can be null for anonymous mmap. */
+ if (!file)
+ return 0;
+ return current_check_access_path(&file->f_path,
+ get_mem_access(prot, flags & MAP_PRIVATE));
+}
+
+static int hook_file_mprotect(struct vm_area_struct *vma,
+ unsigned long reqprot, unsigned long prot)
+{
+ if (WARN_ON_ONCE(!vma))
+ return 0;
+ if (!vma->vm_file)
+ return 0;
+ return current_check_access_path(&vma->vm_file->f_path,
+ get_mem_access(prot, !(vma->vm_flags & VM_SHARED)));
+}
+
+static int hook_file_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ if (WARN_ON_ONCE(!file))
+ return 0;
+ return current_check_access_path(&file->f_path,
+ LANDLOCK_ACCESS_FS_IOCTL);
+}
+
+static int hook_file_lock(struct file *file, unsigned int cmd)
+{
+ if (WARN_ON_ONCE(!file))
+ return 0;
+ return current_check_access_path(&file->f_path,
+ LANDLOCK_ACCESS_FS_LOCK);
+}
+
+static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = {
+ LSM_HOOK_INIT(sb_mount, hook_sb_mount),
+ LSM_HOOK_INIT(move_mount, hook_move_mount),
+ LSM_HOOK_INIT(sb_umount, hook_sb_umount),
+ LSM_HOOK_INIT(sb_remount, hook_sb_remount),
+ LSM_HOOK_INIT(sb_pivotroot, hook_sb_pivotroot),
+
+ LSM_HOOK_INIT(path_link, hook_path_link),
+ LSM_HOOK_INIT(path_mkdir, hook_path_mkdir),
+ LSM_HOOK_INIT(path_mknod, hook_path_mknod),
+ LSM_HOOK_INIT(path_symlink, hook_path_symlink),
+ LSM_HOOK_INIT(path_truncate, hook_path_truncate),
+ LSM_HOOK_INIT(path_unlink, hook_path_unlink),
+ LSM_HOOK_INIT(path_rmdir, hook_path_rmdir),
+ LSM_HOOK_INIT(path_rename, hook_path_rename),
+ LSM_HOOK_INIT(path_chmod, hook_path_chmod),
+ LSM_HOOK_INIT(path_chown, hook_path_chown),
+ LSM_HOOK_INIT(path_chroot, hook_path_chroot),
+
+ LSM_HOOK_INIT(inode_alloc_security, hook_inode_alloc_security),
+ LSM_HOOK_INIT(inode_free_security, hook_inode_free_security),
+ LSM_HOOK_INIT(inode_getattr, hook_inode_getattr),
+
+ LSM_HOOK_INIT(file_open, hook_file_open),
+ LSM_HOOK_INIT(mmap_file, hook_mmap_file),
+ LSM_HOOK_INIT(file_mprotect, hook_file_mprotect),
+ LSM_HOOK_INIT(file_ioctl, hook_file_ioctl),
+ LSM_HOOK_INIT(file_lock, hook_file_lock),
+};
+
+__init void landlock_add_hooks_fs(void)
+{
+ security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
+ LANDLOCK_NAME);
+}
diff --git a/security/landlock/fs.h b/security/landlock/fs.h
new file mode 100644
index 000000000000..5d2ed8a1d4d4
--- /dev/null
+++ b/security/landlock/fs.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Filesystem management and hooks
+ *
+ * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ */
+
+#ifndef _SECURITY_LANDLOCK_FS_H
+#define _SECURITY_LANDLOCK_FS_H
+
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/rcupdate.h>
+
+#include "ruleset.h"
+#include "setup.h"
+
+struct landlock_inode_security {
+ /*
+ * We need an allocated object to be able to safely untie a rule from
+ * an object (i.e. unlink then free a rule), cf. put_rule(). This
+ * object is guarded by the underlying object's lock.
+ */
+ struct landlock_object __rcu *object;
+};
+
+static inline struct landlock_inode_security *inode_landlock(
+ const struct inode *inode)
+{
+ return inode->i_security + landlock_blob_sizes.lbs_inode;
+}
+
+__init void landlock_add_hooks_fs(void);
+
+void landlock_release_inode(struct inode *inode,
+ struct landlock_object *object);
+
+int landlock_append_fs_rule(struct landlock_ruleset *ruleset,
+ struct path *path, u64 actions);
+
+#endif /* _SECURITY_LANDLOCK_FS_H */
diff --git a/security/landlock/object.c b/security/landlock/object.c
index 38fbbb108120..2d373f224989 100644
--- a/security/landlock/object.c
+++ b/security/landlock/object.c
@@ -29,6 +29,7 @@
#include <linux/spinlock.h>
#include <linux/workqueue.h>
+#include "fs.h"
#include "object.h"
struct landlock_object *landlock_create_object(
@@ -138,6 +139,7 @@ static bool release_object(struct landlock_object *object)
switch (object->type) {
case LANDLOCK_OBJECT_INODE:
+ landlock_release_inode(underlying_object, object);
break;
default:
WARN_ON_ONCE(1);
diff --git a/security/landlock/setup.c b/security/landlock/setup.c
index 117afb344da6..93ef2dbe83ae 100644
--- a/security/landlock/setup.c
+++ b/security/landlock/setup.c
@@ -10,11 +10,15 @@
#include <linux/lsm_hooks.h>
#include "cred.h"
+#include "fs.h"
#include "ptrace.h"
#include "setup.h"
+bool landlock_initialized __lsm_ro_after_init = false;
+
struct lsm_blob_sizes landlock_blob_sizes __lsm_ro_after_init = {
.lbs_cred = sizeof(struct landlock_cred_security),
+ .lbs_inode = sizeof(struct landlock_inode_security),
};
static int __init landlock_init(void)
@@ -22,6 +26,8 @@ static int __init landlock_init(void)
pr_info(LANDLOCK_NAME ": Registering hooks\n");
landlock_add_hooks_cred();
landlock_add_hooks_ptrace();
+ landlock_add_hooks_fs();
+ landlock_initialized = true;
return 0;
}
diff --git a/security/landlock/setup.h b/security/landlock/setup.h
index 52eb8d806376..260fd2068b95 100644
--- a/security/landlock/setup.h
+++ b/security/landlock/setup.h
@@ -13,6 +13,8 @@
#define LANDLOCK_NAME "landlock"
+extern bool landlock_initialized;
+
extern struct lsm_blob_sizes landlock_blob_sizes;
#endif /* _SECURITY_LANDLOCK_SETUP_H */
--
2.25.0
^ permalink raw reply related
* [RFC PATCH v14 08/10] selftests/landlock: Add initial tests
From: Mickaël Salaün @ 2020-02-24 16:02 UTC (permalink / raw)
To: linux-kernel
Cc: Mickaël Salaün, Al Viro, Andy Lutomirski, Arnd Bergmann,
Casey Schaufler, Greg Kroah-Hartman, James Morris, Jann Horn,
Jonathan Corbet, Kees Cook, Michael Kerrisk,
Mickaël Salaün, Serge E . Hallyn, Shuah Khan,
Vincent Dagonneau, kernel-hardening, linux-api, linux-arch,
linux-doc, linux-fsdevel, linux-kselftest, linux-security-module,
x86
In-Reply-To: <20200224160215.4136-1-mic@digikod.net>
Test landlock syscall, ptrace hooks semantic and filesystem
access-control.
This is an initial batch, more tests will follow.
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Reviewed-by: Vincent Dagonneau <vincent.dagonneau@ssi.gouv.fr>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: James Morris <jmorris@namei.org>
Cc: Kees Cook <keescook@chromium.org>
Cc: Serge E. Hallyn <serge@hallyn.com>
Cc: Shuah Khan <shuah@kernel.org>
---
Changes since v13:
* Add back the filesystem tests (from v10) and extend them.
* Add tests for the new syscall.
Previous version:
https://lore.kernel.org/lkml/20191104172146.30797-7-mic@digikod.net/
---
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/landlock/.gitignore | 3 +
tools/testing/selftests/landlock/Makefile | 13 +
tools/testing/selftests/landlock/config | 4 +
tools/testing/selftests/landlock/test.h | 40 ++
tools/testing/selftests/landlock/test_base.c | 80 +++
tools/testing/selftests/landlock/test_fs.c | 624 ++++++++++++++++++
.../testing/selftests/landlock/test_ptrace.c | 293 ++++++++
8 files changed, 1058 insertions(+)
create mode 100644 tools/testing/selftests/landlock/.gitignore
create mode 100644 tools/testing/selftests/landlock/Makefile
create mode 100644 tools/testing/selftests/landlock/config
create mode 100644 tools/testing/selftests/landlock/test.h
create mode 100644 tools/testing/selftests/landlock/test_base.c
create mode 100644 tools/testing/selftests/landlock/test_fs.c
create mode 100644 tools/testing/selftests/landlock/test_ptrace.c
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 6ec503912bea..5183f269c244 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -24,6 +24,7 @@ TARGETS += ir
TARGETS += kcmp
TARGETS += kexec
TARGETS += kvm
+TARGETS += landlock
TARGETS += lib
TARGETS += livepatch
TARGETS += lkdtm
diff --git a/tools/testing/selftests/landlock/.gitignore b/tools/testing/selftests/landlock/.gitignore
new file mode 100644
index 000000000000..4ee53c733af0
--- /dev/null
+++ b/tools/testing/selftests/landlock/.gitignore
@@ -0,0 +1,3 @@
+/test_base
+/test_fs
+/test_ptrace
diff --git a/tools/testing/selftests/landlock/Makefile b/tools/testing/selftests/landlock/Makefile
new file mode 100644
index 000000000000..c7e26e1251c4
--- /dev/null
+++ b/tools/testing/selftests/landlock/Makefile
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+
+test_src := $(wildcard test_*.c)
+
+TEST_GEN_PROGS := $(test_src:.c=)
+
+usr_include := ../../../../usr/include
+
+CFLAGS += -Wall -O2 -I$(usr_include)
+
+include ../lib.mk
+
+$(TEST_GEN_PROGS): ../kselftest_harness.h $(usr_include)/linux/landlock.h
diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
new file mode 100644
index 000000000000..662f72c5a0df
--- /dev/null
+++ b/tools/testing/selftests/landlock/config
@@ -0,0 +1,4 @@
+CONFIG_HEADERS_INSTALL=y
+CONFIG_SECURITY_LANDLOCK=y
+CONFIG_SECURITY_PATH=y
+CONFIG_SECURITY=y
diff --git a/tools/testing/selftests/landlock/test.h b/tools/testing/selftests/landlock/test.h
new file mode 100644
index 000000000000..f9cebd8fc169
--- /dev/null
+++ b/tools/testing/selftests/landlock/test.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Landlock test helpers
+ *
+ * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2019-2020 ANSSI
+ */
+
+#include <errno.h>
+#include <sys/syscall.h>
+
+#include "../kselftest_harness.h"
+
+#ifndef landlock
+static inline int landlock(unsigned int command, unsigned int options,
+ size_t attr_size, void *attr_ptr)
+{
+ errno = 0;
+ return syscall(__NR_landlock, command, options, attr_size, attr_ptr, 0,
+ NULL);
+}
+#endif
+
+FIXTURE(ruleset_rw) {
+ struct landlock_attr_ruleset attr_ruleset;
+ int ruleset_fd;
+};
+
+FIXTURE_SETUP(ruleset_rw) {
+ self->attr_ruleset.handled_access_fs = LANDLOCK_ACCESS_FS_READ |
+ LANDLOCK_ACCESS_FS_WRITE;
+ self->ruleset_fd = landlock(LANDLOCK_CMD_CREATE_RULESET,
+ LANDLOCK_OPT_CREATE_RULESET,
+ sizeof(self->attr_ruleset), &self->attr_ruleset);
+ ASSERT_LE(0, self->ruleset_fd);
+}
+
+FIXTURE_TEARDOWN(ruleset_rw) {
+ ASSERT_EQ(0, close(self->ruleset_fd));
+}
diff --git a/tools/testing/selftests/landlock/test_base.c b/tools/testing/selftests/landlock/test_base.c
new file mode 100644
index 000000000000..1ac7dbead3b2
--- /dev/null
+++ b/tools/testing/selftests/landlock/test_base.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Landlock tests - common resources
+ *
+ * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2019-2020 ANSSI
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/landlock.h>
+#include <sys/prctl.h>
+
+#include "test.h"
+
+#define FDINFO_TEMPLATE "/proc/self/fdinfo/%d"
+#define FDINFO_SIZE 128
+
+#ifndef O_PATH
+#define O_PATH 010000000
+#endif
+
+TEST_F(ruleset_rw, fdinfo)
+{
+ int fdinfo_fd, fdinfo_path_size, fdinfo_buf_size;
+ char fdinfo_path[sizeof(FDINFO_TEMPLATE) + 2];
+ char fdinfo_buf[FDINFO_SIZE];
+
+ fdinfo_path_size = snprintf(fdinfo_path, sizeof(fdinfo_path),
+ FDINFO_TEMPLATE, self->ruleset_fd);
+ ASSERT_LE(fdinfo_path_size, sizeof(fdinfo_path));
+
+ fdinfo_fd = open(fdinfo_path, O_RDONLY | O_CLOEXEC);
+ ASSERT_GE(fdinfo_fd, 0);
+
+ fdinfo_buf_size = read(fdinfo_fd, fdinfo_buf, sizeof(fdinfo_buf));
+ ASSERT_LE(fdinfo_buf_size, sizeof(fdinfo_buf) - 1);
+
+ /*
+ * fdinfo_buf: pos: 0
+ * flags: 02000002
+ * mnt_id: 13
+ * handled_access_fs: 804000
+ */
+ EXPECT_EQ(0, close(fdinfo_fd));
+}
+
+TEST(features)
+{
+ struct landlock_attr_features attr_features = {
+ .options_get_features = ~0ULL,
+ .options_create_ruleset = ~0ULL,
+ .options_add_rule = ~0ULL,
+ .options_enforce_ruleset = ~0ULL,
+ .access_fs = ~0ULL,
+ .size_attr_ruleset = ~0ULL,
+ .size_attr_path_beneath = ~0ULL,
+ };
+
+ ASSERT_EQ(0, landlock(LANDLOCK_CMD_GET_FEATURES,
+ LANDLOCK_OPT_CREATE_RULESET,
+ sizeof(attr_features), &attr_features));
+ ASSERT_EQ(((LANDLOCK_OPT_GET_FEATURES << 1) - 1),
+ attr_features.options_get_features);
+ ASSERT_EQ(((LANDLOCK_OPT_CREATE_RULESET << 1) - 1),
+ attr_features.options_create_ruleset);
+ ASSERT_EQ(((LANDLOCK_OPT_ADD_RULE_PATH_BENEATH << 1) - 1),
+ attr_features.options_add_rule);
+ ASSERT_EQ(((LANDLOCK_OPT_ENFORCE_RULESET << 1) - 1),
+ attr_features.options_enforce_ruleset);
+ ASSERT_EQ(((LANDLOCK_ACCESS_FS_MAP << 1) - 1),
+ attr_features.access_fs);
+ ASSERT_EQ(sizeof(struct landlock_attr_ruleset),
+ attr_features.size_attr_ruleset);
+ ASSERT_EQ(sizeof(struct landlock_attr_path_beneath),
+ attr_features.size_attr_path_beneath);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/landlock/test_fs.c b/tools/testing/selftests/landlock/test_fs.c
new file mode 100644
index 000000000000..627cb3a71f89
--- /dev/null
+++ b/tools/testing/selftests/landlock/test_fs.c
@@ -0,0 +1,624 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Landlock tests - filesystem
+ *
+ * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2020 ANSSI
+ */
+
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <linux/landlock.h>
+#include <sched.h>
+#include <sys/mount.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "test.h"
+
+#define TMP_PREFIX "tmp_"
+
+/* Paths (sibling number and depth) */
+const char dir_s0_d1[] = TMP_PREFIX "a0";
+const char dir_s0_d2[] = TMP_PREFIX "a0/b0";
+const char dir_s0_d3[] = TMP_PREFIX "a0/b0/c0";
+const char dir_s1_d1[] = TMP_PREFIX "a1";
+const char dir_s2_d1[] = TMP_PREFIX "a2";
+const char dir_s2_d2[] = TMP_PREFIX "a2/b2";
+
+/* dir_s3_d1 is a tmpfs mount. */
+const char dir_s3_d1[] = TMP_PREFIX "a3";
+const char dir_s3_d2[] = TMP_PREFIX "a3/b3";
+
+/* dir_s4_d2 is a tmpfs mount. */
+const char dir_s4_d1[] = TMP_PREFIX "a4";
+const char dir_s4_d2[] = TMP_PREFIX "a4/b4";
+
+static void cleanup_layout1(void)
+{
+ rmdir(dir_s2_d2);
+ rmdir(dir_s2_d1);
+ rmdir(dir_s1_d1);
+ rmdir(dir_s0_d3);
+ rmdir(dir_s0_d2);
+ rmdir(dir_s0_d1);
+
+ /* dir_s3_d2 may be bind mounted */
+ umount(dir_s3_d2);
+ rmdir(dir_s3_d2);
+ umount(dir_s3_d1);
+ rmdir(dir_s3_d1);
+
+ umount(dir_s4_d2);
+ rmdir(dir_s4_d2);
+ rmdir(dir_s4_d1);
+}
+
+FIXTURE(layout1) {
+};
+
+FIXTURE_SETUP(layout1)
+{
+ cleanup_layout1();
+
+ /* Do not pollute the rest of the system. */
+ ASSERT_NE(-1, unshare(CLONE_NEWNS));
+
+ ASSERT_EQ(0, mkdir(dir_s0_d1, 0600));
+ ASSERT_EQ(0, mkdir(dir_s0_d2, 0600));
+ ASSERT_EQ(0, mkdir(dir_s0_d3, 0600));
+ ASSERT_EQ(0, mkdir(dir_s1_d1, 0600));
+ ASSERT_EQ(0, mkdir(dir_s2_d1, 0600));
+ ASSERT_EQ(0, mkdir(dir_s2_d2, 0600));
+
+ ASSERT_EQ(0, mkdir(dir_s3_d1, 0600));
+ ASSERT_EQ(0, mount("tmp", dir_s3_d1, "tmpfs", 0, NULL));
+ ASSERT_EQ(0, mkdir(dir_s3_d2, 0600));
+
+ ASSERT_EQ(0, mkdir(dir_s4_d1, 0600));
+ ASSERT_EQ(0, mkdir(dir_s4_d2, 0600));
+ ASSERT_EQ(0, mount("tmp", dir_s4_d2, "tmpfs", 0, NULL));
+}
+
+FIXTURE_TEARDOWN(layout1)
+{
+ /*
+ * cleanup_layout1() would be denied here, use TEST(cleanup) instead.
+ */
+}
+
+static void test_path_rel(struct __test_metadata *_metadata, int dirfd,
+ const char *path, int ret)
+{
+ int fd;
+ struct stat statbuf;
+
+ /* faccessat() can not be restricted for now */
+ ASSERT_EQ(ret, fstatat(dirfd, path, &statbuf, 0)) {
+ TH_LOG("fstatat path \"%s\" returned %s\n", path,
+ strerror(errno));
+ }
+ if (ret) {
+ ASSERT_EQ(EACCES, errno);
+ }
+ fd = openat(dirfd, path, O_DIRECTORY);
+ if (ret) {
+ ASSERT_EQ(-1, fd);
+ ASSERT_EQ(EACCES, errno);
+ } else {
+ ASSERT_NE(-1, fd);
+ EXPECT_EQ(0, close(fd));
+ }
+}
+
+static void test_path(struct __test_metadata *_metadata, const char *path,
+ int ret)
+{
+ return test_path_rel(_metadata, AT_FDCWD, path, ret);
+}
+
+TEST_F(layout1, no_restriction)
+{
+ test_path(_metadata, dir_s0_d1, 0);
+ test_path(_metadata, dir_s0_d2, 0);
+ test_path(_metadata, dir_s0_d3, 0);
+ test_path(_metadata, dir_s1_d1, 0);
+ test_path(_metadata, dir_s2_d2, 0);
+}
+
+TEST_F(ruleset_rw, inval)
+{
+ int err;
+ struct landlock_attr_path_beneath path_beneath = {
+ .allowed_access = LANDLOCK_ACCESS_FS_READ |
+ LANDLOCK_ACCESS_FS_WRITE,
+ .parent_fd = -1,
+ };
+ struct landlock_attr_enforce attr_enforce;
+
+ path_beneath.ruleset_fd = self->ruleset_fd;
+ path_beneath.parent_fd = open(dir_s0_d2, O_PATH | O_NOFOLLOW |
+ O_DIRECTORY | O_CLOEXEC);
+ ASSERT_GE(path_beneath.parent_fd, 0);
+ err = landlock(LANDLOCK_CMD_ADD_RULE,
+ LANDLOCK_OPT_ADD_RULE_PATH_BENEATH,
+ sizeof(path_beneath), &path_beneath);
+ ASSERT_EQ(errno, 0);
+ ASSERT_EQ(err, 0);
+ ASSERT_EQ(0, close(path_beneath.parent_fd));
+
+ /* Tests without O_PATH. */
+ path_beneath.parent_fd = open(dir_s0_d2, O_NOFOLLOW | O_DIRECTORY |
+ O_CLOEXEC);
+ ASSERT_GE(path_beneath.parent_fd, 0);
+ err = landlock(LANDLOCK_CMD_ADD_RULE,
+ LANDLOCK_OPT_ADD_RULE_PATH_BENEATH,
+ sizeof(path_beneath), &path_beneath);
+ ASSERT_EQ(err, -1);
+ ASSERT_EQ(errno, EBADR);
+ errno = 0;
+ ASSERT_EQ(0, close(path_beneath.parent_fd));
+
+ /* Checks un-handled access. */
+ path_beneath.parent_fd = open(dir_s0_d2, O_PATH | O_NOFOLLOW |
+ O_DIRECTORY | O_CLOEXEC);
+ ASSERT_GE(path_beneath.parent_fd, 0);
+ path_beneath.allowed_access |= LANDLOCK_ACCESS_FS_EXECUTE;
+ err = landlock(LANDLOCK_CMD_ADD_RULE,
+ LANDLOCK_OPT_ADD_RULE_PATH_BENEATH,
+ sizeof(path_beneath), &path_beneath);
+ ASSERT_EQ(errno, EINVAL);
+ errno = 0;
+ ASSERT_EQ(err, -1);
+ ASSERT_EQ(0, close(path_beneath.parent_fd));
+
+ err = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+ ASSERT_EQ(errno, 0);
+ ASSERT_EQ(err, 0);
+
+ attr_enforce.ruleset_fd = self->ruleset_fd;
+ err = landlock(LANDLOCK_CMD_ENFORCE_RULESET,
+ LANDLOCK_OPT_ENFORCE_RULESET, sizeof(attr_enforce),
+ &attr_enforce);
+ ASSERT_EQ(errno, 0);
+ ASSERT_EQ(err, 0);
+}
+
+TEST_F(ruleset_rw, nsfs)
+{
+ struct landlock_attr_path_beneath path_beneath = {
+ .allowed_access = LANDLOCK_ACCESS_FS_READ |
+ LANDLOCK_ACCESS_FS_WRITE,
+ .ruleset_fd = self->ruleset_fd,
+ };
+ int err;
+
+ path_beneath.parent_fd = open("/proc/self/ns/mnt", O_PATH | O_NOFOLLOW |
+ O_CLOEXEC);
+ ASSERT_GE(path_beneath.parent_fd, 0);
+ err = landlock(LANDLOCK_CMD_ADD_RULE,
+ LANDLOCK_OPT_ADD_RULE_PATH_BENEATH,
+ sizeof(path_beneath), &path_beneath);
+ ASSERT_EQ(errno, 0);
+ ASSERT_EQ(err, 0);
+ ASSERT_EQ(0, close(path_beneath.parent_fd));
+}
+
+static void add_path_beneath(struct __test_metadata *_metadata, int ruleset_fd,
+ __u64 allowed_access, const char *path)
+{
+ int err;
+ struct landlock_attr_path_beneath path_beneath = {
+ .ruleset_fd = ruleset_fd,
+ .allowed_access = allowed_access,
+ };
+
+ path_beneath.parent_fd = open(path, O_PATH | O_NOFOLLOW | O_DIRECTORY |
+ O_CLOEXEC);
+ ASSERT_GE(path_beneath.parent_fd, 0) {
+ TH_LOG("Failed to open directory \"%s\": %s\n", path,
+ strerror(errno));
+ }
+ err = landlock(LANDLOCK_CMD_ADD_RULE,
+ LANDLOCK_OPT_ADD_RULE_PATH_BENEATH,
+ sizeof(path_beneath), &path_beneath);
+ ASSERT_EQ(err, 0) {
+ TH_LOG("Failed to update the ruleset with \"%s\": %s\n",
+ path, strerror(errno));
+ }
+ ASSERT_EQ(errno, 0);
+ ASSERT_EQ(0, close(path_beneath.parent_fd));
+}
+
+static int create_ruleset(struct __test_metadata *_metadata,
+ const char *const dirs[])
+{
+ int ruleset_fd, dirs_len, i;
+ struct landlock_attr_features attr_features;
+ struct landlock_attr_ruleset attr_ruleset = {
+ .handled_access_fs =
+ LANDLOCK_ACCESS_FS_OPEN |
+ LANDLOCK_ACCESS_FS_READ |
+ LANDLOCK_ACCESS_FS_WRITE |
+ LANDLOCK_ACCESS_FS_EXECUTE |
+ LANDLOCK_ACCESS_FS_GETATTR
+ };
+ __u64 allowed_access =
+ LANDLOCK_ACCESS_FS_OPEN |
+ LANDLOCK_ACCESS_FS_READ |
+ LANDLOCK_ACCESS_FS_GETATTR;
+
+ ASSERT_NE(NULL, dirs) {
+ TH_LOG("No directory list\n");
+ }
+ ASSERT_NE(NULL, dirs[0]) {
+ TH_LOG("Empty directory list\n");
+ }
+ /* Gets the number of dir entries. */
+ for (dirs_len = 0; dirs[dirs_len]; dirs_len++);
+
+ ASSERT_EQ(0, landlock(LANDLOCK_CMD_GET_FEATURES,
+ LANDLOCK_OPT_GET_FEATURES,
+ sizeof(attr_features), &attr_features));
+ /* Only for test, use a binary AND for real application instead. */
+ ASSERT_EQ(attr_ruleset.handled_access_fs,
+ attr_ruleset.handled_access_fs &
+ attr_features.access_fs);
+ ASSERT_EQ(allowed_access, allowed_access & attr_features.access_fs);
+ ruleset_fd = landlock(LANDLOCK_CMD_CREATE_RULESET,
+ LANDLOCK_OPT_CREATE_RULESET, sizeof(attr_ruleset),
+ &attr_ruleset);
+ ASSERT_GE(ruleset_fd, 0) {
+ TH_LOG("Failed to create a ruleset: %s\n", strerror(errno));
+ }
+
+ for (i = 0; dirs[i]; i++) {
+ add_path_beneath(_metadata, ruleset_fd, allowed_access,
+ dirs[i]);
+ }
+ return ruleset_fd;
+}
+
+static void enforce_ruleset(struct __test_metadata *_metadata, int ruleset_fd)
+{
+ struct landlock_attr_enforce attr_enforce = {
+ .ruleset_fd = ruleset_fd,
+ };
+ int err;
+
+ err = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+ ASSERT_EQ(errno, 0);
+ ASSERT_EQ(err, 0);
+
+ err = landlock(LANDLOCK_CMD_ENFORCE_RULESET,
+ LANDLOCK_OPT_ENFORCE_RULESET, sizeof(attr_enforce),
+ &attr_enforce);
+ ASSERT_EQ(err, 0) {
+ TH_LOG("Failed to enforce ruleset: %s\n", strerror(errno));
+ }
+ ASSERT_EQ(errno, 0);
+}
+
+TEST_F(layout1, whitelist)
+{
+ int ruleset_fd = create_ruleset(_metadata,
+ (const char *const []){ dir_s0_d2, dir_s1_d1, NULL });
+ ASSERT_NE(-1, ruleset_fd);
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ test_path(_metadata, "/", -1);
+ test_path(_metadata, dir_s0_d1, -1);
+ test_path(_metadata, dir_s0_d2, 0);
+ test_path(_metadata, dir_s0_d3, 0);
+}
+
+TEST_F(layout1, unhandled_access)
+{
+ int ruleset_fd = create_ruleset(_metadata,
+ (const char *const []){ dir_s0_d2, NULL });
+ ASSERT_NE(-1, ruleset_fd);
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ /*
+ * Because the policy does not handled LANDLOCK_ACCESS_FS_CHROOT,
+ * chroot(2) should be allowed.
+ */
+ ASSERT_EQ(0, chroot(dir_s0_d1));
+ ASSERT_EQ(0, chroot(dir_s0_d2));
+ ASSERT_EQ(0, chroot(dir_s0_d3));
+}
+
+TEST_F(layout1, ruleset_overlap)
+{
+ struct stat statbuf;
+ int open_fd;
+ int ruleset_fd = create_ruleset(_metadata,
+ (const char *const []){ dir_s1_d1, NULL });
+ ASSERT_NE(-1, ruleset_fd);
+ /* These rules should be ORed among them. */
+ add_path_beneath(_metadata, ruleset_fd,
+ LANDLOCK_ACCESS_FS_GETATTR, dir_s0_d2);
+ add_path_beneath(_metadata, ruleset_fd,
+ LANDLOCK_ACCESS_FS_OPEN, dir_s0_d2);
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ ASSERT_EQ(-1, fstatat(AT_FDCWD, dir_s0_d1, &statbuf, 0));
+ ASSERT_EQ(-1, openat(AT_FDCWD, dir_s0_d1, O_DIRECTORY));
+ ASSERT_EQ(0, fstatat(AT_FDCWD, dir_s0_d2, &statbuf, 0));
+ open_fd = openat(AT_FDCWD, dir_s0_d2, O_DIRECTORY);
+ ASSERT_LE(0, open_fd);
+ EXPECT_EQ(0, close(open_fd));
+ ASSERT_EQ(0, fstatat(AT_FDCWD, dir_s0_d3, &statbuf, 0));
+ open_fd = openat(AT_FDCWD, dir_s0_d3, O_DIRECTORY);
+ ASSERT_LE(0, open_fd);
+ EXPECT_EQ(0, close(open_fd));
+}
+
+TEST_F(layout1, inherit_superset)
+{
+ struct stat statbuf;
+ int ruleset_fd, open_fd;
+
+ ruleset_fd = create_ruleset(_metadata,
+ (const char *const []){ dir_s1_d1, NULL });
+ ASSERT_NE(-1, ruleset_fd);
+ add_path_beneath(_metadata, ruleset_fd,
+ LANDLOCK_ACCESS_FS_OPEN, dir_s0_d2);
+ enforce_ruleset(_metadata, ruleset_fd);
+
+ ASSERT_EQ(-1, fstatat(AT_FDCWD, dir_s0_d1, &statbuf, 0));
+ ASSERT_EQ(-1, openat(AT_FDCWD, dir_s0_d1, O_DIRECTORY));
+
+ ASSERT_EQ(-1, fstatat(AT_FDCWD, dir_s0_d2, &statbuf, 0));
+ open_fd = openat(AT_FDCWD, dir_s0_d2, O_DIRECTORY);
+ ASSERT_NE(-1, open_fd);
+ ASSERT_EQ(0, close(open_fd));
+
+ ASSERT_EQ(-1, fstatat(AT_FDCWD, dir_s0_d3, &statbuf, 0));
+ open_fd = openat(AT_FDCWD, dir_s0_d3, O_DIRECTORY);
+ ASSERT_NE(-1, open_fd);
+ ASSERT_EQ(0, close(open_fd));
+
+ /*
+ * Test shared rule extension: the following rules should not grant any
+ * new access, only remove some. Once enforced, these rules are ANDed
+ * with the previous ones.
+ */
+ add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_GETATTR,
+ dir_s0_d2);
+ /*
+ * In ruleset_fd, dir_s0_d2 should now have the LANDLOCK_ACCESS_FS_OPEN
+ * and LANDLOCK_ACCESS_FS_GETATTR access rights (even if this directory
+ * is opened a second time). However, when enforcing this updated
+ * ruleset, the ruleset tied to the current process will still only
+ * have the dir_s0_d2 with LANDLOCK_ACCESS_FS_OPEN access,
+ * LANDLOCK_ACCESS_FS_GETATTR must not be allowed because it would be a
+ * privilege escalation.
+ */
+ enforce_ruleset(_metadata, ruleset_fd);
+
+ /* Same tests and results as above. */
+ ASSERT_EQ(-1, fstatat(AT_FDCWD, dir_s0_d1, &statbuf, 0));
+ ASSERT_EQ(-1, openat(AT_FDCWD, dir_s0_d1, O_DIRECTORY));
+
+ /* It is still forbiden to fstat(dir_s0_d2). */
+ ASSERT_EQ(-1, fstatat(AT_FDCWD, dir_s0_d2, &statbuf, 0));
+ open_fd = openat(AT_FDCWD, dir_s0_d2, O_DIRECTORY);
+ ASSERT_NE(-1, open_fd);
+ ASSERT_EQ(0, close(open_fd));
+
+ ASSERT_EQ(-1, fstatat(AT_FDCWD, dir_s0_d3, &statbuf, 0));
+ open_fd = openat(AT_FDCWD, dir_s0_d3, O_DIRECTORY);
+ ASSERT_NE(-1, open_fd);
+ ASSERT_EQ(0, close(open_fd));
+
+ /*
+ * Now, dir_s0_d3 get a new rule tied to it, only allowing
+ * LANDLOCK_ACCESS_FS_GETATTR. The kernel internal difference is that
+ * there was no rule tied to it before.
+ */
+ add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_GETATTR,
+ dir_s0_d3);
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ /*
+ * Same tests and results as above, except for open(dir_s0_d3) which is
+ * now denied because the new rule mask the rule previously inherited
+ * from dir_s0_d2.
+ */
+ ASSERT_EQ(-1, fstatat(AT_FDCWD, dir_s0_d1, &statbuf, 0));
+ ASSERT_EQ(-1, openat(AT_FDCWD, dir_s0_d1, O_DIRECTORY));
+
+ ASSERT_EQ(-1, fstatat(AT_FDCWD, dir_s0_d2, &statbuf, 0));
+ open_fd = openat(AT_FDCWD, dir_s0_d2, O_DIRECTORY);
+ ASSERT_NE(-1, open_fd);
+ ASSERT_EQ(0, close(open_fd));
+
+ /* It is still forbiden to fstat(dir_s0_d3). */
+ ASSERT_EQ(-1, fstatat(AT_FDCWD, dir_s0_d3, &statbuf, 0));
+ open_fd = openat(AT_FDCWD, dir_s0_d3, O_DIRECTORY);
+ /* open(dir_s0_d3) is now forbidden. */
+ ASSERT_EQ(-1, open_fd);
+ ASSERT_EQ(EACCES, errno);
+}
+
+TEST_F(layout1, extend_ruleset_with_denied_path)
+{
+ struct landlock_attr_path_beneath path_beneath = {
+ .allowed_access = LANDLOCK_ACCESS_FS_GETATTR,
+ };
+
+ path_beneath.ruleset_fd = create_ruleset(_metadata,
+ (const char *const []){ dir_s0_d2, NULL });
+ ASSERT_NE(-1, path_beneath.ruleset_fd);
+ enforce_ruleset(_metadata, path_beneath.ruleset_fd);
+
+ ASSERT_EQ(-1, open(dir_s0_d1, O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC));
+ ASSERT_EQ(EACCES, errno);
+
+ /*
+ * Tests that we can't create a rule for which we are not allowed to
+ * open its path.
+ */
+ path_beneath.parent_fd = open(dir_s0_d1, O_PATH | O_NOFOLLOW
+ | O_DIRECTORY | O_CLOEXEC);
+ ASSERT_GE(path_beneath.parent_fd, 0);
+ ASSERT_EQ(-1, landlock(LANDLOCK_CMD_ADD_RULE,
+ LANDLOCK_OPT_CREATE_RULESET,
+ sizeof(path_beneath), &path_beneath));
+ ASSERT_EQ(EACCES, errno);
+ ASSERT_EQ(0, close(path_beneath.parent_fd));
+ EXPECT_EQ(0, close(path_beneath.ruleset_fd));
+}
+
+TEST_F(layout1, rule_on_mountpoint)
+{
+ int ruleset_fd = create_ruleset(_metadata,
+ (const char *const []){ dir_s0_d1, dir_s3_d1, NULL });
+ ASSERT_NE(-1, ruleset_fd);
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ test_path(_metadata, dir_s1_d1, -1);
+ test_path(_metadata, dir_s0_d1, 0);
+ test_path(_metadata, dir_s3_d1, 0);
+}
+
+TEST_F(layout1, rule_over_mountpoint)
+{
+ int ruleset_fd = create_ruleset(_metadata,
+ (const char *const []){ dir_s4_d1, dir_s0_d1, NULL });
+ ASSERT_NE(-1, ruleset_fd);
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ test_path(_metadata, dir_s4_d2, 0);
+ test_path(_metadata, dir_s0_d1, 0);
+ test_path(_metadata, dir_s4_d1, 0);
+}
+
+/*
+ * This test verifies that we can apply a landlock rule on the root (/), it
+ * might require special handling.
+ */
+TEST_F(layout1, rule_over_root)
+{
+ int ruleset_fd = create_ruleset(_metadata,
+ (const char *const []){ "/", NULL });
+ ASSERT_NE(-1, ruleset_fd);
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ test_path(_metadata, "/", 0);
+ test_path(_metadata, dir_s0_d1, 0);
+}
+
+TEST_F(layout1, rule_inside_mount_ns)
+{
+ ASSERT_NE(-1, mount(NULL, "/", NULL, MS_PRIVATE | MS_REC, NULL));
+ ASSERT_NE(-1, syscall(SYS_pivot_root, dir_s3_d1, dir_s3_d2));
+ ASSERT_NE(-1, chdir("/"));
+
+ int ruleset_fd = create_ruleset(_metadata,
+ (const char *const []){ "b3", NULL });
+ ASSERT_NE(-1, ruleset_fd);
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ test_path(_metadata, "b3", 0);
+ test_path(_metadata, "/", -1);
+}
+
+TEST_F(layout1, mount_and_pivot)
+{
+ int ruleset_fd = create_ruleset(_metadata,
+ (const char *const []){ dir_s3_d1, NULL });
+ ASSERT_NE(-1, ruleset_fd);
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ ASSERT_EQ(-1, mount(NULL, "/", NULL, MS_PRIVATE | MS_REC, NULL));
+ ASSERT_EQ(-1, syscall(SYS_pivot_root, dir_s3_d1, dir_s3_d2));
+}
+
+enum relative_access {
+ REL_OPEN,
+ REL_CHDIR,
+ REL_CHROOT,
+};
+
+static void check_access(struct __test_metadata *_metadata,
+ bool enforce, enum relative_access rel)
+{
+ int dirfd;
+ int ruleset_fd = -1;
+
+ if (enforce) {
+ ruleset_fd = create_ruleset(_metadata, (const char *const []){
+ dir_s0_d2, dir_s1_d1, NULL });
+ ASSERT_NE(-1, ruleset_fd);
+ if (rel == REL_CHROOT)
+ ASSERT_NE(-1, chdir(dir_s0_d2));
+ enforce_ruleset(_metadata, ruleset_fd);
+ } else if (rel == REL_CHROOT)
+ ASSERT_NE(-1, chdir(dir_s0_d2));
+ switch (rel) {
+ case REL_OPEN:
+ dirfd = open(dir_s0_d2, O_DIRECTORY);
+ ASSERT_NE(-1, dirfd);
+ break;
+ case REL_CHDIR:
+ ASSERT_NE(-1, chdir(dir_s0_d2));
+ dirfd = AT_FDCWD;
+ break;
+ case REL_CHROOT:
+ ASSERT_NE(-1, chroot(".")) {
+ TH_LOG("Failed to chroot: %s\n", strerror(errno));
+ }
+ dirfd = AT_FDCWD;
+ break;
+ default:
+ ASSERT_TRUE(false);
+ return;
+ }
+
+ test_path_rel(_metadata, dirfd, "..", (rel == REL_CHROOT) ? 0 : -1);
+ test_path_rel(_metadata, dirfd, ".", 0);
+ if (rel != REL_CHROOT) {
+ test_path_rel(_metadata, dirfd, "./c0", 0);
+ test_path_rel(_metadata, dirfd, "../../" TMP_PREFIX "a1", 0);
+ test_path_rel(_metadata, dirfd, "../../" TMP_PREFIX "a2", -1);
+ }
+
+ if (rel == REL_OPEN)
+ EXPECT_EQ(0, close(dirfd));
+ if (enforce)
+ EXPECT_EQ(0, close(ruleset_fd));
+}
+
+TEST_F(layout1, deny_open)
+{
+ check_access(_metadata, true, REL_OPEN);
+}
+
+TEST_F(layout1, deny_chdir)
+{
+ check_access(_metadata, true, REL_CHDIR);
+}
+
+TEST_F(layout1, deny_chroot)
+{
+ check_access(_metadata, true, REL_CHROOT);
+}
+
+TEST(cleanup)
+{
+ cleanup_layout1();
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/landlock/test_ptrace.c b/tools/testing/selftests/landlock/test_ptrace.c
new file mode 100644
index 000000000000..fcdb41e172d1
--- /dev/null
+++ b/tools/testing/selftests/landlock/test_ptrace.c
@@ -0,0 +1,293 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Landlock tests - ptrace
+ *
+ * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2019-2020 ANSSI
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/landlock.h>
+#include <signal.h>
+#include <sys/prctl.h>
+#include <sys/ptrace.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "test.h"
+
+static void create_domain(struct __test_metadata *_metadata)
+{
+ int ruleset_fd, err;
+ struct landlock_attr_features attr_features;
+ struct landlock_attr_enforce attr_enforce;
+ struct landlock_attr_ruleset attr_ruleset = {
+ .handled_access_fs = LANDLOCK_ACCESS_FS_READ,
+ };
+ struct landlock_attr_path_beneath path_beneath = {
+ .allowed_access = LANDLOCK_ACCESS_FS_READ,
+ };
+
+ ASSERT_EQ(0, landlock(LANDLOCK_CMD_GET_FEATURES,
+ LANDLOCK_OPT_GET_FEATURES,
+ sizeof(attr_features), &attr_features));
+ /* Only for test, use a binary AND for real application instead. */
+ ASSERT_EQ(attr_ruleset.handled_access_fs,
+ attr_ruleset.handled_access_fs &
+ attr_features.access_fs);
+ ruleset_fd = landlock(LANDLOCK_CMD_CREATE_RULESET,
+ LANDLOCK_OPT_CREATE_RULESET, sizeof(attr_ruleset),
+ &attr_ruleset);
+ ASSERT_GE(ruleset_fd, 0) {
+ TH_LOG("Failed to create a ruleset: %s\n", strerror(errno));
+ }
+ path_beneath.ruleset_fd = ruleset_fd;
+ path_beneath.parent_fd = open("/tmp", O_PATH | O_NOFOLLOW | O_DIRECTORY
+ | O_CLOEXEC);
+ ASSERT_GE(path_beneath.parent_fd, 0);
+ err = landlock(LANDLOCK_CMD_ADD_RULE,
+ LANDLOCK_OPT_ADD_RULE_PATH_BENEATH,
+ sizeof(path_beneath), &path_beneath);
+ ASSERT_EQ(err, 0);
+ ASSERT_EQ(errno, 0);
+ ASSERT_EQ(0, close(path_beneath.parent_fd));
+
+ err = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
+ ASSERT_EQ(errno, 0);
+ ASSERT_EQ(err, 0);
+
+ attr_enforce.ruleset_fd = ruleset_fd;
+ err = landlock(LANDLOCK_CMD_ENFORCE_RULESET,
+ LANDLOCK_OPT_ENFORCE_RULESET, sizeof(attr_enforce),
+ &attr_enforce);
+ ASSERT_EQ(err, 0);
+ ASSERT_EQ(errno, 0);
+
+ ASSERT_EQ(0, close(ruleset_fd));
+}
+
+/* test PTRACE_TRACEME and PTRACE_ATTACH for parent and child */
+static void check_ptrace(struct __test_metadata *_metadata,
+ bool domain_both, bool domain_parent, bool domain_child)
+{
+ pid_t child, parent;
+ int status;
+ int pipe_child[2], pipe_parent[2];
+ char buf_parent;
+
+ parent = getpid();
+ ASSERT_EQ(0, pipe(pipe_child));
+ ASSERT_EQ(0, pipe(pipe_parent));
+ if (domain_both)
+ create_domain(_metadata);
+
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ char buf_child;
+
+ EXPECT_EQ(0, close(pipe_parent[1]));
+ EXPECT_EQ(0, close(pipe_child[0]));
+ if (domain_child)
+ create_domain(_metadata);
+
+ /* sync #1 */
+ ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)) {
+ TH_LOG("Failed to read() sync #1 from parent");
+ }
+ ASSERT_EQ('.', buf_child);
+
+ /* Tests the parent protection. */
+ ASSERT_EQ(domain_child ? -1 : 0,
+ ptrace(PTRACE_ATTACH, parent, NULL, 0));
+ if (domain_child) {
+ ASSERT_EQ(EPERM, errno);
+ } else {
+ ASSERT_EQ(parent, waitpid(parent, &status, 0));
+ ASSERT_EQ(1, WIFSTOPPED(status));
+ ASSERT_EQ(0, ptrace(PTRACE_DETACH, parent, NULL, 0));
+ }
+
+ /* sync #2 */
+ ASSERT_EQ(1, write(pipe_child[1], ".", 1)) {
+ TH_LOG("Failed to write() sync #2 to parent");
+ }
+
+ /* Tests traceme. */
+ ASSERT_EQ(domain_parent ? -1 : 0, ptrace(PTRACE_TRACEME));
+ if (domain_parent) {
+ ASSERT_EQ(EPERM, errno);
+ } else {
+ ASSERT_EQ(0, raise(SIGSTOP));
+ }
+
+ /* sync #3 */
+ ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)) {
+ TH_LOG("Failed to read() sync #3 from parent");
+ }
+ ASSERT_EQ('.', buf_child);
+ _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE);
+ }
+
+ EXPECT_EQ(0, close(pipe_child[1]));
+ EXPECT_EQ(0, close(pipe_parent[0]));
+ if (domain_parent)
+ create_domain(_metadata);
+
+ /* sync #1 */
+ ASSERT_EQ(1, write(pipe_parent[1], ".", 1)) {
+ TH_LOG("Failed to write() sync #1 to child");
+ }
+
+ /* Tests the parent protection. */
+ /* sync #2 */
+ ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1)) {
+ TH_LOG("Failed to read() sync #2 from child");
+ }
+ ASSERT_EQ('.', buf_parent);
+
+ /* Tests traceme. */
+ if (!domain_parent) {
+ ASSERT_EQ(child, waitpid(child, &status, 0));
+ ASSERT_EQ(1, WIFSTOPPED(status));
+ ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0));
+ }
+ /* Tests attach. */
+ ASSERT_EQ(domain_parent ? -1 : 0,
+ ptrace(PTRACE_ATTACH, child, NULL, 0));
+ if (domain_parent) {
+ ASSERT_EQ(EPERM, errno);
+ } else {
+ ASSERT_EQ(child, waitpid(child, &status, 0));
+ ASSERT_EQ(1, WIFSTOPPED(status));
+ ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0));
+ }
+
+ /* sync #3 */
+ ASSERT_EQ(1, write(pipe_parent[1], ".", 1)) {
+ TH_LOG("Failed to write() sync #3 to child");
+ }
+ ASSERT_EQ(child, waitpid(child, &status, 0));
+ if (WIFSIGNALED(status) || WEXITSTATUS(status))
+ _metadata->passed = 0;
+}
+
+/*
+ * Test multiple tracing combinations between a parent process P1 and a child
+ * process P2.
+ *
+ * Yama's scoped ptrace is presumed disabled. If enabled, this optional
+ * restriction is enforced in addition to any Landlock check, which means that
+ * all P2 requests to trace P1 would be denied.
+ */
+
+/*
+ * No domain
+ *
+ * P1-. P1 -> P2 : allow
+ * \ P2 -> P1 : allow
+ * 'P2
+ */
+TEST(allow_without_domain) {
+ check_ptrace(_metadata, false, false, false);
+}
+
+/*
+ * Child domain
+ *
+ * P1--. P1 -> P2 : allow
+ * \ P2 -> P1 : deny
+ * .'-----.
+ * | P2 |
+ * '------'
+ */
+TEST(allow_with_one_domain) {
+ check_ptrace(_metadata, false, false, true);
+}
+
+/*
+ * Parent domain
+ * .------.
+ * | P1 --. P1 -> P2 : deny
+ * '------' \ P2 -> P1 : allow
+ * '
+ * P2
+ */
+TEST(deny_with_parent_domain) {
+ check_ptrace(_metadata, false, true, false);
+}
+
+/*
+ * Parent + child domain (siblings)
+ * .------.
+ * | P1 ---. P1 -> P2 : deny
+ * '------' \ P2 -> P1 : deny
+ * .---'--.
+ * | P2 |
+ * '------'
+ */
+TEST(deny_with_sibling_domain) {
+ check_ptrace(_metadata, false, true, true);
+}
+
+/*
+ * Same domain (inherited)
+ * .-------------.
+ * | P1----. | P1 -> P2 : allow
+ * | \ | P2 -> P1 : allow
+ * | ' |
+ * | P2 |
+ * '-------------'
+ */
+TEST(allow_sibling_domain) {
+ check_ptrace(_metadata, true, false, false);
+}
+
+/*
+ * Inherited + child domain
+ * .-----------------.
+ * | P1----. | P1 -> P2 : allow
+ * | \ | P2 -> P1 : deny
+ * | .-'----. |
+ * | | P2 | |
+ * | '------' |
+ * '-----------------'
+ */
+TEST(allow_with_nested_domain) {
+ check_ptrace(_metadata, true, false, true);
+}
+
+/*
+ * Inherited + parent domain
+ * .-----------------.
+ * |.------. | P1 -> P2 : deny
+ * || P1 ----. | P2 -> P1 : allow
+ * |'------' \ |
+ * | ' |
+ * | P2 |
+ * '-----------------'
+ */
+TEST(deny_with_nested_and_parent_domain) {
+ check_ptrace(_metadata, true, true, false);
+}
+
+/*
+ * Inherited + parent and child domain (siblings)
+ * .-----------------.
+ * | .------. | P1 -> P2 : deny
+ * | | P1 . | P2 -> P1 : deny
+ * | '------'\ |
+ * | \ |
+ * | .--'---. |
+ * | | P2 | |
+ * | '------' |
+ * '-----------------'
+ */
+TEST(deny_with_forked_domain) {
+ check_ptrace(_metadata, true, true, true);
+}
+
+TEST_HARNESS_MAIN
--
2.25.0
^ permalink raw reply related
* [RFC PATCH v14 07/10] arch: Wire up landlock() syscall
From: Mickaël Salaün @ 2020-02-24 16:02 UTC (permalink / raw)
To: linux-kernel
Cc: Mickaël Salaün, Al Viro, Andy Lutomirski, Arnd Bergmann,
Casey Schaufler, Greg Kroah-Hartman, James Morris, Jann Horn,
Jonathan Corbet, Kees Cook, Michael Kerrisk,
Mickaël Salaün, Serge E . Hallyn, Shuah Khan,
Vincent Dagonneau, kernel-hardening, linux-api, linux-arch,
linux-doc, linux-fsdevel, linux-kselftest, linux-security-module,
x86
In-Reply-To: <20200224160215.4136-1-mic@digikod.net>
Wire up the landlock() call for x86_64 (for now).
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: James Morris <jmorris@namei.org>
Cc: Kees Cook <keescook@chromium.org>
Cc: Serge E. Hallyn <serge@hallyn.com>
---
Changes since v13:
* New implementation.
---
arch/x86/entry/syscalls/syscall_64.tbl | 1 +
include/uapi/asm-generic/unistd.h | 4 +++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index 44d510bc9b78..3e759505c8bf 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -359,6 +359,7 @@
435 common clone3 __x64_sys_clone3/ptregs
437 common openat2 __x64_sys_openat2
438 common pidfd_getfd __x64_sys_pidfd_getfd
+439 common landlock __x64_sys_landlock
#
# x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/include/uapi/asm-generic/unistd.h b/include/uapi/asm-generic/unistd.h
index 3a3201e4618e..31d5814ddb13 100644
--- a/include/uapi/asm-generic/unistd.h
+++ b/include/uapi/asm-generic/unistd.h
@@ -855,9 +855,11 @@ __SYSCALL(__NR_clone3, sys_clone3)
__SYSCALL(__NR_openat2, sys_openat2)
#define __NR_pidfd_getfd 438
__SYSCALL(__NR_pidfd_getfd, sys_pidfd_getfd)
+#define __NR_landlock 439
+__SYSCALL(__NR_landlock, sys_landlock)
#undef __NR_syscalls
-#define __NR_syscalls 439
+#define __NR_syscalls 440
/*
* 32 bit systems traditionally used different
--
2.25.0
^ permalink raw reply related
* [RFC PATCH v14 00/10] Landlock LSM
From: Mickaël Salaün @ 2020-02-24 16:02 UTC (permalink / raw)
To: linux-kernel
Cc: Mickaël Salaün, Al Viro, Andy Lutomirski, Arnd Bergmann,
Casey Schaufler, Greg Kroah-Hartman, James Morris, Jann Horn,
Jonathan Corbet, Kees Cook, Michael Kerrisk,
Mickaël Salaün, Serge E . Hallyn, Shuah Khan,
Vincent Dagonneau, kernel-hardening, linux-api, linux-arch,
linux-doc, linux-fsdevel, linux-kselftest, linux-security-module,
x86
Hi,
This new version of Landlock is a major revamp of the previous series
[1], hence the RFC tag. The three main changes are the replacement of
eBPF with a dedicated safe management of access rules, the replacement
of the use of seccomp(2) with a dedicated syscall, and the management of
filesystem access-control (back from the v10).
As discussed in [2], eBPF may be too powerful and dangerous to be put in
the hand of unprivileged and potentially malicious processes, especially
because of side-channel attacks against access-controls or other parts
of the kernel.
Thanks to this new implementation (1540 SLOC), designed from the ground
to be used by unprivileged processes, this series enables a process to
sandbox itself without requiring CAP_SYS_ADMIN, but only the
no_new_privs constraint (like seccomp). Not relying on eBPF also
enables to improve performances, especially for stacked security
policies thanks to mergeable rulesets.
The compiled documentation is available here:
https://landlock.io/linux-doc/landlock-v14/security/landlock/index.html
This series can be applied on top of v5.6-rc3. This can be tested with
CONFIG_SECURITY_LANDLOCK and CONFIG_SAMPLE_LANDLOCK. This patch series
can be found in a Git repository here:
https://github.com/landlock-lsm/linux/commits/landlock-v14
I would really appreciate constructive comments on the design and the code.
# Landlock LSM
The goal of Landlock is to enable to restrict ambient rights (e.g.
global filesystem access) for a set of processes. Because Landlock is a
stackable LSM [3], it makes possible to create safe security sandboxes
as new security layers in addition to the existing system-wide
access-controls. This kind of sandbox is expected to help mitigate the
security impact of bugs or unexpected/malicious behaviors in user-space
applications. Landlock empower any process, including unprivileged ones,
to securely restrict themselves.
Landlock is inspired by seccomp-bpf but instead of filtering syscalls
and their raw arguments, a Landlock rule can restrict the use of kernel
objects like file hierarchies, according to the kernel semantic.
Landlock also takes inspiration from other OS sandbox mechanisms: XNU
Sandbox, FreeBSD Capsicum or OpenBSD Pledge/Unveil.
# Current limitations
## Path walk
Landlock need to use dentries to identify a file hierarchy, which is
needed for composable and unprivileged access-controls. This means that
path resolution/walking (handled with inode_permission()) is not
supported, yet. This could be filled with a future extension first of
the LSM framework. The Landlock userspace ABI can handle such change
with new option (e.g. to the struct landlock_ruleset).
## UnionFS
An UnionFS super-block use a set of upper and lower directories. An
access request to a file in one of these hierarchy trigger a call to
ovl_path_real() which generate another access request according to the
matching hierarchy. Because such super-block is not aware of its current
mount point, OverlayFS can't create a dedicated mnt_parent for each of
the upper and lower directories mount clones. It is then not currently
possible to track the source of such indirect access-request, and then
not possible to identify a unified OverlayFS hierarchy.
## Syscall
Because it is only tested on x86_64, the syscall is only wired up for
this architecture. The whole x86 family (and probably all the others)
will be supported in the next patch series.
## Memory limits
There is currently no limit on the memory usage. Any idea to leverage
an existing mechanism (e.g. rlimit)?
# Changes since v13
* Revamp of the LSM: remove the need for eBPF and seccomp(2).
* Implement a full filesystem access-control.
* Take care of the backward compatibility issues, especially for
this security features.
Previous version:
https://lore.kernel.org/lkml/20191104172146.30797-1-mic@digikod.net/
[1] https://lore.kernel.org/lkml/20191104172146.30797-1-mic@digikod.net/
[2] https://lore.kernel.org/lkml/a6b61f33-82dc-0c1c-7a6c-1926343ef63e@digikod.net/
[3] https://lore.kernel.org/lkml/50db058a-7dde-441b-a7f9-f6837fe8b69f@schaufler-ca.com/
Regards,
Mickaël Salaün (10):
landlock: Add object and rule management
landlock: Add ruleset and domain management
landlock: Set up the security framework and manage credentials
landlock: Add ptrace restrictions
fs,landlock: Support filesystem access-control
landlock: Add syscall implementation
arch: Wire up landlock() syscall
selftests/landlock: Add initial tests
samples/landlock: Add a sandbox manager example
landlock: Add user and kernel documentation
Documentation/security/index.rst | 1 +
Documentation/security/landlock/index.rst | 18 +
Documentation/security/landlock/kernel.rst | 44 ++
Documentation/security/landlock/user.rst | 233 +++++++
MAINTAINERS | 12 +
arch/x86/entry/syscalls/syscall_64.tbl | 1 +
fs/super.c | 2 +
include/linux/landlock.h | 22 +
include/linux/syscalls.h | 3 +
include/uapi/asm-generic/unistd.h | 4 +-
include/uapi/linux/landlock.h | 315 +++++++++
samples/Kconfig | 7 +
samples/Makefile | 1 +
samples/landlock/.gitignore | 1 +
samples/landlock/Makefile | 15 +
samples/landlock/sandboxer.c | 226 +++++++
security/Kconfig | 11 +-
security/Makefile | 2 +
security/landlock/Kconfig | 16 +
security/landlock/Makefile | 4 +
security/landlock/cred.c | 47 ++
security/landlock/cred.h | 55 ++
security/landlock/fs.c | 591 +++++++++++++++++
security/landlock/fs.h | 42 ++
security/landlock/object.c | 341 ++++++++++
security/landlock/object.h | 134 ++++
security/landlock/ptrace.c | 118 ++++
security/landlock/ptrace.h | 14 +
security/landlock/ruleset.c | 463 +++++++++++++
security/landlock/ruleset.h | 106 +++
security/landlock/setup.c | 38 ++
security/landlock/setup.h | 20 +
security/landlock/syscall.c | 470 +++++++++++++
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/landlock/.gitignore | 3 +
tools/testing/selftests/landlock/Makefile | 13 +
tools/testing/selftests/landlock/config | 4 +
tools/testing/selftests/landlock/test.h | 40 ++
tools/testing/selftests/landlock/test_base.c | 80 +++
tools/testing/selftests/landlock/test_fs.c | 624 ++++++++++++++++++
.../testing/selftests/landlock/test_ptrace.c | 293 ++++++++
41 files changed, 4429 insertions(+), 6 deletions(-)
create mode 100644 Documentation/security/landlock/index.rst
create mode 100644 Documentation/security/landlock/kernel.rst
create mode 100644 Documentation/security/landlock/user.rst
create mode 100644 include/linux/landlock.h
create mode 100644 include/uapi/linux/landlock.h
create mode 100644 samples/landlock/.gitignore
create mode 100644 samples/landlock/Makefile
create mode 100644 samples/landlock/sandboxer.c
create mode 100644 security/landlock/Kconfig
create mode 100644 security/landlock/Makefile
create mode 100644 security/landlock/cred.c
create mode 100644 security/landlock/cred.h
create mode 100644 security/landlock/fs.c
create mode 100644 security/landlock/fs.h
create mode 100644 security/landlock/object.c
create mode 100644 security/landlock/object.h
create mode 100644 security/landlock/ptrace.c
create mode 100644 security/landlock/ptrace.h
create mode 100644 security/landlock/ruleset.c
create mode 100644 security/landlock/ruleset.h
create mode 100644 security/landlock/setup.c
create mode 100644 security/landlock/setup.h
create mode 100644 security/landlock/syscall.c
create mode 100644 tools/testing/selftests/landlock/.gitignore
create mode 100644 tools/testing/selftests/landlock/Makefile
create mode 100644 tools/testing/selftests/landlock/config
create mode 100644 tools/testing/selftests/landlock/test.h
create mode 100644 tools/testing/selftests/landlock/test_base.c
create mode 100644 tools/testing/selftests/landlock/test_fs.c
create mode 100644 tools/testing/selftests/landlock/test_ptrace.c
--
2.25.0
^ permalink raw reply
* [RFC PATCH v14 09/10] samples/landlock: Add a sandbox manager example
From: Mickaël Salaün @ 2020-02-24 16:02 UTC (permalink / raw)
To: linux-kernel
Cc: Mickaël Salaün, Al Viro, Andy Lutomirski, Arnd Bergmann,
Casey Schaufler, Greg Kroah-Hartman, James Morris, Jann Horn,
Jonathan Corbet, Kees Cook, Michael Kerrisk,
Mickaël Salaün, Serge E . Hallyn, Shuah Khan,
Vincent Dagonneau, kernel-hardening, linux-api, linux-arch,
linux-doc, linux-fsdevel, linux-kselftest, linux-security-module,
x86
In-Reply-To: <20200224160215.4136-1-mic@digikod.net>
Add a basic sandbox tool to launch a command which can only access a
whitelist of file hierarchies in a read-only or read-write way.
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: James Morris <jmorris@namei.org>
Cc: Kees Cook <keescook@chromium.org>
Cc: Serge E. Hallyn <serge@hallyn.com>
---
Changes since v11:
* Add back the filesystem sandbox manager and update it to work with the
new Landlock syscall.
Previous version:
https://lore.kernel.org/lkml/20190721213116.23476-9-mic@digikod.net/
---
samples/Kconfig | 7 ++
samples/Makefile | 1 +
samples/landlock/.gitignore | 1 +
samples/landlock/Makefile | 15 +++
samples/landlock/sandboxer.c | 226 +++++++++++++++++++++++++++++++++++
5 files changed, 250 insertions(+)
create mode 100644 samples/landlock/.gitignore
create mode 100644 samples/landlock/Makefile
create mode 100644 samples/landlock/sandboxer.c
diff --git a/samples/Kconfig b/samples/Kconfig
index 9d236c346de5..5ec43a732b10 100644
--- a/samples/Kconfig
+++ b/samples/Kconfig
@@ -120,6 +120,13 @@ config SAMPLE_HIDRAW
bool "hidraw sample"
depends on HEADERS_INSTALL
+config SAMPLE_LANDLOCK
+ bool "Build Landlock sample code"
+ depends on HEADERS_INSTALL
+ help
+ Build a simple Landlock sandbox manager able to launch a process
+ restricted by a user-defined filesystem access-control security policy.
+
config SAMPLE_PIDFD
bool "pidfd sample"
depends on HEADERS_INSTALL
diff --git a/samples/Makefile b/samples/Makefile
index f8f847b4f61f..61a2bd216f53 100644
--- a/samples/Makefile
+++ b/samples/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_SAMPLE_KDB) += kdb/
obj-$(CONFIG_SAMPLE_KFIFO) += kfifo/
obj-$(CONFIG_SAMPLE_KOBJECT) += kobject/
obj-$(CONFIG_SAMPLE_KPROBES) += kprobes/
+subdir-$(CONFIG_SAMPLE_LANDLOCK) += landlock
obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch/
subdir-$(CONFIG_SAMPLE_PIDFD) += pidfd
obj-$(CONFIG_SAMPLE_QMI_CLIENT) += qmi/
diff --git a/samples/landlock/.gitignore b/samples/landlock/.gitignore
new file mode 100644
index 000000000000..f43668b2d318
--- /dev/null
+++ b/samples/landlock/.gitignore
@@ -0,0 +1 @@
+/sandboxer
diff --git a/samples/landlock/Makefile b/samples/landlock/Makefile
new file mode 100644
index 000000000000..9dfb571641ba
--- /dev/null
+++ b/samples/landlock/Makefile
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: BSD-3-Clause
+
+hostprogs-y := sandboxer
+
+always := $(hostprogs-y)
+
+KBUILD_HOSTCFLAGS += -I$(objtree)/usr/include
+
+.PHONY: all clean
+
+all:
+ $(MAKE) -C ../.. samples/landlock/
+
+clean:
+ $(MAKE) -C ../.. M=samples/landlock/ clean
diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
new file mode 100644
index 000000000000..882c12f71edb
--- /dev/null
+++ b/samples/landlock/sandboxer.c
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: BSD-3-Clause
+/*
+ * Simple Landlock sandbox manager able to launch a process restricted by a
+ * user-defined filesystem access-control security policy.
+ *
+ * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2020 ANSSI
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/landlock.h>
+#include <linux/prctl.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#ifndef landlock
+
+#ifndef __NR_landlock
+#define __NR_landlock 436
+#endif
+
+static inline int landlock(unsigned int command, unsigned int options,
+ size_t attr_size, void *attr_ptr)
+{
+ errno = 0;
+ return syscall(__NR_landlock, command, options, attr_size, attr_ptr, 0,
+ NULL);
+}
+#endif
+
+#define ENV_FS_RO_NAME "LL_FS_RO"
+#define ENV_FS_RW_NAME "LL_FS_RW"
+#define ENV_PATH_TOKEN ":"
+
+static int parse_path(char *env_path, const char ***path_list)
+{
+ int i, path_nb = 0;
+
+ if (env_path) {
+ path_nb++;
+ for (i = 0; env_path[i]; i++) {
+ if (env_path[i] == ENV_PATH_TOKEN[0])
+ path_nb++;
+ }
+ }
+ *path_list = malloc(path_nb * sizeof(**path_list));
+ for (i = 0; i < path_nb; i++)
+ (*path_list)[i] = strsep(&env_path, ENV_PATH_TOKEN);
+
+ return path_nb;
+}
+
+static int populate_ruleset(const struct landlock_attr_features *attr_features,
+ const char *env_var, int ruleset_fd, __u64 allowed_access)
+{
+ int path_nb, i;
+ char *env_path_name;
+ const char **path_list = NULL;
+ struct landlock_attr_path_beneath path_beneath = {
+ .ruleset_fd = ruleset_fd,
+ .allowed_access = allowed_access,
+ .parent_fd = -1,
+ };
+
+ env_path_name = getenv(env_var);
+ if (!env_path_name) {
+ fprintf(stderr, "Missing environment variable %s\n", env_var);
+ return 1;
+ }
+ env_path_name = strdup(env_path_name);
+ unsetenv(env_var);
+ path_nb = parse_path(env_path_name, &path_list);
+ if (path_nb == 1 && path_list[0][0] == '\0') {
+ fprintf(stderr, "Missing path in %s\n", env_var);
+ goto err_free_name;
+ }
+
+ /* follow a best-effort approach */
+ path_beneath.allowed_access &= attr_features->access_fs;
+ for (i = 0; i < path_nb; i++) {
+ path_beneath.parent_fd = open(path_list[i],
+ O_PATH | O_NOFOLLOW | O_CLOEXEC);
+ if (path_beneath.parent_fd < 0) {
+ fprintf(stderr, "Failed to open \"%s\": %s\n",
+ path_list[i],
+ strerror(errno));
+ goto err_free_name;
+ }
+ if (landlock(LANDLOCK_CMD_ADD_RULE,
+ LANDLOCK_OPT_ADD_RULE_PATH_BENEATH,
+ sizeof(path_beneath), &path_beneath)) {
+ fprintf(stderr, "Failed to update the ruleset with \"%s\": %s\n",
+ path_list[i], strerror(errno));
+ close(path_beneath.parent_fd);
+ goto err_free_name;
+ }
+ close(path_beneath.parent_fd);
+ }
+ free(env_path_name);
+ return 0;
+
+err_free_name:
+ free(env_path_name);
+ return 1;
+}
+
+#define ACCESS_FS_ROUGHLY_READ ( \
+ LANDLOCK_ACCESS_FS_READ | \
+ LANDLOCK_ACCESS_FS_READDIR | \
+ LANDLOCK_ACCESS_FS_GETATTR | \
+ LANDLOCK_ACCESS_FS_EXECUTE | \
+ LANDLOCK_ACCESS_FS_CHROOT)
+
+#define ACCESS_FS_ROUGHLY_WRITE ( \
+ LANDLOCK_ACCESS_FS_WRITE | \
+ LANDLOCK_ACCESS_FS_TRUNCATE | \
+ LANDLOCK_ACCESS_FS_LOCK | \
+ LANDLOCK_ACCESS_FS_CHMOD | \
+ LANDLOCK_ACCESS_FS_CHOWN | \
+ LANDLOCK_ACCESS_FS_CHGRP | \
+ LANDLOCK_ACCESS_FS_IOCTL | \
+ LANDLOCK_ACCESS_FS_LINK_TO | \
+ LANDLOCK_ACCESS_FS_RENAME_FROM | \
+ LANDLOCK_ACCESS_FS_RENAME_TO | \
+ LANDLOCK_ACCESS_FS_RMDIR | \
+ LANDLOCK_ACCESS_FS_UNLINK | \
+ LANDLOCK_ACCESS_FS_MAKE_CHAR | \
+ LANDLOCK_ACCESS_FS_MAKE_DIR | \
+ LANDLOCK_ACCESS_FS_MAKE_REG | \
+ LANDLOCK_ACCESS_FS_MAKE_SOCK | \
+ LANDLOCK_ACCESS_FS_MAKE_FIFO | \
+ LANDLOCK_ACCESS_FS_MAKE_BLOCK | \
+ LANDLOCK_ACCESS_FS_MAKE_SYM)
+
+int main(int argc, char * const argv[], char * const *envp)
+{
+ char *cmd_path;
+ char * const *cmd_argv;
+ int ruleset_fd;
+ struct landlock_attr_features attr_features;
+ struct landlock_attr_ruleset ruleset = {
+ /* only restrict open and getattr */
+ .handled_access_fs = ACCESS_FS_ROUGHLY_READ |
+ ACCESS_FS_ROUGHLY_WRITE,
+ };
+ struct landlock_attr_enforce attr_enforce = {};
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s=\"...\" %s=\"...\" %s <cmd> [args]...\n\n",
+ ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
+ fprintf(stderr, "Launch a command in a restricted environment.\n\n");
+ fprintf(stderr, "Environment variables containing paths, each separated by a colon:\n");
+ fprintf(stderr, "* %s: list of paths allowed to be used in a read-only way.\n",
+ ENV_FS_RO_NAME);
+ fprintf(stderr, "* %s: list of paths allowed to be used in a read-write way.\n",
+ ENV_FS_RO_NAME);
+ fprintf(stderr, "\nexample:\n"
+ "%s=\"/bin:/lib:/usr\" "
+ "%s=\"/dev/pts\" "
+ "%s /bin/bash -i\n",
+ ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
+ return 1;
+ }
+
+ if (landlock(LANDLOCK_CMD_GET_FEATURES, LANDLOCK_OPT_GET_FEATURES,
+ sizeof(attr_features), &attr_features)) {
+ perror("Failed to probe the Landlock supported features");
+ switch (errno) {
+ case ENOSYS:
+ fprintf(stderr, "Hint: this kernel does not support Landlock.\n");
+ break;
+ case ENOPKG:
+ fprintf(stderr, "Hint: Landlock is currently disabled. It can be enabled in the kernel configuration or at boot with the \"lsm=landlock\" parameter.\n");
+ break;
+ }
+ return 1;
+ }
+ /* follow a best-effort approach */
+ ruleset.handled_access_fs &= attr_features.access_fs;
+ ruleset_fd = landlock(LANDLOCK_CMD_CREATE_RULESET,
+ LANDLOCK_OPT_CREATE_RULESET, sizeof(ruleset),
+ &ruleset);
+ if (ruleset_fd < 0) {
+ perror("Failed to create a ruleset");
+ return 1;
+ }
+ if (populate_ruleset(&attr_features, ENV_FS_RO_NAME, ruleset_fd,
+ ACCESS_FS_ROUGHLY_READ)) {
+ goto err_close_ruleset;
+ }
+ if (populate_ruleset(&attr_features, ENV_FS_RW_NAME, ruleset_fd,
+ ACCESS_FS_ROUGHLY_READ |
+ ACCESS_FS_ROUGHLY_WRITE)) {
+ goto err_close_ruleset;
+ }
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
+ perror("Failed to restrict privileges");
+ goto err_close_ruleset;
+ }
+ attr_enforce.ruleset_fd = ruleset_fd;
+ if (landlock(LANDLOCK_CMD_ENFORCE_RULESET,
+ LANDLOCK_OPT_ENFORCE_RULESET,
+ sizeof(attr_enforce), &attr_enforce)) {
+ perror("Failed to enforce ruleset");
+ goto err_close_ruleset;
+ }
+ close(ruleset_fd);
+
+ cmd_path = argv[1];
+ cmd_argv = argv + 1;
+ execve(cmd_path, cmd_argv, envp);
+ fprintf(stderr, "Failed to execute \"%s\"\n", cmd_path);
+ fprintf(stderr, "Hint: access to the binary or its shared libraries may be denied.\n");
+ return 1;
+
+err_close_ruleset:
+ close(ruleset_fd);
+ return 1;
+}
--
2.25.0
^ permalink raw reply related
* [RFC PATCH v14 02/10] landlock: Add ruleset and domain management
From: Mickaël Salaün @ 2020-02-24 16:02 UTC (permalink / raw)
To: linux-kernel
Cc: Mickaël Salaün, Al Viro, Andy Lutomirski, Arnd Bergmann,
Casey Schaufler, Greg Kroah-Hartman, James Morris, Jann Horn,
Jonathan Corbet, Kees Cook, Michael Kerrisk,
Mickaël Salaün, Serge E . Hallyn, Shuah Khan,
Vincent Dagonneau, kernel-hardening, linux-api, linux-arch,
linux-doc, linux-fsdevel, linux-kselftest, linux-security-module,
x86
In-Reply-To: <20200224160215.4136-1-mic@digikod.net>
A Landlock ruleset is mainly a red-black tree with Landlock rules as
nodes. This enables quick update and lookup to match a requested access
e.g., to a file. A ruleset is usable through a dedicated file
descriptor (cf. following commit adding the syscall) which enables a
process to build it by adding new rules.
A domain is a ruleset tied to a set of processes. This group of rules
defined the security policy enforced on these processes and their future
children. A domain can transition to a new domain which is the merge of
itself with a ruleset provided by the current process. This merge is
the intersection of all the constraints, which means that a process can
only gain more constraints over time.
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: James Morris <jmorris@namei.org>
Cc: Kees Cook <keescook@chromium.org>
Cc: Serge E. Hallyn <serge@hallyn.com>
---
Changes since v13:
* New implementation, inspired by the previous inode eBPF map, but
agnostic to the underlying kernel object.
Previous version:
https://lore.kernel.org/lkml/20190721213116.23476-7-mic@digikod.net/
---
MAINTAINERS | 1 +
include/uapi/linux/landlock.h | 102 ++++++++
security/landlock/Makefile | 2 +-
security/landlock/ruleset.c | 460 ++++++++++++++++++++++++++++++++++
security/landlock/ruleset.h | 106 ++++++++
5 files changed, 670 insertions(+), 1 deletion(-)
create mode 100644 include/uapi/linux/landlock.h
create mode 100644 security/landlock/ruleset.c
create mode 100644 security/landlock/ruleset.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 206f85768cd9..937257925e65 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9366,6 +9366,7 @@ L: linux-security-module@vger.kernel.org
W: https://landlock.io
T: git https://github.com/landlock-lsm/linux.git
S: Supported
+F: include/uapi/linux/landlock.h
F: security/landlock/
K: landlock
K: LANDLOCK
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
new file mode 100644
index 000000000000..92760aca3645
--- /dev/null
+++ b/include/uapi/linux/landlock.h
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Landlock - UAPI headers
+ *
+ * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ */
+
+#ifndef _UAPI__LINUX_LANDLOCK_H__
+#define _UAPI__LINUX_LANDLOCK_H__
+
+/**
+ * DOC: fs_access
+ *
+ * A set of actions on kernel objects may be defined by an attribute (e.g.
+ * &struct landlock_attr_path_beneath) and a bitmask of access.
+ *
+ * Filesystem flags
+ * ~~~~~~~~~~~~~~~~
+ *
+ * These flags enable to restrict a sandbox process to a set of of actions on
+ * files and directories. Files or directories opened before the sandboxing
+ * are not subject to these restrictions.
+ *
+ * - %LANDLOCK_ACCESS_FS_READ: Open or map a file with read access.
+ * - %LANDLOCK_ACCESS_FS_READDIR: List the content of a directory.
+ * - %LANDLOCK_ACCESS_FS_GETATTR: Read metadata of a file or a directory.
+ * - %LANDLOCK_ACCESS_FS_WRITE: Write to a file.
+ * - %LANDLOCK_ACCESS_FS_TRUNCATE: Truncate a file.
+ * - %LANDLOCK_ACCESS_FS_LOCK: Lock a file.
+ * - %LANDLOCK_ACCESS_FS_CHMOD: Change DAC permissions on a file or a
+ * directory.
+ * - %LANDLOCK_ACCESS_FS_CHOWN: Change the owner of a file or a directory.
+ * - %LANDLOCK_ACCESS_FS_CHGRP: Change the group of a file or a directory.
+ * - %LANDLOCK_ACCESS_FS_IOCTL: Send various command to a special file, cf.
+ * :manpage:`ioctl(2)`.
+ * - %LANDLOCK_ACCESS_FS_LINK_TO: Link a file into a directory.
+ * - %LANDLOCK_ACCESS_FS_RENAME_FROM: Rename a file or a directory.
+ * - %LANDLOCK_ACCESS_FS_RENAME_TO: Rename a file or a directory.
+ * - %LANDLOCK_ACCESS_FS_RMDIR: Remove an empty directory.
+ * - %LANDLOCK_ACCESS_FS_UNLINK: Remove a file.
+ * - %LANDLOCK_ACCESS_FS_MAKE_CHAR: Create a character device.
+ * - %LANDLOCK_ACCESS_FS_MAKE_DIR: Create a directory.
+ * - %LANDLOCK_ACCESS_FS_MAKE_REG: Create a regular file.
+ * - %LANDLOCK_ACCESS_FS_MAKE_SOCK: Create a UNIX domain socket.
+ * - %LANDLOCK_ACCESS_FS_MAKE_FIFO: Create a named pipe.
+ * - %LANDLOCK_ACCESS_FS_MAKE_BLOCK: Create a block device.
+ * - %LANDLOCK_ACCESS_FS_MAKE_SYM: Create a symbolic link.
+ * - %LANDLOCK_ACCESS_FS_EXECUTE: Execute a file.
+ * - %LANDLOCK_ACCESS_FS_CHROOT: Change the root directory of the current
+ * process.
+ * - %LANDLOCK_ACCESS_FS_OPEN: Open a file or a directory. This flag is set
+ * for any actions (e.g. read, write, execute) requested to open a file or
+ * directory.
+ * - %LANDLOCK_ACCESS_FS_MAP: Map a file. This flag is set for any actions
+ * (e.g. read, write, execute) requested to map a file.
+ *
+ * There is currently no restriction for directory walking e.g.,
+ * :manpage:`chdir(2)`.
+ */
+#define LANDLOCK_ACCESS_FS_READ (1ULL << 0)
+#define LANDLOCK_ACCESS_FS_READDIR (1ULL << 1)
+#define LANDLOCK_ACCESS_FS_GETATTR (1ULL << 2)
+#define LANDLOCK_ACCESS_FS_WRITE (1ULL << 3)
+#define LANDLOCK_ACCESS_FS_TRUNCATE (1ULL << 4)
+#define LANDLOCK_ACCESS_FS_LOCK (1ULL << 5)
+#define LANDLOCK_ACCESS_FS_CHMOD (1ULL << 6)
+#define LANDLOCK_ACCESS_FS_CHOWN (1ULL << 7)
+#define LANDLOCK_ACCESS_FS_CHGRP (1ULL << 8)
+#define LANDLOCK_ACCESS_FS_IOCTL (1ULL << 9)
+#define LANDLOCK_ACCESS_FS_LINK_TO (1ULL << 10)
+#define LANDLOCK_ACCESS_FS_RENAME_FROM (1ULL << 11)
+#define LANDLOCK_ACCESS_FS_RENAME_TO (1ULL << 12)
+#define LANDLOCK_ACCESS_FS_RMDIR (1ULL << 13)
+#define LANDLOCK_ACCESS_FS_UNLINK (1ULL << 14)
+#define LANDLOCK_ACCESS_FS_MAKE_CHAR (1ULL << 15)
+#define LANDLOCK_ACCESS_FS_MAKE_DIR (1ULL << 16)
+#define LANDLOCK_ACCESS_FS_MAKE_REG (1ULL << 17)
+#define LANDLOCK_ACCESS_FS_MAKE_SOCK (1ULL << 18)
+#define LANDLOCK_ACCESS_FS_MAKE_FIFO (1ULL << 19)
+#define LANDLOCK_ACCESS_FS_MAKE_BLOCK (1ULL << 20)
+#define LANDLOCK_ACCESS_FS_MAKE_SYM (1ULL << 21)
+#define LANDLOCK_ACCESS_FS_EXECUTE (1ULL << 22)
+#define LANDLOCK_ACCESS_FS_CHROOT (1ULL << 23)
+#define LANDLOCK_ACCESS_FS_OPEN (1ULL << 24)
+#define LANDLOCK_ACCESS_FS_MAP (1ULL << 25)
+
+/*
+ * Potential future access:
+ * - %LANDLOCK_ACCESS_FS_SETATTR
+ * - %LANDLOCK_ACCESS_FS_APPEND
+ * - %LANDLOCK_ACCESS_FS_LINK_FROM
+ * - %LANDLOCK_ACCESS_FS_MOUNT_FROM
+ * - %LANDLOCK_ACCESS_FS_MOUNT_TO
+ * - %LANDLOCK_ACCESS_FS_UNMOUNT
+ * - %LANDLOCK_ACCESS_FS_TRANSFER
+ * - %LANDLOCK_ACCESS_FS_RECEIVE
+ * - %LANDLOCK_ACCESS_FS_CHDIR
+ * - %LANDLOCK_ACCESS_FS_FCNTL
+ */
+
+#endif /* _UAPI__LINUX_LANDLOCK_H__ */
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index cb6deefbf4c0..d846eba445bb 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -1,3 +1,3 @@
obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
-landlock-y := object.o
+landlock-y := object.o ruleset.o
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
new file mode 100644
index 000000000000..5ec013a4188d
--- /dev/null
+++ b/security/landlock/ruleset.c
@@ -0,0 +1,460 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Ruleset management
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ */
+
+#include <linux/bug.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/rbtree.h>
+#include <linux/rcupdate.h>
+#include <linux/refcount.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+
+#include "object.h"
+#include "ruleset.h"
+
+static struct landlock_ruleset *create_ruleset(void)
+{
+ struct landlock_ruleset *ruleset;
+
+ ruleset = kzalloc(sizeof(*ruleset), GFP_KERNEL);
+ if (!ruleset)
+ return ERR_PTR(-ENOMEM);
+ refcount_set(&ruleset->usage, 1);
+ mutex_init(&ruleset->lock);
+ atomic_set(&ruleset->nb_rules, 0);
+ ruleset->root = RB_ROOT;
+ return ruleset;
+}
+
+struct landlock_ruleset *landlock_create_ruleset(u64 fs_access_mask)
+{
+ struct landlock_ruleset *ruleset;
+
+ /* Safely handles 32-bits conversion. */
+ BUILD_BUG_ON(!__same_type(fs_access_mask, _LANDLOCK_ACCESS_FS_LAST));
+
+ /* Checks content. */
+ if ((fs_access_mask | _LANDLOCK_ACCESS_FS_MASK) !=
+ _LANDLOCK_ACCESS_FS_MASK)
+ return ERR_PTR(-EINVAL);
+ /* Informs about useless ruleset. */
+ if (!fs_access_mask)
+ return ERR_PTR(-ENOMSG);
+ ruleset = create_ruleset();
+ if (!IS_ERR(ruleset))
+ ruleset->fs_access_mask = fs_access_mask;
+ return ruleset;
+}
+
+/*
+ * The underlying kernel object must be held by the caller.
+ */
+static struct landlock_ruleset_elem *create_ruleset_elem(
+ struct landlock_object *object)
+{
+ struct landlock_ruleset_elem *ruleset_elem;
+
+ ruleset_elem = kzalloc(sizeof(*ruleset_elem), GFP_KERNEL);
+ if (!ruleset_elem)
+ return ERR_PTR(-ENOMEM);
+ RB_CLEAR_NODE(&ruleset_elem->node);
+ RCU_INIT_POINTER(ruleset_elem->ref.object, object);
+ return ruleset_elem;
+}
+
+static struct landlock_rule *create_rule(struct landlock_object *object,
+ struct landlock_access *access)
+{
+ struct landlock_rule *new_rule;
+
+ if (WARN_ON_ONCE(!object))
+ return ERR_PTR(-EFAULT);
+ if (WARN_ON_ONCE(!access))
+ return ERR_PTR(-EFAULT);
+ new_rule = kzalloc(sizeof(*new_rule), GFP_KERNEL);
+ if (!new_rule)
+ return ERR_PTR(-ENOMEM);
+ refcount_set(&new_rule->usage, 1);
+ INIT_LIST_HEAD(&new_rule->list);
+ new_rule->access = *access;
+
+ spin_lock(&object->lock);
+ list_add_tail(&new_rule->list, &object->rules);
+ spin_unlock(&object->lock);
+ return new_rule;
+}
+
+/*
+ * An inserted rule can not be removed, only disabled (cf. struct
+ * landlock_ruleset_elem).
+ *
+ * The underlying kernel object must be held by the caller.
+ *
+ * @rule: Allocated struct owned by this function. The caller must hold the
+ * underlying kernel object (e.g., with a FD).
+ */
+int landlock_insert_ruleset_rule(struct landlock_ruleset *ruleset,
+ struct landlock_object *object, struct landlock_access *access,
+ struct landlock_rule *rule)
+{
+ struct rb_node **new;
+ struct rb_node *parent = NULL;
+ struct landlock_ruleset_elem *ruleset_elem;
+ struct landlock_rule *new_rule;
+
+ might_sleep();
+ /* Accesses may be set when creating a new rule. */
+ if (rule) {
+ if (WARN_ON_ONCE(access))
+ return -EINVAL;
+ } else {
+ if (WARN_ON_ONCE(!access))
+ return -EFAULT;
+ }
+
+ lockdep_assert_held(&ruleset->lock);
+ new = &(ruleset->root.rb_node);
+ while (*new) {
+ struct landlock_ruleset_elem *this = rb_entry(*new,
+ struct landlock_ruleset_elem, node);
+ uintptr_t this_object;
+ struct landlock_rule *this_rule;
+ struct landlock_access new_access;
+
+ this_object = (uintptr_t)rcu_access_pointer(this->ref.object);
+ if (this_object != (uintptr_t)object) {
+ parent = *new;
+ if (this_object < (uintptr_t)object)
+ new = &((*new)->rb_right);
+ else
+ new = &((*new)->rb_left);
+ continue;
+ }
+
+ /* Do not increment ruleset->nb_rules. */
+ this_rule = rcu_dereference_protected(this->ref.rule,
+ lockdep_is_held(&ruleset->lock));
+ /*
+ * Checks if it is a new object with the same address as a
+ * previously disabled one. There is no possible race
+ * condition because an object can not be disabled/deleted
+ * while being inserted in this tree.
+ */
+ if (landlock_rule_is_disabled(this_rule)) {
+ if (rule) {
+ refcount_inc(&rule->usage);
+ new_rule = rule;
+ } else {
+ /* Replace the previous rule with a new one. */
+ new_rule = create_rule(object, access);
+ if (IS_ERR(new_rule))
+ return PTR_ERR(new_rule);
+ }
+ rcu_assign_pointer(this->ref.rule, new_rule);
+ landlock_put_rule(object, this_rule);
+ return 0;
+ }
+
+ /* this_rule is potentially enabled. */
+ if (refcount_read(&this_rule->usage) == 1) {
+ if (rule) {
+ /* merge rule: intersection of access rights */
+ this_rule->access.self &= rule->access.self;
+ this_rule->access.beneath &=
+ rule->access.beneath;
+ } else {
+ /* extend rule: union of access rights */
+ this_rule->access.self |= access->self;
+ this_rule->access.beneath |= access->beneath;
+ }
+ return 0;
+ }
+
+ /*
+ * If this_rule is shared with another ruleset, then create a
+ * new object rule.
+ */
+ if (rule) {
+ /* Merging a rule means an intersection of access. */
+ new_access.self = this_rule->access.self &
+ rule->access.self;
+ new_access.beneath = this_rule->access.beneath &
+ rule->access.beneath;
+ } else {
+ /* Extending a rule means a union of access. */
+ new_access.self = this_rule->access.self |
+ access->self;
+ new_access.beneath = this_rule->access.self |
+ access->beneath;
+ }
+ new_rule = create_rule(object, &new_access);
+ if (IS_ERR(new_rule))
+ return PTR_ERR(new_rule);
+ rcu_assign_pointer(this->ref.rule, new_rule);
+ landlock_put_rule(object, this_rule);
+ return 0;
+ }
+
+ /* There is no match for @object. */
+ ruleset_elem = create_ruleset_elem(object);
+ if (IS_ERR(ruleset_elem))
+ return PTR_ERR(ruleset_elem);
+ if (rule) {
+ refcount_inc(&rule->usage);
+ new_rule = rule;
+ } else {
+ new_rule = create_rule(object, access);
+ if (IS_ERR(new_rule)) {
+ kfree(ruleset_elem);
+ return PTR_ERR(new_rule);
+ }
+ }
+ RCU_INIT_POINTER(ruleset_elem->ref.rule, new_rule);
+ /*
+ * Because of the missing RCU context annotation in struct rb_node,
+ * Sparse emits a warning when encountering rb_link_node_rcu(), but
+ * this function call is still safe.
+ */
+ rb_link_node_rcu(&ruleset_elem->node, parent, new);
+ rb_insert_color(&ruleset_elem->node, &ruleset->root);
+ atomic_inc(&ruleset->nb_rules);
+ return 0;
+}
+
+static int merge_ruleset(struct landlock_ruleset *dst,
+ struct landlock_ruleset *src)
+{
+ struct rb_node *node;
+ int err = 0;
+
+ might_sleep();
+ if (!src)
+ return 0;
+ if (WARN_ON_ONCE(!dst))
+ return -EFAULT;
+ if (WARN_ON_ONCE(!dst->hierarchy))
+ return -EINVAL;
+
+ mutex_lock(&dst->lock);
+ mutex_lock_nested(&src->lock, 1);
+ dst->fs_access_mask |= src->fs_access_mask;
+ for (node = rb_first(&src->root); node; node = rb_next(node)) {
+ struct landlock_ruleset_elem *elem = rb_entry(node,
+ struct landlock_ruleset_elem, node);
+ struct landlock_object *object =
+ rcu_dereference_protected(elem->ref.object,
+ lockdep_is_held(&src->lock));
+ struct landlock_rule *rule =
+ rcu_dereference_protected(elem->ref.rule,
+ lockdep_is_held(&src->lock));
+
+ err = landlock_insert_ruleset_rule(dst, object, NULL, rule);
+ if (err)
+ goto out_unlock;
+ }
+
+out_unlock:
+ mutex_unlock(&src->lock);
+ mutex_unlock(&dst->lock);
+ return err;
+}
+
+void landlock_get_ruleset(struct landlock_ruleset *ruleset)
+{
+ if (!ruleset)
+ return;
+ refcount_inc(&ruleset->usage);
+}
+
+static void put_hierarchy(struct landlock_hierarchy *hierarchy)
+{
+ if (hierarchy && refcount_dec_and_test(&hierarchy->usage))
+ kfree(hierarchy);
+}
+
+static void put_ruleset(struct landlock_ruleset *ruleset)
+{
+ struct rb_node *orig;
+
+ might_sleep();
+ for (orig = rb_first(&ruleset->root); orig; orig = rb_next(orig)) {
+ struct landlock_ruleset_elem *freeme;
+ struct landlock_object *object;
+ struct landlock_rule *rule;
+
+ freeme = rb_entry(orig, struct landlock_ruleset_elem, node);
+ object = rcu_dereference_protected(freeme->ref.object,
+ refcount_read(&ruleset->usage) == 0);
+ rule = rcu_dereference_protected(freeme->ref.rule,
+ refcount_read(&ruleset->usage) == 0);
+ landlock_put_rule(object, rule);
+ kfree_rcu(freeme, rcu_free);
+ }
+ put_hierarchy(ruleset->hierarchy);
+ kfree_rcu(ruleset, rcu_free);
+}
+
+void landlock_put_ruleset(struct landlock_ruleset *ruleset)
+{
+ might_sleep();
+ if (ruleset && refcount_dec_and_test(&ruleset->usage))
+ put_ruleset(ruleset);
+}
+
+static void put_ruleset_work(struct work_struct *work)
+{
+ struct landlock_ruleset *ruleset;
+
+ ruleset = container_of(work, struct landlock_ruleset, work_put);
+ /*
+ * Clean up rcu_free because of previous use through union work_put.
+ * ruleset->rcu_free.func is already NULLed by __rcu_reclaim().
+ */
+ ruleset->rcu_free.next = NULL;
+ put_ruleset(ruleset);
+}
+
+void landlock_put_ruleset_enqueue(struct landlock_ruleset *ruleset)
+{
+ if (ruleset && refcount_dec_and_test(&ruleset->usage)) {
+ INIT_WORK(&ruleset->work_put, put_ruleset_work);
+ schedule_work(&ruleset->work_put);
+ }
+}
+
+static bool clean_ref(struct landlock_ref *ref)
+{
+ struct landlock_rule *rule;
+
+ rule = rcu_dereference(ref->rule);
+ if (!rule)
+ return false;
+ if (!landlock_rule_is_disabled(rule))
+ return false;
+ rcu_assign_pointer(ref->rule, NULL);
+ /*
+ * landlock_put_rule() will not sleep because we already checked
+ * !landlock_rule_is_disabled(rule).
+ */
+ landlock_put_rule(rcu_dereference(ref->object), rule);
+ return true;
+}
+
+static void clean_ruleset(struct landlock_ruleset *ruleset)
+{
+ struct rb_node *node;
+
+ if (!ruleset)
+ return;
+ /* We must lock the ruleset to not have a wrong nb_rules counter. */
+ mutex_lock(&ruleset->lock);
+ rcu_read_lock();
+ for (node = rb_first(&ruleset->root); node; node = rb_next(node)) {
+ struct landlock_ruleset_elem *elem = rb_entry(node,
+ struct landlock_ruleset_elem, node);
+
+ if (clean_ref(&elem->ref)) {
+ rb_erase(&elem->node, &ruleset->root);
+ kfree_rcu(elem, rcu_free);
+ atomic_dec(&ruleset->nb_rules);
+ }
+ }
+ rcu_read_unlock();
+ mutex_unlock(&ruleset->lock);
+}
+
+/*
+ * Creates a new ruleset, merged of @parent and @ruleset, or return @parent if
+ * @ruleset is empty. If @parent is empty, return a duplicate of @ruleset.
+ *
+ * @parent: Must not be modified (i.e. locked or read-only).
+ */
+struct landlock_ruleset *landlock_merge_ruleset(
+ struct landlock_ruleset *parent,
+ struct landlock_ruleset *ruleset)
+{
+ struct landlock_ruleset *new_dom;
+ int err;
+
+ might_sleep();
+ /* Opportunistically put disabled rules. */
+ clean_ruleset(ruleset);
+
+ if (parent && WARN_ON_ONCE(!parent->hierarchy))
+ return ERR_PTR(-EINVAL);
+ if (!ruleset || atomic_read(&ruleset->nb_rules) == 0 ||
+ parent == ruleset) {
+ landlock_get_ruleset(parent);
+ return parent;
+ }
+
+ new_dom = create_ruleset();
+ if (IS_ERR(new_dom))
+ return new_dom;
+ new_dom->hierarchy = kzalloc(sizeof(*new_dom->hierarchy), GFP_KERNEL);
+ if (!new_dom->hierarchy) {
+ landlock_put_ruleset(new_dom);
+ return ERR_PTR(-ENOMEM);
+ }
+ refcount_set(&new_dom->hierarchy->usage, 1);
+
+ if (parent) {
+ new_dom->hierarchy->parent = parent->hierarchy;
+ refcount_inc(&parent->hierarchy->usage);
+ err = merge_ruleset(new_dom, parent);
+ if (err) {
+ landlock_put_ruleset(new_dom);
+ return ERR_PTR(err);
+ }
+ }
+ err = merge_ruleset(new_dom, ruleset);
+ if (err) {
+ landlock_put_ruleset(new_dom);
+ return ERR_PTR(err);
+ }
+ return new_dom;
+}
+
+/*
+ * The return pointer must only be used in a RCU-read block.
+ */
+const struct landlock_access *landlock_find_access(
+ const struct landlock_ruleset *ruleset,
+ const struct landlock_object *object)
+{
+ struct rb_node *node;
+
+ WARN_ON_ONCE(!rcu_read_lock_held());
+ if (!object)
+ return NULL;
+ node = ruleset->root.rb_node;
+ while (node) {
+ struct landlock_ruleset_elem *this = rb_entry(node,
+ struct landlock_ruleset_elem, node);
+ uintptr_t this_object =
+ (uintptr_t)rcu_access_pointer(this->ref.object);
+
+ if (this_object == (uintptr_t)object) {
+ struct landlock_rule *rule;
+
+ rule = rcu_dereference(this->ref.rule);
+ if (!landlock_rule_is_disabled(rule))
+ return &rule->access;
+ return NULL;
+ }
+ if (this_object < (uintptr_t)object)
+ node = node->rb_right;
+ else
+ node = node->rb_left;
+ }
+ return NULL;
+}
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
new file mode 100644
index 000000000000..afc88dbb8b4b
--- /dev/null
+++ b/security/landlock/ruleset.h
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Ruleset management
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ */
+
+#ifndef _SECURITY_LANDLOCK_RULESET_H
+#define _SECURITY_LANDLOCK_RULESET_H
+
+#include <linux/compiler.h>
+#include <linux/mutex.h>
+#include <linux/poison.h>
+#include <linux/rbtree.h>
+#include <linux/rcupdate.h>
+#include <linux/refcount.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+#include <uapi/linux/landlock.h>
+
+#include "object.h"
+
+#define _LANDLOCK_ACCESS_FS_LAST LANDLOCK_ACCESS_FS_MAP
+#define _LANDLOCK_ACCESS_FS_MASK ((_LANDLOCK_ACCESS_FS_LAST << 1) - 1)
+
+struct landlock_ref {
+ /*
+ * @object: Identify a kernel object (e.g. an inode). This is used as
+ * a key for a ruleset tree (cf. struct landlock_ruleset_elem). This
+ * pointer is set once and never modified. It may point to a deleted
+ * object and should then be dereferenced with great care, thanks to a
+ * call to landlock_rule_is_disabled(@rule) from inside an RCU-read
+ * block, cf. landlock_put_rule().
+ */
+ struct landlock_object __rcu *object;
+ /*
+ * @rule: Ties a rule to an object. Set once with an allocated rule,
+ * but can be NULLed if the rule is disabled.
+ */
+ struct landlock_rule __rcu *rule;
+};
+
+/*
+ * Red-black tree element used in a landlock_ruleset.
+ */
+struct landlock_ruleset_elem {
+ struct landlock_ref ref;
+ struct rb_node node;
+ struct rcu_head rcu_free;
+};
+
+/*
+ * Enable hierarchy identification even when a parent domain vanishes. This is
+ * needed for the ptrace protection.
+ */
+struct landlock_hierarchy {
+ struct landlock_hierarchy *parent;
+ refcount_t usage;
+};
+
+/*
+ * Kernel representation of a ruleset. This data structure must contains
+ * unique entries, be updatable, and quick to match an object.
+ */
+struct landlock_ruleset {
+ /*
+ * @fs_access_mask: Contains the subset of filesystem actions which are
+ * restricted by a ruleset. This is used when merging rulesets and for
+ * userspace backward compatibility (i.e. future-proof). Set once and
+ * never changed for the lifetime of the ruleset.
+ */
+ u32 fs_access_mask;
+ struct landlock_hierarchy *hierarchy;
+ refcount_t usage;
+ union {
+ struct rcu_head rcu_free;
+ struct work_struct work_put;
+ };
+ struct mutex lock;
+ atomic_t nb_rules;
+ /*
+ * @root: Red-black tree containing landlock_ruleset_elem nodes.
+ */
+ struct rb_root root;
+};
+
+struct landlock_ruleset *landlock_create_ruleset(u64 fs_access_mask);
+
+void landlock_get_ruleset(struct landlock_ruleset *ruleset);
+void landlock_put_ruleset(struct landlock_ruleset *ruleset);
+void landlock_put_ruleset_enqueue(struct landlock_ruleset *ruleset);
+
+int landlock_insert_ruleset_rule(struct landlock_ruleset *ruleset,
+ struct landlock_object *object, struct landlock_access *access,
+ struct landlock_rule *rule);
+
+struct landlock_ruleset *landlock_merge_ruleset(
+ struct landlock_ruleset *domain,
+ struct landlock_ruleset *ruleset);
+
+const struct landlock_access *landlock_find_access(
+ const struct landlock_ruleset *ruleset,
+ const struct landlock_object *object);
+
+#endif /* _SECURITY_LANDLOCK_RULESET_H */
--
2.25.0
^ permalink raw reply related
* [RFC PATCH v14 06/10] landlock: Add syscall implementation
From: Mickaël Salaün @ 2020-02-24 16:02 UTC (permalink / raw)
To: linux-kernel
Cc: Mickaël Salaün, Al Viro, Andy Lutomirski, Arnd Bergmann,
Casey Schaufler, Greg Kroah-Hartman, James Morris, Jann Horn,
Jonathan Corbet, Kees Cook, Michael Kerrisk,
Mickaël Salaün, Serge E . Hallyn, Shuah Khan,
Vincent Dagonneau, kernel-hardening, linux-api, linux-arch,
linux-doc, linux-fsdevel, linux-kselftest, linux-security-module,
x86
In-Reply-To: <20200224160215.4136-1-mic@digikod.net>
This syscall, inspired from seccomp(2) and bpf(2), is designed to be
used by unprivileged processes to sandbox themselves. It has the same
usage restrictions as seccomp(2): no_new_privs check.
There is currently four commands:
* get_features: Gets the supported features (required for backward
compatibility and best-effort security).
* create_ruleset: Creates a ruleset and returns its file descriptor.
* add_rule: Adds a rule (e.g. file hierarchy access) to a ruleset,
identified by the dedicated file descriptor.
* enforce_ruleset: Enforces a ruleset on the current thread (similar to
seccomp).
See the user and code documentation for more details.
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: James Morris <jmorris@namei.org>
Cc: Kees Cook <keescook@chromium.org>
Cc: Serge E. Hallyn <serge@hallyn.com>
---
Changes since v13:
* New implementation, replacing the dependency on seccomp(2) and bpf(2).
---
include/linux/syscalls.h | 3 +
include/uapi/linux/landlock.h | 213 +++++++++++++++
security/landlock/Makefile | 2 +-
security/landlock/ruleset.c | 3 +
security/landlock/syscall.c | 470 ++++++++++++++++++++++++++++++++++
5 files changed, 690 insertions(+), 1 deletion(-)
create mode 100644 security/landlock/syscall.c
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 1815065d52f3..beaadcf4ef77 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -1003,6 +1003,9 @@ asmlinkage long sys_pidfd_send_signal(int pidfd, int sig,
siginfo_t __user *info,
unsigned int flags);
asmlinkage long sys_pidfd_getfd(int pidfd, int fd, unsigned int flags);
+asmlinkage long sys_landlock(unsigned int command, unsigned int options,
+ size_t attr1_size, void __user *attr1_ptr,
+ size_t attr2_size, void __user *attr2_ptr);
/*
* Architecture-specific system calls
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 92760aca3645..0b6d3e9f4b37 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -9,6 +9,219 @@
#ifndef _UAPI__LINUX_LANDLOCK_H__
#define _UAPI__LINUX_LANDLOCK_H__
+#include <linux/types.h>
+
+/**
+ * enum landlock_cmd - Landlock commands
+ *
+ * First argument of sys_landlock().
+ */
+enum landlock_cmd {
+ /**
+ * @LANDLOCK_CMD_GET_FEATURES: Asks the kernel for supported Landlock
+ * features. The option argument must contains
+ * %LANDLOCK_OPT_GET_FEATURES. This commands fills the &struct
+ * landlock_attr_features provided as first attribute.
+ */
+ LANDLOCK_CMD_GET_FEATURES = 1,
+ /**
+ * @LANDLOCK_CMD_CREATE_RULESET: Creates a new ruleset and return its
+ * file descriptor on success. The option argument must contains
+ * %LANDLOCK_OPT_CREATE_RULESET. The ruleset is defined by the &struct
+ * landlock_attr_ruleset provided as first attribute.
+ */
+ LANDLOCK_CMD_CREATE_RULESET,
+ /**
+ * @LANDLOCK_CMD_ADD_RULE: Adds a rule to a ruleset. The option
+ * argument must contains %LANDLOCK_OPT_ADD_RULE_PATH_BENEATH. The
+ * ruleset and the rule are both defined by the &struct
+ * landlock_attr_path_beneath provided as first attribute.
+ */
+ LANDLOCK_CMD_ADD_RULE,
+ /**
+ * @LANDLOCK_CMD_ENFORCE_RULESET: Enforces a ruleset on the current
+ * process. The option argument must contains
+ * %LANDLOCK_OPT_ENFORCE_RULESET. The ruleset is defined by the
+ * &struct landlock_attr_enforce provided as first attribute.
+ */
+ LANDLOCK_CMD_ENFORCE_RULESET,
+};
+
+/**
+ * DOC: options_intro
+ *
+ * These options may be used as second argument of sys_landlock(). Each
+ * command have a dedicated set of options, represented as bitmasks. For two
+ * different commands, their options may overlap. Each command have at least
+ * one option defining the used attribute type. This also enables to always
+ * have a usable &struct landlock_attr_features (i.e. filled with bits).
+ */
+
+/**
+ * DOC: options_get_features
+ *
+ * Options for ``LANDLOCK_CMD_GET_FEATURES``
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * - %LANDLOCK_OPT_GET_FEATURES: the attr type is `struct
+ * landlock_attr_features`.
+ */
+#define LANDLOCK_OPT_GET_FEATURES (1ULL << 0)
+
+/**
+ * DOC: options_create_ruleset
+ *
+ * Options for ``LANDLOCK_CMD_CREATE_RULESET``
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * - %LANDLOCK_OPT_CREATE_RULESET: the attr type is `struct
+ * landlock_attr_ruleset`.
+ */
+#define LANDLOCK_OPT_CREATE_RULESET (1ULL << 0)
+
+/**
+ * DOC: options_add_rule
+ *
+ * Options for ``LANDLOCK_CMD_ADD_RULE``
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * - %LANDLOCK_OPT_ADD_RULE_PATH_BENEATH: the attr type is `struct
+ * landlock_attr_path_beneath`.
+ */
+#define LANDLOCK_OPT_ADD_RULE_PATH_BENEATH (1ULL << 0)
+
+/**
+ * DOC: options_enforce_ruleset
+ *
+ * Options for ``LANDLOCK_CMD_ENFORCE_RULESET``
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * - %LANDLOCK_OPT_ENFORCE_RULESET: the attr type is `struct
+ * landlock_attr_enforce`.
+ */
+#define LANDLOCK_OPT_ENFORCE_RULESET (1ULL << 0)
+
+/**
+ * struct landlock_attr_features - Receives the supported features
+ *
+ * This struct should be allocated by user space but it will be filled by the
+ * kernel to indicate the subset of Landlock features effectively handled by
+ * the running kernel. This enables backward compatibility for applications
+ * which are developed on a newer kernel than the one running the application.
+ * This helps avoid hard errors that may entirely disable the use of Landlock
+ * features because some of them may not be supported. Indeed, because
+ * Landlock is a security feature, even if the kernel doesn't support all the
+ * requested features, user space applications should still use the subset
+ * which is supported by the running kernel. Indeed, a partial security policy
+ * can still improve the security of the application and better protect the
+ * user (i.e. best-effort approach). The %LANDLOCK_CMD_GET_FEATURES command
+ * and &struct landlock_attr_features are future-proof because the future
+ * unknown fields requested by user space (i.e. a larger &struct
+ * landlock_attr_features) can still be filled with zeros.
+ *
+ * The Landlock commands will fail if an unsupported option or access is
+ * requested. By firstly requesting the supported options and accesses, it is
+ * quite easy for the developer to binary AND these returned bitmasks with the
+ * used options and accesses from the attribute structs (e.g. &struct
+ * landlock_attr_ruleset), and even infer the supported Landlock commands.
+ * Indeed, because each command must support at least one option, the options_*
+ * fields are always filled if the related commands are supported. The
+ * supported attributes are also discoverable thanks to the size_* fields. All
+ * this data enable to create applications doing their best to sandbox
+ * themselves regardless of the running kernel.
+ */
+struct landlock_attr_features {
+ /**
+ * @options_get_features: Options supported by the
+ * %LANDLOCK_CMD_GET_FEATURES command. Cf. `Options`_.
+ */
+ __aligned_u64 options_get_features;
+ /**
+ * @options_create_ruleset: Options supported by the
+ * %LANDLOCK_CMD_CREATE_RULESET command. Cf. `Options`_.
+ */
+ __aligned_u64 options_create_ruleset;
+ /**
+ * @options_add_rule: Options supported by the %LANDLOCK_CMD_ADD_RULE
+ * command. Cf. `Options`_.
+ */
+ __aligned_u64 options_add_rule;
+ /**
+ * @options_enforce_ruleset: Options supported by the
+ * %LANDLOCK_CMD_ENFORCE_RULESET command. Cf. `Options`_.
+ */
+ __aligned_u64 options_enforce_ruleset;
+ /**
+ * @access_fs: Subset of file system access supported by the running
+ * kernel, used in &struct landlock_attr_ruleset and &struct
+ * landlock_attr_path_beneath. Cf. `Filesystem flags`_.
+ */
+ __aligned_u64 access_fs;
+ /**
+ * @size_attr_ruleset: Size of the &struct landlock_attr_ruleset as
+ * known by the kernel (i.e. ``sizeof(struct
+ * landlock_attr_ruleset)``).
+ */
+ __aligned_u64 size_attr_ruleset;
+ /**
+ * @size_attr_path_beneath: Size of the &struct
+ * landlock_attr_path_beneath as known by the kernel (i.e.
+ * ``sizeof(struct landlock_path_beneath)``).
+ */
+ __aligned_u64 size_attr_path_beneath;
+};
+
+/**
+ * struct landlock_attr_ruleset- Defines a new ruleset
+ *
+ * Used as first attribute for the %LANDLOCK_CMD_CREATE_RULESET command and
+ * with the %LANDLOCK_OPT_CREATE_RULESET option.
+ */
+struct landlock_attr_ruleset {
+ /**
+ * @handled_access_fs: Bitmask of actions (cf. `Filesystem flags`_)
+ * that is handled by this ruleset and should then be forbidden if no
+ * rule explicitly allow them. This is needed for backward
+ * compatibility reasons. The user space code should check the
+ * effectively supported actions thanks to %LANDLOCK_CMD_GET_SUPPORTED
+ * and &struct landlock_attr_features, and then adjust the arguments of
+ * the next calls to sys_landlock() accordingly.
+ */
+ __aligned_u64 handled_access_fs;
+};
+
+/**
+ * struct landlock_attr_path_beneath - Defines a path hierarchy
+ */
+struct landlock_attr_path_beneath {
+ /**
+ * @ruleset_fd: File descriptor tied to the ruleset which should be
+ * extended with this new access.
+ */
+ __aligned_u64 ruleset_fd;
+ /**
+ * @parent_fd: File descriptor, open with ``O_PATH``, which identify
+ * the parent directory of a file hierarchy, or just a file.
+ */
+ __aligned_u64 parent_fd;
+ /**
+ * @allowed_access: Bitmask of allowed actions for this file hierarchy
+ * (cf. `Filesystem flags`_).
+ */
+ __aligned_u64 allowed_access;
+};
+
+/**
+ * struct landlock_attr_enforce - Describes the enforcement
+ */
+struct landlock_attr_enforce {
+ /**
+ * @ruleset_fd: File descriptor tied to the ruleset to merge with the
+ * current domain.
+ */
+ __aligned_u64 ruleset_fd;
+};
+
/**
* DOC: fs_access
*
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index 92e3d80ab8ed..4388494779ec 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -1,4 +1,4 @@
obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
-landlock-y := setup.o object.o ruleset.o \
+landlock-y := setup.o syscall.o object.o ruleset.o \
cred.o ptrace.o fs.o
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 5ec013a4188d..fab17110804f 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -17,6 +17,7 @@
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
+#include <uapi/linux/landlock.h>
#include "object.h"
#include "ruleset.h"
@@ -40,6 +41,8 @@ struct landlock_ruleset *landlock_create_ruleset(u64 fs_access_mask)
struct landlock_ruleset *ruleset;
/* Safely handles 32-bits conversion. */
+ BUILD_BUG_ON(!__same_type(fs_access_mask, ((struct
+ landlock_attr_ruleset *)NULL)->handled_access_fs));
BUILD_BUG_ON(!__same_type(fs_access_mask, _LANDLOCK_ACCESS_FS_LAST));
/* Checks content. */
diff --git a/security/landlock/syscall.c b/security/landlock/syscall.c
new file mode 100644
index 000000000000..da80e3061b5a
--- /dev/null
+++ b/security/landlock/syscall.c
@@ -0,0 +1,470 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - System call and user space interfaces
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ */
+
+#include <asm/current.h>
+#include <linux/anon_inodes.h>
+#include <linux/build_bug.h>
+#include <linux/capability.h>
+#include <linux/dcache.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/landlock.h>
+#include <linux/limits.h>
+#include <linux/path.h>
+#include <linux/rcupdate.h>
+#include <linux/refcount.h>
+#include <linux/sched.h>
+#include <linux/security.h>
+#include <linux/syscalls.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <uapi/linux/landlock.h>
+
+#include "cred.h"
+#include "fs.h"
+#include "ruleset.h"
+#include "setup.h"
+
+/**
+ * copy_struct_if_any_from_user - Safe future-proof argument copying
+ *
+ * Extend copy_struct_from_user() to handle NULL @src, which allows for future
+ * use of @src even if it is not used right now.
+ *
+ * @dst: kernel space pointer or NULL
+ * @ksize: size of the data pointed by @dst
+ * @src: user space pointer or NULL
+ * @usize: size of the data pointed by @src
+ */
+static int copy_struct_if_any_from_user(void *dst, size_t ksize,
+ const void __user *src, size_t usize)
+{
+ int ret;
+
+ if (dst) {
+ if (WARN_ON_ONCE(ksize == 0))
+ return -EFAULT;
+ } else {
+ if (WARN_ON_ONCE(ksize != 0))
+ return -EFAULT;
+ }
+ if (!src) {
+ if (usize != 0)
+ return -EFAULT;
+ if (dst)
+ memset(dst, 0, ksize);
+ return 0;
+ }
+ if (usize == 0)
+ return -ENODATA;
+ if (usize > PAGE_SIZE)
+ return -E2BIG;
+ if (dst)
+ return copy_struct_from_user(dst, ksize, src, usize);
+ ret = check_zeroed_user(src, usize);
+ if (ret <= 0)
+ return ret ?: -E2BIG;
+ return 0;
+}
+
+/* Features */
+
+#define _LANDLOCK_OPT_GET_FEATURES_LAST LANDLOCK_OPT_GET_FEATURES
+#define _LANDLOCK_OPT_GET_FEATURES_MASK ((_LANDLOCK_OPT_GET_FEATURES_LAST << 1) - 1)
+
+#define _LANDLOCK_OPT_CREATE_RULESET_LAST LANDLOCK_OPT_CREATE_RULESET
+#define _LANDLOCK_OPT_CREATE_RULESET_MASK ((_LANDLOCK_OPT_CREATE_RULESET_LAST << 1) - 1)
+
+#define _LANDLOCK_OPT_ADD_RULE_LAST LANDLOCK_OPT_ADD_RULE_PATH_BENEATH
+#define _LANDLOCK_OPT_ADD_RULE_MASK ((_LANDLOCK_OPT_ADD_RULE_LAST << 1) - 1)
+
+#define _LANDLOCK_OPT_ENFORCE_RULESET_LAST LANDLOCK_OPT_ENFORCE_RULESET
+#define _LANDLOCK_OPT_ENFORCE_RULESET_MASK ((_LANDLOCK_OPT_ENFORCE_RULESET_LAST << 1) - 1)
+
+static int syscall_get_features(size_t attr_size, void __user *attr_ptr)
+{
+ size_t data_size, fill_size;
+ struct landlock_attr_features supported = {
+ .options_get_features = _LANDLOCK_OPT_GET_FEATURES_MASK,
+ .options_create_ruleset = _LANDLOCK_OPT_CREATE_RULESET_MASK,
+ .options_add_rule = _LANDLOCK_OPT_ADD_RULE_MASK,
+ .options_enforce_ruleset = _LANDLOCK_OPT_ENFORCE_RULESET_MASK,
+ .access_fs = _LANDLOCK_ACCESS_FS_MASK,
+ .size_attr_ruleset = sizeof(struct landlock_attr_ruleset),
+ .size_attr_path_beneath = sizeof(struct
+ landlock_attr_path_beneath),
+ };
+
+ if (attr_size == 0)
+ return -ENODATA;
+ if (attr_size > PAGE_SIZE)
+ return -E2BIG;
+ data_size = min(sizeof(supported), attr_size);
+ if (copy_to_user(attr_ptr, &supported, data_size))
+ return -EFAULT;
+ /* Fills the rest with zeros. */
+ fill_size = attr_size - data_size;
+ if (fill_size > 0 && clear_user(attr_ptr + data_size, fill_size))
+ return -EFAULT;
+ return 0;
+}
+
+/* Ruleset handling */
+
+#ifdef CONFIG_PROC_FS
+static void fop_ruleset_show_fdinfo(struct seq_file *m, struct file *filp)
+{
+ const struct landlock_ruleset *ruleset = filp->private_data;
+
+ seq_printf(m, "handled_access_fs:\t%x\n", ruleset->fs_access_mask);
+ seq_printf(m, "nb_rules:\t%d\n", atomic_read(&ruleset->nb_rules));
+}
+#endif
+
+static int fop_ruleset_release(struct inode *inode, struct file *filp)
+{
+ struct landlock_ruleset *ruleset = filp->private_data;
+
+ landlock_put_ruleset(ruleset);
+ return 0;
+}
+
+static ssize_t fop_dummy_read(struct file *filp, char __user *buf, size_t size,
+ loff_t *ppos)
+{
+ /* Dummy handler to enable FMODE_CAN_READ. */
+ return -EINVAL;
+}
+
+static ssize_t fop_dummy_write(struct file *filp, const char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ /* Dummy handler to enable FMODE_CAN_WRITE. */
+ return -EINVAL;
+}
+
+/*
+ * A ruleset file descriptor enables to build a ruleset by adding (i.e.
+ * writing) rule after rule, without relying on the task's context. This
+ * reentrant design is also used in a read way to enforce the ruleset on the
+ * current task.
+ */
+static const struct file_operations ruleset_fops = {
+#ifdef CONFIG_PROC_FS
+ .show_fdinfo = fop_ruleset_show_fdinfo,
+#endif
+ .release = fop_ruleset_release,
+ .read = fop_dummy_read,
+ .write = fop_dummy_write,
+};
+
+static int syscall_create_ruleset(size_t attr_size, void __user *attr_ptr)
+{
+ struct landlock_attr_ruleset attr_ruleset;
+ struct landlock_ruleset *ruleset;
+ int err, ruleset_fd;
+
+ /* Copies raw userspace struct. */
+ err = copy_struct_if_any_from_user(&attr_ruleset, sizeof(attr_ruleset),
+ attr_ptr, attr_size);
+ if (err)
+ return err;
+
+ /* Checks arguments and transform to kernel struct. */
+ ruleset = landlock_create_ruleset(attr_ruleset.handled_access_fs);
+ if (IS_ERR(ruleset))
+ return PTR_ERR(ruleset);
+
+ /* Creates anonymous FD referring to the ruleset, with safe flags. */
+ ruleset_fd = anon_inode_getfd("landlock-ruleset", &ruleset_fops,
+ ruleset, O_RDWR | O_CLOEXEC);
+ if (ruleset_fd < 0)
+ landlock_put_ruleset(ruleset);
+ return ruleset_fd;
+}
+
+/*
+ * Returns an owned ruleset from a FD. It is thus needed to call
+ * landlock_put_ruleset() on the return value.
+ */
+static struct landlock_ruleset *get_ruleset_from_fd(u64 fd, fmode_t mode)
+{
+ struct fd ruleset_f;
+ struct landlock_ruleset *ruleset;
+ int err;
+
+ BUILD_BUG_ON(!__same_type(fd,
+ ((struct landlock_attr_path_beneath *)NULL)->ruleset_fd));
+ BUILD_BUG_ON(!__same_type(fd,
+ ((struct landlock_attr_enforce *)NULL)->ruleset_fd));
+ /* Checks 32-bits overflow. fdget() checks for INT_MAX/FD. */
+ if (fd > U32_MAX)
+ return ERR_PTR(-EINVAL);
+ ruleset_f = fdget(fd);
+ if (!ruleset_f.file)
+ return ERR_PTR(-EBADF);
+ err = 0;
+ if (ruleset_f.file->f_op != &ruleset_fops)
+ err = -EBADR;
+ else if (!(ruleset_f.file->f_mode & mode))
+ err = -EPERM;
+ if (!err) {
+ ruleset = ruleset_f.file->private_data;
+ landlock_get_ruleset(ruleset);
+ }
+ fdput(ruleset_f);
+ return err ? ERR_PTR(err) : ruleset;
+}
+
+/* Path handling */
+
+static inline bool is_user_mountable(struct dentry *dentry)
+{
+ /*
+ * Check pseudo-filesystems that will never be mountable (e.g. sockfs,
+ * pipefs, bdev), cf. fs/libfs.c:init_pseudo().
+ */
+ return d_is_positive(dentry) &&
+ !IS_PRIVATE(dentry->d_inode) &&
+ !(dentry->d_sb->s_flags & SB_NOUSER);
+}
+
+/*
+ * @path: Must call put_path(@path) after the call if it succeeded.
+ */
+static int get_path_from_fd(u64 fd, struct path *path)
+{
+ struct fd f;
+ int err;
+
+ BUILD_BUG_ON(!__same_type(fd,
+ ((struct landlock_attr_path_beneath *)NULL)->parent_fd));
+ /* Checks 32-bits overflow. fdget_raw() checks for INT_MAX/FD. */
+ if (fd > U32_MAX)
+ return -EINVAL;
+ /* Handles O_PATH. */
+ f = fdget_raw(fd);
+ if (!f.file)
+ return -EBADF;
+ /*
+ * Forbids to add to a ruleset a path which is forbidden to open (by
+ * Landlock, another LSM, DAC...). Because the file was open with
+ * O_PATH, the file mode doesn't have FMODE_READ nor FMODE_WRITE.
+ *
+ * WARNING: security_file_open() was only called in do_dentry_open()
+ * until now. The main difference now is that f_op may be NULL. This
+ * field doesn't seem to be dereferenced by any upstream LSM though.
+ */
+ err = security_file_open(f.file);
+ if (err)
+ goto out_fdput;
+ /*
+ * Only allows O_PATH FD: enable to restrict ambiant (FS) accesses
+ * without requiring to open and risk leaking or misuing a FD. Accept
+ * removed, but still open directory (S_DEAD).
+ */
+ if (!(f.file->f_mode & FMODE_PATH) || !f.file->f_path.mnt ||
+ !is_user_mountable(f.file->f_path.dentry)) {
+ err = -EBADR;
+ goto out_fdput;
+ }
+ path->mnt = f.file->f_path.mnt;
+ path->dentry = f.file->f_path.dentry;
+ path_get(path);
+
+out_fdput:
+ fdput(f);
+ return err;
+}
+
+static int syscall_add_rule_path_beneath(size_t attr_size,
+ void __user *attr_ptr)
+{
+ struct landlock_attr_path_beneath attr_path_beneath;
+ struct path path;
+ struct landlock_ruleset *ruleset;
+ int err;
+
+ /* Copies raw userspace struct. */
+ err = copy_struct_if_any_from_user(&attr_path_beneath,
+ sizeof(attr_path_beneath), attr_ptr, attr_size);
+ if (err)
+ return err;
+
+ /* Gets the ruleset. */
+ ruleset = get_ruleset_from_fd(attr_path_beneath.ruleset_fd,
+ FMODE_CAN_WRITE);
+ if (IS_ERR(ruleset))
+ return PTR_ERR(ruleset);
+
+ /* Checks content (fs_access_mask is upgraded to 64-bits). */
+ if ((attr_path_beneath.allowed_access | ruleset->fs_access_mask) !=
+ ruleset->fs_access_mask) {
+ err = -EINVAL;
+ goto out_put_ruleset;
+ }
+
+ err = get_path_from_fd(attr_path_beneath.parent_fd, &path);
+ if (err)
+ goto out_put_ruleset;
+
+ err = landlock_append_fs_rule(ruleset, &path,
+ attr_path_beneath.allowed_access);
+ path_put(&path);
+
+out_put_ruleset:
+ landlock_put_ruleset(ruleset);
+ return err;
+}
+
+/* Enforcement */
+
+static int syscall_enforce_ruleset(size_t attr_size,
+ void __user *attr_ptr)
+{
+ struct landlock_ruleset *new_dom, *ruleset;
+ struct cred *new_cred;
+ struct landlock_cred_security *new_llcred;
+ struct landlock_attr_enforce attr_enforce;
+ int err;
+
+ /*
+ * Enforcing a Landlock ruleset requires that the task has
+ * CAP_SYS_ADMIN in its namespace or be running with no_new_privs.
+ * This avoids scenarios where unprivileged tasks can affect the
+ * behavior of privileged children. These are similar checks as for
+ * seccomp(2), except that an -EPERM may be returned.
+ */
+ if (!task_no_new_privs(current)) {
+ err = security_capable(current_cred(), current_user_ns(),
+ CAP_SYS_ADMIN, CAP_OPT_NOAUDIT);
+ if (err)
+ return err;
+ }
+
+ /* Copies raw userspace struct. */
+ err = copy_struct_if_any_from_user(&attr_enforce, sizeof(attr_enforce),
+ attr_ptr, attr_size);
+ if (err)
+ return err;
+
+ /* Get the ruleset. */
+ ruleset = get_ruleset_from_fd(attr_enforce.ruleset_fd, FMODE_CAN_READ);
+ if (IS_ERR(ruleset))
+ return PTR_ERR(ruleset);
+ /* Informs about useless ruleset. */
+ if (!atomic_read(&ruleset->nb_rules)) {
+ err = -ENOMSG;
+ goto out_put_ruleset;
+ }
+
+ new_cred = prepare_creds();
+ if (!new_cred) {
+ err = -ENOMEM;
+ goto out_put_ruleset;
+ }
+ new_llcred = landlock_cred(new_cred);
+ /*
+ * There is no possible race condition while copying and manipulating
+ * the current credentials because they are dedicated per thread.
+ */
+ new_dom = landlock_merge_ruleset(new_llcred->domain, ruleset);
+ if (IS_ERR(new_dom)) {
+ err = PTR_ERR(new_dom);
+ goto out_put_creds;
+ }
+ /* Replaces the old (prepared) domain. */
+ landlock_put_ruleset(new_llcred->domain);
+ new_llcred->domain = new_dom;
+
+ landlock_put_ruleset(ruleset);
+ return commit_creds(new_cred);
+
+out_put_creds:
+ abort_creds(new_cred);
+
+out_put_ruleset:
+ landlock_put_ruleset(ruleset);
+ return err;
+}
+
+/**
+ * landlock - System call to enable a process to safely sandbox itself
+ *
+ * @command: Landlock command to perform miscellaneous, but safe, actions. Cf.
+ * `Commands`_.
+ * @options: Bitmask of options dedicated to one command. Cf. `Options`_.
+ * @attr1_size: First attribute size (i.e. size of the struct).
+ * @attr1_ptr: Pointer to the first attribute. Cf. `Attributes`_.
+ * @attr2_size: Unused for now.
+ * @attr2_ptr: Unused for now.
+ *
+ * The @command and @options arguments enable a seccomp-bpf policy to control
+ * the requested actions. However, it should be noted that Landlock is
+ * designed from the ground to enable unprivileged process to drop privileges
+ * and accesses in a way that can not harm other processes. This syscall and
+ * all its arguments should then be allowed for any process, which will then
+ * enable applications to strengthen the security of the whole system.
+ *
+ * @attr2_size and @attr2_ptr describe a second attribute which could be used
+ * in the future to compose with the first attribute (e.g. a
+ * landlock_attr_path_beneath with a landlock_attr_ioctl).
+ *
+ * The order of return errors begins with ENOPKG (disabled Landlock),
+ * EOPNOTSUPP (unknown command or option) and then EINVAL (invalid attribute).
+ * The other error codes may be specific to each command.
+ */
+SYSCALL_DEFINE6(landlock, unsigned int, command, unsigned int, options,
+ size_t, attr1_size, void __user *, attr1_ptr,
+ size_t, attr2_size, void __user *, attr2_ptr)
+{
+ /*
+ * Enables user space to identify if Landlock is disabled, thanks to a
+ * specific error code.
+ */
+ if (!landlock_initialized)
+ return -ENOPKG;
+
+ switch ((enum landlock_cmd)command) {
+ case LANDLOCK_CMD_GET_FEATURES:
+ if (options == LANDLOCK_OPT_GET_FEATURES) {
+ if (attr2_size || attr2_ptr)
+ return -EINVAL;
+ return syscall_get_features(attr1_size, attr1_ptr);
+ }
+ return -EOPNOTSUPP;
+ case LANDLOCK_CMD_CREATE_RULESET:
+ if (options == LANDLOCK_OPT_CREATE_RULESET) {
+ if (attr2_size || attr2_ptr)
+ return -EINVAL;
+ return syscall_create_ruleset(attr1_size, attr1_ptr);
+ }
+ return -EOPNOTSUPP;
+ case LANDLOCK_CMD_ADD_RULE:
+ /*
+ * A future extension could add a
+ * LANDLOCK_OPT_ADD_RULE_PATH_RANGE.
+ */
+ if (options == LANDLOCK_OPT_ADD_RULE_PATH_BENEATH) {
+ if (attr2_size || attr2_ptr)
+ return -EINVAL;
+ return syscall_add_rule_path_beneath(attr1_size,
+ attr1_ptr);
+ }
+ return -EOPNOTSUPP;
+ case LANDLOCK_CMD_ENFORCE_RULESET:
+ if (options == LANDLOCK_OPT_ENFORCE_RULESET) {
+ if (attr2_size || attr2_ptr)
+ return -EINVAL;
+ return syscall_enforce_ruleset(attr1_size, attr1_ptr);
+ }
+ return -EOPNOTSUPP;
+ }
+ return -EOPNOTSUPP;
+}
--
2.25.0
^ permalink raw reply related
* [RFC PATCH v14 04/10] landlock: Add ptrace restrictions
From: Mickaël Salaün @ 2020-02-24 16:02 UTC (permalink / raw)
To: linux-kernel
Cc: Mickaël Salaün, Al Viro, Andy Lutomirski, Arnd Bergmann,
Casey Schaufler, Greg Kroah-Hartman, James Morris, Jann Horn,
Jonathan Corbet, Kees Cook, Michael Kerrisk,
Mickaël Salaün, Serge E . Hallyn, Shuah Khan,
Vincent Dagonneau, kernel-hardening, linux-api, linux-arch,
linux-doc, linux-fsdevel, linux-kselftest, linux-security-module,
x86
In-Reply-To: <20200224160215.4136-1-mic@digikod.net>
Using ptrace(2) and related debug features on a target process can lead
to a privilege escalation. Indeed, ptrace(2) can be used by an attacker
to impersonate another task and to remain undetected while performing
malicious activities. Thanks to ptrace_may_access(), various part of
the kernel can check if a tracer is more privileged than a tracee.
A landlocked process has fewer privileges than a non-landlocked process
and must then be subject to additional restrictions when manipulating
processes. To be allowed to use ptrace(2) and related syscalls on a
target process, a landlocked process must have a subset of the target
process' rules (i.e. the tracee must be in a sub-domain of the tracer).
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: James Morris <jmorris@namei.org>
Cc: Kees Cook <keescook@chromium.org>
Cc: Serge E. Hallyn <serge@hallyn.com>
---
Changes since v13:
* Make the ptrace restriction mandatory, like in the v10.
* Remove the eBPF dependency.
Previous version:
https://lore.kernel.org/lkml/20191104172146.30797-5-mic@digikod.net/
---
security/landlock/Makefile | 2 +-
security/landlock/ptrace.c | 118 +++++++++++++++++++++++++++++++++++++
security/landlock/ptrace.h | 14 +++++
security/landlock/setup.c | 2 +
4 files changed, 135 insertions(+), 1 deletion(-)
create mode 100644 security/landlock/ptrace.c
create mode 100644 security/landlock/ptrace.h
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index 041ea242e627..f1d1eb72fa76 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -1,4 +1,4 @@
obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
landlock-y := setup.o object.o ruleset.o \
- cred.o
+ cred.o ptrace.o
diff --git a/security/landlock/ptrace.c b/security/landlock/ptrace.c
new file mode 100644
index 000000000000..6c7326788c46
--- /dev/null
+++ b/security/landlock/ptrace.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Ptrace hooks
+ *
+ * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2020 ANSSI
+ */
+
+#include <asm/current.h>
+#include <linux/cred.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/lsm_hooks.h>
+#include <linux/rcupdate.h>
+#include <linux/sched.h>
+
+#include "cred.h"
+#include "ptrace.h"
+#include "ruleset.h"
+#include "setup.h"
+
+/**
+ * domain_scope_le - Checks domain ordering for scoped ptrace
+ *
+ * @parent: Parent domain.
+ * @child: Potential child of @parent.
+ *
+ * Checks if the @parent domain is less or equal to (i.e. an ancestor, which
+ * means a subset of) the @child domain.
+ */
+static bool domain_scope_le(const struct landlock_ruleset *parent,
+ const struct landlock_ruleset *child)
+{
+ const struct landlock_hierarchy *walker;
+
+ if (!parent)
+ return true;
+ if (!child)
+ return false;
+ for (walker = child->hierarchy; walker; walker = walker->parent) {
+ if (walker == parent->hierarchy)
+ /* @parent is in the scoped hierarchy of @child. */
+ return true;
+ }
+ /* There is no relationship between @parent and @child. */
+ return false;
+}
+
+static bool task_is_scoped(struct task_struct *parent,
+ struct task_struct *child)
+{
+ bool is_scoped;
+ const struct landlock_ruleset *dom_parent, *dom_child;
+
+ rcu_read_lock();
+ dom_parent = landlock_get_task_domain(parent);
+ dom_child = landlock_get_task_domain(child);
+ is_scoped = domain_scope_le(dom_parent, dom_child);
+ rcu_read_unlock();
+ return is_scoped;
+}
+
+static int task_ptrace(struct task_struct *parent, struct task_struct *child)
+{
+ /* Quick return for non-landlocked tasks. */
+ if (!landlocked(parent))
+ return 0;
+ if (task_is_scoped(parent, child))
+ return 0;
+ return -EPERM;
+}
+
+/**
+ * hook_ptrace_access_check - Determines whether the current process may access
+ * another
+ *
+ * @child: Process to be accessed.
+ * @mode: Mode of attachment.
+ *
+ * If the current task has Landlock rules, then the child must have at least
+ * the same rules. Else denied.
+ *
+ * Determines whether a process may access another, returning 0 if permission
+ * granted, -errno if denied.
+ */
+static int hook_ptrace_access_check(struct task_struct *child,
+ unsigned int mode)
+{
+ return task_ptrace(current, child);
+}
+
+/**
+ * hook_ptrace_traceme - Determines whether another process may trace the
+ * current one
+ *
+ * @parent: Task proposed to be the tracer.
+ *
+ * If the parent has Landlock rules, then the current task must have the same
+ * or more rules. Else denied.
+ *
+ * Determines whether the nominated task is permitted to trace the current
+ * process, returning 0 if permission is granted, -errno if denied.
+ */
+static int hook_ptrace_traceme(struct task_struct *parent)
+{
+ return task_ptrace(parent, current);
+}
+
+static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = {
+ LSM_HOOK_INIT(ptrace_access_check, hook_ptrace_access_check),
+ LSM_HOOK_INIT(ptrace_traceme, hook_ptrace_traceme),
+};
+
+__init void landlock_add_hooks_ptrace(void)
+{
+ security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
+ LANDLOCK_NAME);
+}
diff --git a/security/landlock/ptrace.h b/security/landlock/ptrace.h
new file mode 100644
index 000000000000..6740c6a723de
--- /dev/null
+++ b/security/landlock/ptrace.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Ptrace hooks
+ *
+ * Copyright © 2017-2019 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2019 ANSSI
+ */
+
+#ifndef _SECURITY_LANDLOCK_PTRACE_H
+#define _SECURITY_LANDLOCK_PTRACE_H
+
+__init void landlock_add_hooks_ptrace(void);
+
+#endif /* _SECURITY_LANDLOCK_PTRACE_H */
diff --git a/security/landlock/setup.c b/security/landlock/setup.c
index fca5fa185465..117afb344da6 100644
--- a/security/landlock/setup.c
+++ b/security/landlock/setup.c
@@ -10,6 +10,7 @@
#include <linux/lsm_hooks.h>
#include "cred.h"
+#include "ptrace.h"
#include "setup.h"
struct lsm_blob_sizes landlock_blob_sizes __lsm_ro_after_init = {
@@ -20,6 +21,7 @@ static int __init landlock_init(void)
{
pr_info(LANDLOCK_NAME ": Registering hooks\n");
landlock_add_hooks_cred();
+ landlock_add_hooks_ptrace();
return 0;
}
--
2.25.0
^ permalink raw reply related
* [RFC PATCH v14 03/10] landlock: Set up the security framework and manage credentials
From: Mickaël Salaün @ 2020-02-24 16:02 UTC (permalink / raw)
To: linux-kernel
Cc: Mickaël Salaün, Al Viro, Andy Lutomirski, Arnd Bergmann,
Casey Schaufler, Greg Kroah-Hartman, James Morris, Jann Horn,
Jonathan Corbet, Kees Cook, Michael Kerrisk,
Mickaël Salaün, Serge E . Hallyn, Shuah Khan,
Vincent Dagonneau, kernel-hardening, linux-api, linux-arch,
linux-doc, linux-fsdevel, linux-kselftest, linux-security-module,
x86
In-Reply-To: <20200224160215.4136-1-mic@digikod.net>
A process credentials point to a Landlock domain, which is underneath
implemented with a ruleset. In the following commits, this domain is
used to check and enforce the ptrace and filesystem security policies.
A domain is inherited from a parent to its child the same way a thread
inherits a seccomp policy.
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: James Morris <jmorris@namei.org>
Cc: Kees Cook <keescook@chromium.org>
Cc: Serge E. Hallyn <serge@hallyn.com>
---
Changes since v13:
* totally get ride of the seccomp dependency
* only keep credential management and LSM setup.
Previous version:
https://lore.kernel.org/lkml/20191104172146.30797-4-mic@digikod.net/
---
security/Kconfig | 10 +++----
security/landlock/Makefile | 3 ++-
security/landlock/cred.c | 47 ++++++++++++++++++++++++++++++++
security/landlock/cred.h | 55 ++++++++++++++++++++++++++++++++++++++
security/landlock/setup.c | 30 +++++++++++++++++++++
security/landlock/setup.h | 18 +++++++++++++
6 files changed, 157 insertions(+), 6 deletions(-)
create mode 100644 security/landlock/cred.c
create mode 100644 security/landlock/cred.h
create mode 100644 security/landlock/setup.c
create mode 100644 security/landlock/setup.h
diff --git a/security/Kconfig b/security/Kconfig
index 9d9981394fb0..76547b5c694d 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -278,11 +278,11 @@ endchoice
config LSM
string "Ordered list of enabled LSMs"
- 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"
+ default "landlock,lockdown,yama,loadpin,safesetid,integrity,smack,selinux,tomoyo,apparmor" if DEFAULT_SECURITY_SMACK
+ default "landlock,lockdown,yama,loadpin,safesetid,integrity,apparmor,selinux,smack,tomoyo" if DEFAULT_SECURITY_APPARMOR
+ default "landlock,lockdown,yama,loadpin,safesetid,integrity,tomoyo" if DEFAULT_SECURITY_TOMOYO
+ default "landlock,lockdown,yama,loadpin,safesetid,integrity" if DEFAULT_SECURITY_DAC
+ default "landlock,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
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index d846eba445bb..041ea242e627 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -1,3 +1,4 @@
obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
-landlock-y := object.o ruleset.o
+landlock-y := setup.o object.o ruleset.o \
+ cred.o
diff --git a/security/landlock/cred.c b/security/landlock/cred.c
new file mode 100644
index 000000000000..69ef93e29a53
--- /dev/null
+++ b/security/landlock/cred.c
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Credential hooks
+ *
+ * Copyright © 2017-2019 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2019 ANSSI
+ */
+
+#include <linux/cred.h>
+#include <linux/lsm_hooks.h>
+
+#include "cred.h"
+#include "ruleset.h"
+#include "setup.h"
+
+static int hook_cred_prepare(struct cred *new, const struct cred *old,
+ gfp_t gfp)
+{
+ const struct landlock_cred_security *cred_old = landlock_cred(old);
+ struct landlock_cred_security *cred_new = landlock_cred(new);
+ struct landlock_ruleset *dom_old;
+
+ dom_old = cred_old->domain;
+ if (dom_old) {
+ landlock_get_ruleset(dom_old);
+ cred_new->domain = dom_old;
+ } else {
+ cred_new->domain = NULL;
+ }
+ return 0;
+}
+
+static void hook_cred_free(struct cred *cred)
+{
+ landlock_put_ruleset_enqueue(landlock_cred(cred)->domain);
+}
+
+static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = {
+ LSM_HOOK_INIT(cred_prepare, hook_cred_prepare),
+ LSM_HOOK_INIT(cred_free, hook_cred_free),
+};
+
+__init void landlock_add_hooks_cred(void)
+{
+ security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
+ LANDLOCK_NAME);
+}
diff --git a/security/landlock/cred.h b/security/landlock/cred.h
new file mode 100644
index 000000000000..1e24682ee27e
--- /dev/null
+++ b/security/landlock/cred.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Credential hooks
+ *
+ * Copyright © 2019 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2019 ANSSI
+ */
+
+#ifndef _SECURITY_LANDLOCK_CRED_H
+#define _SECURITY_LANDLOCK_CRED_H
+
+#include <linux/cred.h>
+#include <linux/init.h>
+#include <linux/rcupdate.h>
+
+#include "ruleset.h"
+#include "setup.h"
+
+struct landlock_cred_security {
+ struct landlock_ruleset *domain;
+};
+
+static inline struct landlock_cred_security *landlock_cred(
+ const struct cred *cred)
+{
+ return cred->security + landlock_blob_sizes.lbs_cred;
+}
+
+static inline struct landlock_ruleset *landlock_get_current_domain(void)
+{
+ return landlock_cred(current_cred())->domain;
+}
+
+/*
+ * The caller needs an RCU lock.
+ */
+static inline struct landlock_ruleset *landlock_get_task_domain(
+ struct task_struct *task)
+{
+ return landlock_cred(__task_cred(task))->domain;
+}
+
+static inline bool landlocked(struct task_struct *task)
+{
+ bool has_dom;
+
+ rcu_read_lock();
+ has_dom = !!landlock_get_task_domain(task);
+ rcu_read_unlock();
+ return has_dom;
+}
+
+__init void landlock_add_hooks_cred(void);
+
+#endif /* _SECURITY_LANDLOCK_CRED_H */
diff --git a/security/landlock/setup.c b/security/landlock/setup.c
new file mode 100644
index 000000000000..fca5fa185465
--- /dev/null
+++ b/security/landlock/setup.c
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Security framework setup
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ */
+
+#include <linux/init.h>
+#include <linux/lsm_hooks.h>
+
+#include "cred.h"
+#include "setup.h"
+
+struct lsm_blob_sizes landlock_blob_sizes __lsm_ro_after_init = {
+ .lbs_cred = sizeof(struct landlock_cred_security),
+};
+
+static int __init landlock_init(void)
+{
+ pr_info(LANDLOCK_NAME ": Registering hooks\n");
+ landlock_add_hooks_cred();
+ return 0;
+}
+
+DEFINE_LSM(LANDLOCK_NAME) = {
+ .name = LANDLOCK_NAME,
+ .init = landlock_init,
+ .blobs = &landlock_blob_sizes,
+};
diff --git a/security/landlock/setup.h b/security/landlock/setup.h
new file mode 100644
index 000000000000..52eb8d806376
--- /dev/null
+++ b/security/landlock/setup.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Security framework setup
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ */
+
+#ifndef _SECURITY_LANDLOCK_SETUP_H
+#define _SECURITY_LANDLOCK_SETUP_H
+
+#include <linux/lsm_hooks.h>
+
+#define LANDLOCK_NAME "landlock"
+
+extern struct lsm_blob_sizes landlock_blob_sizes;
+
+#endif /* _SECURITY_LANDLOCK_SETUP_H */
--
2.25.0
^ permalink raw reply related
* [RFC PATCH v14 10/10] landlock: Add user and kernel documentation
From: Mickaël Salaün @ 2020-02-24 16:02 UTC (permalink / raw)
To: linux-kernel
Cc: Mickaël Salaün, Al Viro, Andy Lutomirski, Arnd Bergmann,
Casey Schaufler, Greg Kroah-Hartman, James Morris, Jann Horn,
Jonathan Corbet, Kees Cook, Michael Kerrisk,
Mickaël Salaün, Serge E . Hallyn, Shuah Khan,
Vincent Dagonneau, kernel-hardening, linux-api, linux-arch,
linux-doc, linux-fsdevel, linux-kselftest, linux-security-module,
x86
In-Reply-To: <20200224160215.4136-1-mic@digikod.net>
This documentation can be built with the Sphinx framework.
Another location might be more appropriate, though.
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Reviewed-by: Vincent Dagonneau <vincent.dagonneau@ssi.gouv.fr>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: James Morris <jmorris@namei.org>
Cc: Kees Cook <keescook@chromium.org>
Cc: Serge E. Hallyn <serge@hallyn.com>
---
Changes since v13:
* Rewrote the documentation according to the major revamp.
Previous version:
https://lore.kernel.org/lkml/20191104172146.30797-8-mic@digikod.net/
---
Documentation/security/index.rst | 1 +
Documentation/security/landlock/index.rst | 18 ++
Documentation/security/landlock/kernel.rst | 44 ++++
Documentation/security/landlock/user.rst | 233 +++++++++++++++++++++
4 files changed, 296 insertions(+)
create mode 100644 Documentation/security/landlock/index.rst
create mode 100644 Documentation/security/landlock/kernel.rst
create mode 100644 Documentation/security/landlock/user.rst
diff --git a/Documentation/security/index.rst b/Documentation/security/index.rst
index fc503dd689a7..4d213e76ddf4 100644
--- a/Documentation/security/index.rst
+++ b/Documentation/security/index.rst
@@ -15,3 +15,4 @@ Security Documentation
self-protection
siphash
tpm/index
+ landlock/index
diff --git a/Documentation/security/landlock/index.rst b/Documentation/security/landlock/index.rst
new file mode 100644
index 000000000000..dbd33b96ce60
--- /dev/null
+++ b/Documentation/security/landlock/index.rst
@@ -0,0 +1,18 @@
+=========================================
+Landlock LSM: unprivileged access control
+=========================================
+
+:Author: Mickaël Salaün
+
+The goal of Landlock is to enable to restrict ambient rights (e.g. global
+filesystem access) for a set of processes. Because Landlock is a stackable
+LSM, it makes possible to create safe security sandboxes as new security layers
+in addition to the existing system-wide access-controls. This kind of sandbox
+is expected to help mitigate the security impact of bugs or
+unexpected/malicious behaviors in user-space applications. Landlock empower any
+process, including unprivileged ones, to securely restrict themselves.
+
+.. toctree::
+
+ user
+ kernel
diff --git a/Documentation/security/landlock/kernel.rst b/Documentation/security/landlock/kernel.rst
new file mode 100644
index 000000000000..b87769909029
--- /dev/null
+++ b/Documentation/security/landlock/kernel.rst
@@ -0,0 +1,44 @@
+==============================
+Landlock: kernel documentation
+==============================
+
+Landlock's goal is to create scoped access-control (i.e. sandboxing). To
+harden a whole system, this feature should be available to any process,
+including unprivileged ones. Because such process may be compromised or
+backdoored (i.e. untrusted), Landlock's features must be safe to use from the
+kernel and other processes point of view. Landlock's interface must therefore
+expose a minimal attack surface.
+
+Landlock is designed to be usable by unprivileged processes while following the
+system security policy enforced by other access control mechanisms (e.g. DAC,
+LSM). Indeed, a Landlock rule shall not interfere with other access-controls
+enforced on the system, only add more restrictions.
+
+Any user can enforce Landlock rulesets on their processes. They are merged and
+evaluated according to the inherited ones in a way that ensure that only more
+constraints can be added.
+
+
+Guiding principles for safe access controls
+===========================================
+
+* A Landlock rule shall be focused on access control on kernel objects instead
+ of syscall filtering (i.e. syscall arguments), which is the purpose of
+ seccomp-bpf.
+* To avoid multiple kind of side-channel attacks (e.g. leak of security
+ policies, CPU-based attacks), Landlock rules shall not be able to
+ programmatically communicate with user space.
+* Kernel access check shall not slow down access request from unsandboxed
+ processes.
+* Computation related to Landlock operations (e.g. enforce a ruleset) shall
+ only impact the processes requesting them.
+
+
+Landlock rulesets and domains
+=============================
+
+A domain is a read-only ruleset tied to a set of subjects (i.e. tasks). A
+domain can transition to a new one which is the intersection of the constraints
+from the current and a new ruleset. The definition of a subject is implicit
+for a task sandboxing itself, which makes the reasoning much easier and helps
+avoid pitfalls.
diff --git a/Documentation/security/landlock/user.rst b/Documentation/security/landlock/user.rst
new file mode 100644
index 000000000000..cbd7f61fca8c
--- /dev/null
+++ b/Documentation/security/landlock/user.rst
@@ -0,0 +1,233 @@
+=================================
+Landlock: userspace documentation
+=================================
+
+Landlock rules
+==============
+
+A Landlock rule enables to describe an action on an object. An object is
+currently a file hierarchy, and the related filesystem actions are defined in
+`Access rights`_. A set of rules are aggregated in a ruleset, which can then
+restricts the thread enforcing it, and its future children.
+
+
+Defining and enforcing a security policy
+----------------------------------------
+
+Before defining a security policy, an application should first probe for the
+features supported by the running kernel, which is important to be compatible
+with older kernels. This can be done thanks to the `landlock` syscall (cf.
+:ref:`syscall`).
+
+.. code-block:: c
+
+ struct landlock_attr_features attr_features;
+
+ if (landlock(LANDLOCK_CMD_GET_FEATURES, LANDLOCK_OPT_GET_FEATURES,
+ sizeof(attr_features), &attr_features)) {
+ perror("Failed to probe the Landlock supported features");
+ return 1;
+ }
+
+Then, we need to create the ruleset that will contains our rules. For this
+example, the ruleset will contains rules which only allow read actions, but
+write actions will be denied. The ruleset then needs to handle both of these
+kind of actions. To have a backward compatibility, these actions should be
+ANDed with the supported ones.
+
+.. code-block:: c
+
+ int ruleset_fd;
+ struct landlock_attr_ruleset ruleset = {
+ .handled_access_fs =
+ LANDLOCK_ACCESS_FS_READ |
+ LANDLOCK_ACCESS_FS_READDIR |
+ LANDLOCK_ACCESS_FS_EXECUTE |
+ LANDLOCK_ACCESS_FS_WRITE |
+ LANDLOCK_ACCESS_FS_TRUNCATE |
+ LANDLOCK_ACCESS_FS_CHMOD |
+ LANDLOCK_ACCESS_FS_CHOWN |
+ LANDLOCK_ACCESS_FS_CHGRP |
+ LANDLOCK_ACCESS_FS_LINK_TO |
+ LANDLOCK_ACCESS_FS_RENAME_FROM |
+ LANDLOCK_ACCESS_FS_RENAME_TO |
+ LANDLOCK_ACCESS_FS_RMDIR |
+ LANDLOCK_ACCESS_FS_UNLINK |
+ LANDLOCK_ACCESS_FS_MAKE_CHAR |
+ LANDLOCK_ACCESS_FS_MAKE_DIR |
+ LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_MAKE_SOCK |
+ LANDLOCK_ACCESS_FS_MAKE_FIFO |
+ LANDLOCK_ACCESS_FS_MAKE_BLOCK |
+ LANDLOCK_ACCESS_FS_MAKE_SYM,
+ };
+
+ ruleset.handled_access_fs &= attr_features.access_fs;
+ ruleset_fd = landlock(LANDLOCK_CMD_CREATE_RULESET,
+ LANDLOCK_OPT_CREATE_RULESET, sizeof(ruleset), &ruleset);
+ if (ruleset_fd < 0) {
+ perror("Failed to create a ruleset");
+ return 1;
+ }
+
+We can now add a new rule to this ruleset thanks to the returned file
+descriptor referring to this ruleset. The rule will only enable to read the
+file hierarchy ``/usr``. Without other rule, write actions would then be
+denied by the ruleset. To add ``/usr`` to the ruleset, we open it with the
+``O_PATH`` flag and fill the &struct landlock_attr_path_beneath with this file
+descriptor.
+
+.. code-block:: c
+
+ int err;
+ struct landlock_attr_path_beneath path_beneath = {
+ .ruleset_fd = ruleset_fd,
+ .allowed_access =
+ LANDLOCK_ACCESS_FS_READ |
+ LANDLOCK_ACCESS_FS_READDIR |
+ LANDLOCK_ACCESS_FS_EXECUTE,
+ };
+
+ path_beneath.allowed_access &= attr_features.access_fs;
+ path_beneath.parent_fd = open("/usr", O_PATH | O_CLOEXEC);
+ if (path_beneath.parent_fd < 0) {
+ perror("Failed to open file");
+ close(ruleset_fd);
+ return 1;
+ }
+ err = landlock(LANDLOCK_CMD_ADD_RULE, LANDLOCK_OPT_ADD_RULE_PATH_BENEATH,
+ sizeof(path_beneath), &path_beneath);
+ close(path_beneath.parent_fd);
+ if (err) {
+ perror("Failed to update ruleset");
+ close(ruleset_fd);
+ return 1;
+ }
+
+We now have a ruleset with one rule allowing read access to ``/usr`` while
+denying all accesses featured in ``attr_features.access_fs`` to everything else
+on the filesystem. The next step is to restrict the current thread from
+gaining more privileges (e.g. thanks to a SUID binary).
+
+.. code-block:: c
+
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
+ perror("Failed to restrict privileges");
+ close(ruleset_fd);
+ return 1;
+ }
+
+The current thread is now ready to sandbox itself with the ruleset.
+
+.. code-block:: c
+
+ struct landlock_attr_enforce attr_enforce = {
+ .ruleset_fd = ruleset_fd,
+ };
+
+ if (landlock(LANDLOCK_CMD_ENFORCE_RULESET, LANDLOCK_OPT_ENFORCE_RULESET,
+ sizeof(attr_enforce), &attr_enforce)) {
+ perror("Failed to enforce ruleset");
+ close(ruleset_fd);
+ return 1;
+ }
+ close(ruleset_fd);
+
+If this last system call succeeds, the current thread is now restricted and
+this policy will be enforced on all its subsequently created children as well.
+Once a thread is landlocked, there is no way to remove its security policy,
+only adding more restrictions is allowed. These threads are now in a new
+Landlock domain, merge of their parent one (if any) with the new ruleset.
+
+A full working code can be found in `samples/landlock/sandboxer.c`_.
+
+
+Inheritance
+-----------
+
+Every new thread resulting from a :manpage:`clone(2)` inherits Landlock program
+restrictions from its parent. This is similar to the seccomp inheritance (cf.
+:doc:`/userspace-api/seccomp_filter`) or any other LSM dealing with task's
+:manpage:`credentials(7)`. For instance, one process' thread may apply
+Landlock rules to itself, but they will not be automatically applied to other
+sibling threads (unlike POSIX thread credential changes, cf.
+:manpage:`nptl(7)`).
+
+
+Ptrace restrictions
+-------------------
+
+A sandboxed process has less privileges than a non-sandboxed process and must
+then be subject to additional restrictions when manipulating another process.
+To be allowed to use :manpage:`ptrace(2)` and related syscalls on a target
+process, a sandboxed process should have a subset of the target process rules,
+which means the tracee must be in a sub-domain of the tracer.
+
+
+.. _syscall:
+
+The `landlock` syscall and its arguments
+========================================
+
+.. kernel-doc:: security/landlock/syscall.c
+ :functions: sys_landlock
+
+Commands
+--------
+
+.. kernel-doc:: include/uapi/linux/landlock.h
+ :functions: landlock_cmd
+
+Options
+-------
+
+.. kernel-doc:: include/uapi/linux/landlock.h
+ :functions: options_intro
+ options_get_features options_create_ruleset
+ options_add_rule options_enforce_ruleset
+
+Attributes
+----------
+
+.. kernel-doc:: include/uapi/linux/landlock.h
+ :functions: landlock_attr_features landlock_attr_ruleset
+ landlock_attr_path_beneath landlock_attr_enforce
+
+Access rights
+-------------
+
+.. kernel-doc:: include/uapi/linux/landlock.h
+ :functions: fs_access
+
+
+Questions and answers
+=====================
+
+What about user space sandbox managers?
+---------------------------------------
+
+Using user space process to enforce restrictions on kernel resources can lead
+to race conditions or inconsistent evaluations (i.e. `Incorrect mirroring of
+the OS code and state
+<https://www.ndss-symposium.org/ndss2003/traps-and-pitfalls-practical-problems-system-call-interposition-based-security-tools/>`_).
+
+What about namespaces and containers?
+-------------------------------------
+
+Namespaces can help create sandboxes but they are not designed for
+access-control and then miss useful features for such use case (e.g. no
+fine-grained restrictions). Moreover, their complexity can lead to security
+issues, especially when untrusted processes can manipulate them (cf.
+`Controlling access to user namespaces <https://lwn.net/Articles/673597/>`_).
+
+
+Additional documentation
+========================
+
+See https://landlock.io
+
+
+.. Links
+.. _samples/landlock/sandboxer.c: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/samples/landlock/sandboxer.c
+.. _tools/testing/selftests/landlock/: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/tools/testing/selftests/landlock/
+.. _tools/testing/selftests/landlock/test_ptrace.c: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/tools/testing/selftests/landlock/test_ptrace.c
--
2.25.0
^ permalink raw reply related
* [PATCH v2 0/6] proc: Dentry flushing without proc_mnt
From: Eric W. Biederman @ 2020-02-24 16:25 UTC (permalink / raw)
To: Linus Torvalds
Cc: Al Viro, LKML, Kernel Hardening, Linux API, Linux FS Devel,
Linux Security Module, Akinobu Mita, Alexey Dobriyan,
Andrew Morton, Andy Lutomirski, Daniel Micay, Djalal Harouni,
Dmitry V . Levin, Greg Kroah-Hartman, Ingo Molnar,
J . Bruce Fields, Jeff Layton, Jonathan Corbet, Kees Cook,
Oleg Nesterov, Solar Designer, Alexey Gladkov
In-Reply-To: <871rqpaswu.fsf_-_@x220.int.ebiederm.org>
I have addressed all of the review comments as I understand them,
and fixed the small oversight the kernel test robot was able to
find. (I had failed to initialize the new field pid->inodes).
I did not hear any concerns from the 10,000 foot level last time
so I am assuming this set of changes (baring bugs) is good to go.
Unless some new issues appear my plan is to put this in my tree
and get this into linux-next. Which will give Alexey something
to build his changes on.
I tested this set of changes by running:
(while ls -1 -f /proc > /dev/null ; do :; done ) &
And monitoring the amount of free memory.
With the flushing disabled I saw the used memory in the system grow by
20M before the shrinker would bring it back down to where it started.
With the patch applied I saw the memory usage stay essentially fixed.
So flushing definitely keeps things working better.
If anyone sees any problems with this code please let me know.
Thank you,
Eric W. Biederman (6):
proc: Rename in proc_inode rename sysctl_inodes sibling_inodes
proc: Generalize proc_sys_prune_dcache into proc_prune_siblings_dcache
proc: In proc_prune_siblings_dcache cache an aquired super block
proc: Use d_invalidate in proc_prune_siblings_dcache
proc: Clear the pieces of proc_inode that proc_evict_inode cares about
proc: Use a list of inodes to flush from proc
fs/proc/base.c | 111 ++++++++++++++++--------------------------------
fs/proc/inode.c | 73 ++++++++++++++++++++++++++++---
fs/proc/internal.h | 4 +-
fs/proc/proc_sysctl.c | 45 +++-----------------
include/linux/pid.h | 1 +
include/linux/proc_fs.h | 4 +-
kernel/exit.c | 4 +-
kernel/pid.c | 1 +
8 files changed, 120 insertions(+), 123 deletions(-)
^ permalink raw reply
* [PATCH v2 1/6] proc: Rename in proc_inode rename sysctl_inodes sibling_inodes
From: Eric W. Biederman @ 2020-02-24 16:26 UTC (permalink / raw)
To: Linus Torvalds
Cc: Al Viro, LKML, Kernel Hardening, Linux API, Linux FS Devel,
Linux Security Module, Akinobu Mita, Alexey Dobriyan,
Andrew Morton, Andy Lutomirski, Daniel Micay, Djalal Harouni,
Dmitry V . Levin, Greg Kroah-Hartman, Ingo Molnar,
J . Bruce Fields, Jeff Layton, Jonathan Corbet, Kees Cook,
Oleg Nesterov, Solar Designer, Alexey Gladkov
In-Reply-To: <871rqk2brn.fsf_-_@x220.int.ebiederm.org>
I about to need and use the same functionality for pid based
inodes and there is no point in adding a second field when
this field is already here and serving the same purporse.
Just give the field a generic name so it is clear that
it is no longer sysctl specific.
Also for good measure initialize sibling_inodes when
proc_inode is initialized.
Signed-off-by: Eric W. Biederman <ebiederm@xmission.com>
---
fs/proc/inode.c | 1 +
fs/proc/internal.h | 2 +-
fs/proc/proc_sysctl.c | 8 ++++----
3 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 6da18316d209..bdae442d5262 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -68,6 +68,7 @@ static struct inode *proc_alloc_inode(struct super_block *sb)
ei->pde = NULL;
ei->sysctl = NULL;
ei->sysctl_entry = NULL;
+ INIT_HLIST_NODE(&ei->sibling_inodes);
ei->ns_ops = NULL;
return &ei->vfs_inode;
}
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index 41587276798e..366cd3aa690b 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -91,7 +91,7 @@ struct proc_inode {
struct proc_dir_entry *pde;
struct ctl_table_header *sysctl;
struct ctl_table *sysctl_entry;
- struct hlist_node sysctl_inodes;
+ struct hlist_node sibling_inodes;
const struct proc_ns_operations *ns_ops;
struct inode vfs_inode;
} __randomize_layout;
diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index c75bb4632ed1..42fbb7f3c587 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -279,9 +279,9 @@ static void proc_sys_prune_dcache(struct ctl_table_header *head)
node = hlist_first_rcu(&head->inodes);
if (!node)
break;
- ei = hlist_entry(node, struct proc_inode, sysctl_inodes);
+ ei = hlist_entry(node, struct proc_inode, sibling_inodes);
spin_lock(&sysctl_lock);
- hlist_del_init_rcu(&ei->sysctl_inodes);
+ hlist_del_init_rcu(&ei->sibling_inodes);
spin_unlock(&sysctl_lock);
inode = &ei->vfs_inode;
@@ -483,7 +483,7 @@ static struct inode *proc_sys_make_inode(struct super_block *sb,
}
ei->sysctl = head;
ei->sysctl_entry = table;
- hlist_add_head_rcu(&ei->sysctl_inodes, &head->inodes);
+ hlist_add_head_rcu(&ei->sibling_inodes, &head->inodes);
head->count++;
spin_unlock(&sysctl_lock);
@@ -514,7 +514,7 @@ static struct inode *proc_sys_make_inode(struct super_block *sb,
void proc_sys_evict_inode(struct inode *inode, struct ctl_table_header *head)
{
spin_lock(&sysctl_lock);
- hlist_del_init_rcu(&PROC_I(inode)->sysctl_inodes);
+ hlist_del_init_rcu(&PROC_I(inode)->sibling_inodes);
if (!--head->count)
kfree_rcu(head, rcu);
spin_unlock(&sysctl_lock);
--
2.25.0
^ permalink raw reply related
* [PATCH v2 2/6] proc: Generalize proc_sys_prune_dcache into proc_prune_siblings_dcache
From: Eric W. Biederman @ 2020-02-24 16:27 UTC (permalink / raw)
To: Linus Torvalds
Cc: Al Viro, LKML, Kernel Hardening, Linux API, Linux FS Devel,
Linux Security Module, Akinobu Mita, Alexey Dobriyan,
Andrew Morton, Andy Lutomirski, Daniel Micay, Djalal Harouni,
Dmitry V . Levin, Greg Kroah-Hartman, Ingo Molnar,
J . Bruce Fields, Jeff Layton, Jonathan Corbet, Kees Cook,
Oleg Nesterov, Solar Designer, Alexey Gladkov
In-Reply-To: <871rqk2brn.fsf_-_@x220.int.ebiederm.org>
This prepares the way for allowing the pid part of proc to use this
dcache pruning code as well.
Signed-off-by: Eric W. Biederman <ebiederm@xmission.com>
---
fs/proc/inode.c | 38 ++++++++++++++++++++++++++++++++++++++
fs/proc/internal.h | 1 +
fs/proc/proc_sysctl.c | 35 +----------------------------------
3 files changed, 40 insertions(+), 34 deletions(-)
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index bdae442d5262..74ce4a8d05eb 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -103,6 +103,44 @@ void __init proc_init_kmemcache(void)
BUILD_BUG_ON(sizeof(struct proc_dir_entry) >= SIZEOF_PDE);
}
+void proc_prune_siblings_dcache(struct hlist_head *inodes, spinlock_t *lock)
+{
+ struct inode *inode;
+ struct proc_inode *ei;
+ struct hlist_node *node;
+ struct super_block *sb;
+
+ rcu_read_lock();
+ for (;;) {
+ node = hlist_first_rcu(inodes);
+ if (!node)
+ break;
+ ei = hlist_entry(node, struct proc_inode, sibling_inodes);
+ spin_lock(lock);
+ hlist_del_init_rcu(&ei->sibling_inodes);
+ spin_unlock(lock);
+
+ inode = &ei->vfs_inode;
+ sb = inode->i_sb;
+ if (!atomic_inc_not_zero(&sb->s_active))
+ continue;
+ inode = igrab(inode);
+ rcu_read_unlock();
+ if (unlikely(!inode)) {
+ deactivate_super(sb);
+ rcu_read_lock();
+ continue;
+ }
+
+ d_prune_aliases(inode);
+ iput(inode);
+ deactivate_super(sb);
+
+ rcu_read_lock();
+ }
+ rcu_read_unlock();
+}
+
static int proc_show_options(struct seq_file *seq, struct dentry *root)
{
struct super_block *sb = root->d_sb;
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index 366cd3aa690b..ba9a991824a5 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -210,6 +210,7 @@ extern const struct inode_operations proc_pid_link_inode_operations;
extern const struct super_operations proc_sops;
void proc_init_kmemcache(void);
+void proc_prune_siblings_dcache(struct hlist_head *inodes, spinlock_t *lock);
void set_proc_pid_nlink(void);
extern struct inode *proc_get_inode(struct super_block *, struct proc_dir_entry *);
extern void proc_entry_rundown(struct proc_dir_entry *);
diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index 42fbb7f3c587..5da9d7f7ae34 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -269,40 +269,7 @@ static void unuse_table(struct ctl_table_header *p)
static void proc_sys_prune_dcache(struct ctl_table_header *head)
{
- struct inode *inode;
- struct proc_inode *ei;
- struct hlist_node *node;
- struct super_block *sb;
-
- rcu_read_lock();
- for (;;) {
- node = hlist_first_rcu(&head->inodes);
- if (!node)
- break;
- ei = hlist_entry(node, struct proc_inode, sibling_inodes);
- spin_lock(&sysctl_lock);
- hlist_del_init_rcu(&ei->sibling_inodes);
- spin_unlock(&sysctl_lock);
-
- inode = &ei->vfs_inode;
- sb = inode->i_sb;
- if (!atomic_inc_not_zero(&sb->s_active))
- continue;
- inode = igrab(inode);
- rcu_read_unlock();
- if (unlikely(!inode)) {
- deactivate_super(sb);
- rcu_read_lock();
- continue;
- }
-
- d_prune_aliases(inode);
- iput(inode);
- deactivate_super(sb);
-
- rcu_read_lock();
- }
- rcu_read_unlock();
+ proc_prune_siblings_dcache(&head->inodes, &sysctl_lock);
}
/* called under sysctl_lock, will reacquire if has to wait */
--
2.25.0
^ permalink raw reply related
* [PATCH v2 3/6] proc: In proc_prune_siblings_dcache cache an aquired super block
From: Eric W. Biederman @ 2020-02-24 16:27 UTC (permalink / raw)
To: Linus Torvalds
Cc: Al Viro, LKML, Kernel Hardening, Linux API, Linux FS Devel,
Linux Security Module, Akinobu Mita, Alexey Dobriyan,
Andrew Morton, Andy Lutomirski, Daniel Micay, Djalal Harouni,
Dmitry V . Levin, Greg Kroah-Hartman, Ingo Molnar,
J . Bruce Fields, Jeff Layton, Jonathan Corbet, Kees Cook,
Oleg Nesterov, Solar Designer, Alexey Gladkov
In-Reply-To: <871rqk2brn.fsf_-_@x220.int.ebiederm.org>
Because there are likely to be several sysctls in a row on the
same superblock cache the super_block after the count has
been raised and don't deactivate it until we are processing
another super_block.
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
---
fs/proc/inode.c | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 74ce4a8d05eb..fa2dc732cd77 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -108,10 +108,11 @@ void proc_prune_siblings_dcache(struct hlist_head *inodes, spinlock_t *lock)
struct inode *inode;
struct proc_inode *ei;
struct hlist_node *node;
- struct super_block *sb;
+ struct super_block *old_sb = NULL;
rcu_read_lock();
for (;;) {
+ struct super_block *sb;
node = hlist_first_rcu(inodes);
if (!node)
break;
@@ -122,23 +123,28 @@ void proc_prune_siblings_dcache(struct hlist_head *inodes, spinlock_t *lock)
inode = &ei->vfs_inode;
sb = inode->i_sb;
- if (!atomic_inc_not_zero(&sb->s_active))
+ if ((sb != old_sb) && !atomic_inc_not_zero(&sb->s_active))
continue;
inode = igrab(inode);
rcu_read_unlock();
+ if (sb != old_sb) {
+ if (old_sb)
+ deactivate_super(old_sb);
+ old_sb = sb;
+ }
if (unlikely(!inode)) {
- deactivate_super(sb);
rcu_read_lock();
continue;
}
d_prune_aliases(inode);
iput(inode);
- deactivate_super(sb);
rcu_read_lock();
}
rcu_read_unlock();
+ if (old_sb)
+ deactivate_super(old_sb);
}
static int proc_show_options(struct seq_file *seq, struct dentry *root)
--
2.25.0
^ permalink raw reply related
* [PATCH v2 4/6] proc: Use d_invalidate in proc_prune_siblings_dcache
From: Eric W. Biederman @ 2020-02-24 16:28 UTC (permalink / raw)
To: Linus Torvalds
Cc: Al Viro, LKML, Kernel Hardening, Linux API, Linux FS Devel,
Linux Security Module, Akinobu Mita, Alexey Dobriyan,
Andrew Morton, Andy Lutomirski, Daniel Micay, Djalal Harouni,
Dmitry V . Levin, Greg Kroah-Hartman, Ingo Molnar,
J . Bruce Fields, Jeff Layton, Jonathan Corbet, Kees Cook,
Oleg Nesterov, Solar Designer, Alexey Gladkov
In-Reply-To: <871rqk2brn.fsf_-_@x220.int.ebiederm.org>
The function d_prune_aliases has the problem that it will only prune
aliases thare are completely unused. It will not remove aliases for
the dcache or even think of removing mounts from the dcache. For that
behavior d_invalidate is needed.
To use d_invalidate replace d_prune_aliases with d_find_alias followed
by d_invalidate and dput.
For completeness the directory and the non-directory cases are
separated because in theory (although not in currently in practice for
proc) directories can only ever have a single dentry while
non-directories can have hardlinks and thus multiple dentries.
As part of this separation use d_find_any_alias for directories
to spare d_find_alias the extra work of doing that.
Plus the differences between d_find_any_alias and d_find_alias makes
it clear why the directory and non-directory code and not share code.
To make it clear these routines now invalidate dentries rename
proc_prune_siblings_dache to proc_invalidate_siblings_dcache, and rename
proc_sys_prune_dcache proc_sys_invalidate_dcache.
V2: Split the directory and non-directory cases. To make this
code robust to future changes in proc.
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
---
fs/proc/inode.c | 16 ++++++++++++++--
fs/proc/internal.h | 2 +-
fs/proc/proc_sysctl.c | 8 ++++----
3 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index fa2dc732cd77..ba6acd300ce1 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -103,7 +103,7 @@ void __init proc_init_kmemcache(void)
BUILD_BUG_ON(sizeof(struct proc_dir_entry) >= SIZEOF_PDE);
}
-void proc_prune_siblings_dcache(struct hlist_head *inodes, spinlock_t *lock)
+void proc_invalidate_siblings_dcache(struct hlist_head *inodes, spinlock_t *lock)
{
struct inode *inode;
struct proc_inode *ei;
@@ -137,7 +137,19 @@ void proc_prune_siblings_dcache(struct hlist_head *inodes, spinlock_t *lock)
continue;
}
- d_prune_aliases(inode);
+ if (S_ISDIR(inode->i_mode)) {
+ struct dentry *dir = d_find_any_alias(inode);
+ if (dir) {
+ d_invalidate(dir);
+ dput(dir);
+ }
+ } else {
+ struct dentry *dentry;
+ while ((dentry = d_find_alias(inode))) {
+ d_invalidate(dentry);
+ dput(dentry);
+ }
+ }
iput(inode);
rcu_read_lock();
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index ba9a991824a5..fd470172675f 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -210,7 +210,7 @@ extern const struct inode_operations proc_pid_link_inode_operations;
extern const struct super_operations proc_sops;
void proc_init_kmemcache(void);
-void proc_prune_siblings_dcache(struct hlist_head *inodes, spinlock_t *lock);
+void proc_invalidate_siblings_dcache(struct hlist_head *inodes, spinlock_t *lock);
void set_proc_pid_nlink(void);
extern struct inode *proc_get_inode(struct super_block *, struct proc_dir_entry *);
extern void proc_entry_rundown(struct proc_dir_entry *);
diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index 5da9d7f7ae34..b6f5d459b087 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -267,9 +267,9 @@ static void unuse_table(struct ctl_table_header *p)
complete(p->unregistering);
}
-static void proc_sys_prune_dcache(struct ctl_table_header *head)
+static void proc_sys_invalidate_dcache(struct ctl_table_header *head)
{
- proc_prune_siblings_dcache(&head->inodes, &sysctl_lock);
+ proc_invalidate_siblings_dcache(&head->inodes, &sysctl_lock);
}
/* called under sysctl_lock, will reacquire if has to wait */
@@ -291,10 +291,10 @@ static void start_unregistering(struct ctl_table_header *p)
spin_unlock(&sysctl_lock);
}
/*
- * Prune dentries for unregistered sysctls: namespaced sysctls
+ * Invalidate dentries for unregistered sysctls: namespaced sysctls
* can have duplicate names and contaminate dcache very badly.
*/
- proc_sys_prune_dcache(p);
+ proc_sys_invalidate_dcache(p);
/*
* do not remove from the list until nobody holds it; walking the
* list in do_sysctl() relies on that.
--
2.25.0
^ permalink raw reply related
* [PATCH v2 5/6] proc: Clear the pieces of proc_inode that proc_evict_inode cares about
From: Eric W. Biederman @ 2020-02-24 16:28 UTC (permalink / raw)
To: Linus Torvalds
Cc: Al Viro, LKML, Kernel Hardening, Linux API, Linux FS Devel,
Linux Security Module, Akinobu Mita, Alexey Dobriyan,
Andrew Morton, Andy Lutomirski, Daniel Micay, Djalal Harouni,
Dmitry V . Levin, Greg Kroah-Hartman, Ingo Molnar,
J . Bruce Fields, Jeff Layton, Jonathan Corbet, Kees Cook,
Oleg Nesterov, Solar Designer, Alexey Gladkov
In-Reply-To: <871rqk2brn.fsf_-_@x220.int.ebiederm.org>
This just keeps everything tidier, and allows for using flags like
SLAB_TYPESAFE_BY_RCU where slabs are not always cleared before reuse.
I don't see reuse without reinitializing happening with the proc_inode
but I had a false alarm while reworking flushing of proc dentries and
indoes when a process dies that caused me to tidy this up.
The code is a little easier to follow and reason about this
way so I figured the changes might as well be kept.
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
---
fs/proc/inode.c | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index ba6acd300ce1..d9243b24554a 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -33,21 +33,27 @@ static void proc_evict_inode(struct inode *inode)
{
struct proc_dir_entry *de;
struct ctl_table_header *head;
+ struct proc_inode *ei = PROC_I(inode);
truncate_inode_pages_final(&inode->i_data);
clear_inode(inode);
/* Stop tracking associated processes */
- put_pid(PROC_I(inode)->pid);
+ if (ei->pid) {
+ put_pid(ei->pid);
+ ei->pid = NULL;
+ }
/* Let go of any associated proc directory entry */
- de = PDE(inode);
- if (de)
+ de = ei->pde;
+ if (de) {
pde_put(de);
+ ei->pde = NULL;
+ }
- head = PROC_I(inode)->sysctl;
+ head = ei->sysctl;
if (head) {
- RCU_INIT_POINTER(PROC_I(inode)->sysctl, NULL);
+ RCU_INIT_POINTER(ei->sysctl, NULL);
proc_sys_evict_inode(inode, head);
}
}
--
2.25.0
^ permalink raw reply related
* [PATCH v2 6/6] proc: Use a list of inodes to flush from proc
From: Eric W. Biederman @ 2020-02-24 16:29 UTC (permalink / raw)
To: Linus Torvalds
Cc: Al Viro, LKML, Kernel Hardening, Linux API, Linux FS Devel,
Linux Security Module, Akinobu Mita, Alexey Dobriyan,
Andrew Morton, Andy Lutomirski, Daniel Micay, Djalal Harouni,
Dmitry V . Levin, Greg Kroah-Hartman, Ingo Molnar,
J . Bruce Fields, Jeff Layton, Jonathan Corbet, Kees Cook,
Oleg Nesterov, Solar Designer, Alexey Gladkov
In-Reply-To: <871rqk2brn.fsf_-_@x220.int.ebiederm.org>
Rework the flushing of proc to use a list of directory inodes that
need to be flushed.
The list is kept on struct pid not on struct task_struct, as there is
a fixed connection between proc inodes and pids but at least for the
case of de_thread the pid of a task_struct changes.
This removes the dependency on proc_mnt which allows for different
mounts of proc having different mount options even in the same pid
namespace and this allows for the removal of proc_mnt which will
trivially the first mount of proc to honor it's mount options.
This flushing remains an optimization. The functions
pid_delete_dentry and pid_revalidate ensure that ordinary dcache
management will not attempt to use dentries past the point their
respective task has died. When unused the shrinker will
eventually be able to remove these dentries.
There is a case in de_thread where proc_flush_pid can be
called early for a given pid. Which winds up being
safe (if suboptimal) as this is just an optiimization.
Only pid directories are put on the list as the other
per pid files are children of those directories and
d_invalidate on the directory will get them as well.
So that the pid can be used during flushing it's reference count is
taken in release_task and dropped in proc_flush_pid. Further the call
of proc_flush_pid is moved after the tasklist_lock is released in
release_task so that it is certain that the pid has already been
unhashed when flushing it taking place. This removes a small race
where a dentry could recreated.
As struct pid is supposed to be small and I need a per pid lock
I reuse the only lock that currently exists in struct pid the
the wait_pidfd.lock.
The net result is that this adds all of this functionality
with just a little extra list management overhead and
a single extra pointer in struct pid.
v2: Initialize pid->inodes. I somehow failed to get that
initialization into the initial version of the patch. A boot
failure was reported by "kernel test robot <lkp@intel.com>", and
failure to initialize that pid->inodes matches all of the reported
symptoms.
Signed-off-by: Eric W. Biederman <ebiederm@xmission.com>
---
fs/proc/base.c | 111 +++++++++++++---------------------------
fs/proc/inode.c | 2 +-
fs/proc/internal.h | 1 +
include/linux/pid.h | 1 +
include/linux/proc_fs.h | 4 +-
kernel/exit.c | 4 +-
kernel/pid.c | 1 +
7 files changed, 45 insertions(+), 79 deletions(-)
diff --git a/fs/proc/base.c b/fs/proc/base.c
index c7c64272b0fa..e7efe9d6f3d6 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -1834,11 +1834,25 @@ void task_dump_owner(struct task_struct *task, umode_t mode,
*rgid = gid;
}
+void proc_pid_evict_inode(struct proc_inode *ei)
+{
+ struct pid *pid = ei->pid;
+
+ if (S_ISDIR(ei->vfs_inode.i_mode)) {
+ spin_lock(&pid->wait_pidfd.lock);
+ hlist_del_init_rcu(&ei->sibling_inodes);
+ spin_unlock(&pid->wait_pidfd.lock);
+ }
+
+ put_pid(pid);
+}
+
struct inode *proc_pid_make_inode(struct super_block * sb,
struct task_struct *task, umode_t mode)
{
struct inode * inode;
struct proc_inode *ei;
+ struct pid *pid;
/* We need a new inode */
@@ -1856,10 +1870,18 @@ struct inode *proc_pid_make_inode(struct super_block * sb,
/*
* grab the reference to task.
*/
- ei->pid = get_task_pid(task, PIDTYPE_PID);
- if (!ei->pid)
+ pid = get_task_pid(task, PIDTYPE_PID);
+ if (!pid)
goto out_unlock;
+ /* Let the pid remember us for quick removal */
+ ei->pid = pid;
+ if (S_ISDIR(mode)) {
+ spin_lock(&pid->wait_pidfd.lock);
+ hlist_add_head_rcu(&ei->sibling_inodes, &pid->inodes);
+ spin_unlock(&pid->wait_pidfd.lock);
+ }
+
task_dump_owner(task, 0, &inode->i_uid, &inode->i_gid);
security_task_to_inode(task, inode);
@@ -3230,90 +3252,29 @@ static const struct inode_operations proc_tgid_base_inode_operations = {
.permission = proc_pid_permission,
};
-static void proc_flush_task_mnt(struct vfsmount *mnt, pid_t pid, pid_t tgid)
-{
- struct dentry *dentry, *leader, *dir;
- char buf[10 + 1];
- struct qstr name;
-
- name.name = buf;
- name.len = snprintf(buf, sizeof(buf), "%u", pid);
- /* no ->d_hash() rejects on procfs */
- dentry = d_hash_and_lookup(mnt->mnt_root, &name);
- if (dentry) {
- d_invalidate(dentry);
- dput(dentry);
- }
-
- if (pid == tgid)
- return;
-
- name.name = buf;
- name.len = snprintf(buf, sizeof(buf), "%u", tgid);
- leader = d_hash_and_lookup(mnt->mnt_root, &name);
- if (!leader)
- goto out;
-
- name.name = "task";
- name.len = strlen(name.name);
- dir = d_hash_and_lookup(leader, &name);
- if (!dir)
- goto out_put_leader;
-
- name.name = buf;
- name.len = snprintf(buf, sizeof(buf), "%u", pid);
- dentry = d_hash_and_lookup(dir, &name);
- if (dentry) {
- d_invalidate(dentry);
- dput(dentry);
- }
-
- dput(dir);
-out_put_leader:
- dput(leader);
-out:
- return;
-}
-
/**
- * proc_flush_task - Remove dcache entries for @task from the /proc dcache.
- * @task: task that should be flushed.
+ * proc_flush_pid - Remove dcache entries for @pid from the /proc dcache.
+ * @pid: pid that should be flushed.
*
- * When flushing dentries from proc, one needs to flush them from global
- * proc (proc_mnt) and from all the namespaces' procs this task was seen
- * in. This call is supposed to do all of this job.
- *
- * Looks in the dcache for
- * /proc/@pid
- * /proc/@tgid/task/@pid
- * if either directory is present flushes it and all of it'ts children
- * from the dcache.
+ * This function walks a list of inodes (that belong to any proc
+ * filesystem) that are attached to the pid and flushes them from
+ * the dentry cache.
*
* It is safe and reasonable to cache /proc entries for a task until
* that task exits. After that they just clog up the dcache with
* useless entries, possibly causing useful dcache entries to be
- * flushed instead. This routine is proved to flush those useless
- * dcache entries at process exit time.
+ * flushed instead. This routine is provided to flush those useless
+ * dcache entries when a process is reaped.
*
* NOTE: This routine is just an optimization so it does not guarantee
- * that no dcache entries will exist at process exit time it
- * just makes it very unlikely that any will persist.
+ * that no dcache entries will exist after a process is reaped
+ * it just makes it very unlikely that any will persist.
*/
-void proc_flush_task(struct task_struct *task)
+void proc_flush_pid(struct pid *pid)
{
- int i;
- struct pid *pid, *tgid;
- struct upid *upid;
-
- pid = task_pid(task);
- tgid = task_tgid(task);
-
- for (i = 0; i <= pid->level; i++) {
- upid = &pid->numbers[i];
- proc_flush_task_mnt(upid->ns->proc_mnt, upid->nr,
- tgid->numbers[i].nr);
- }
+ proc_invalidate_siblings_dcache(&pid->inodes, &pid->wait_pidfd.lock);
+ put_pid(pid);
}
static struct dentry *proc_pid_instantiate(struct dentry * dentry,
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index d9243b24554a..1e730ea1dcd6 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -40,7 +40,7 @@ static void proc_evict_inode(struct inode *inode)
/* Stop tracking associated processes */
if (ei->pid) {
- put_pid(ei->pid);
+ proc_pid_evict_inode(ei);
ei->pid = NULL;
}
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index fd470172675f..9e294f0290e5 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -158,6 +158,7 @@ extern int proc_pid_statm(struct seq_file *, struct pid_namespace *,
extern const struct dentry_operations pid_dentry_operations;
extern int pid_getattr(const struct path *, struct kstat *, u32, unsigned int);
extern int proc_setattr(struct dentry *, struct iattr *);
+extern void proc_pid_evict_inode(struct proc_inode *);
extern struct inode *proc_pid_make_inode(struct super_block *, struct task_struct *, umode_t);
extern void pid_update_inode(struct task_struct *, struct inode *);
extern int pid_delete_dentry(const struct dentry *);
diff --git a/include/linux/pid.h b/include/linux/pid.h
index 998ae7d24450..01a0d4e28506 100644
--- a/include/linux/pid.h
+++ b/include/linux/pid.h
@@ -62,6 +62,7 @@ struct pid
unsigned int level;
/* lists of tasks that use this pid */
struct hlist_head tasks[PIDTYPE_MAX];
+ struct hlist_head inodes;
/* wait queue for pidfd notifications */
wait_queue_head_t wait_pidfd;
struct rcu_head rcu;
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
index 3dfa92633af3..40a7982b7285 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -32,7 +32,7 @@ struct proc_ops {
typedef int (*proc_write_t)(struct file *, char *, size_t);
extern void proc_root_init(void);
-extern void proc_flush_task(struct task_struct *);
+extern void proc_flush_pid(struct pid *);
extern struct proc_dir_entry *proc_symlink(const char *,
struct proc_dir_entry *, const char *);
@@ -105,7 +105,7 @@ static inline void proc_root_init(void)
{
}
-static inline void proc_flush_task(struct task_struct *task)
+static inline void proc_flush_pid(struct pid *pid)
{
}
diff --git a/kernel/exit.c b/kernel/exit.c
index 2833ffb0c211..502b4995b688 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -191,6 +191,7 @@ void put_task_struct_rcu_user(struct task_struct *task)
void release_task(struct task_struct *p)
{
struct task_struct *leader;
+ struct pid *thread_pid;
int zap_leader;
repeat:
/* don't need to get the RCU readlock here - the process is dead and
@@ -199,11 +200,11 @@ void release_task(struct task_struct *p)
atomic_dec(&__task_cred(p)->user->processes);
rcu_read_unlock();
- proc_flush_task(p);
cgroup_release(p);
write_lock_irq(&tasklist_lock);
ptrace_release_task(p);
+ thread_pid = get_pid(p->thread_pid);
__exit_signal(p);
/*
@@ -226,6 +227,7 @@ void release_task(struct task_struct *p)
}
write_unlock_irq(&tasklist_lock);
+ proc_flush_pid(thread_pid);
release_thread(p);
put_task_struct_rcu_user(p);
diff --git a/kernel/pid.c b/kernel/pid.c
index 0f4ecb57214c..ca08d6a3aa77 100644
--- a/kernel/pid.c
+++ b/kernel/pid.c
@@ -258,6 +258,7 @@ struct pid *alloc_pid(struct pid_namespace *ns, pid_t *set_tid,
INIT_HLIST_HEAD(&pid->tasks[type]);
init_waitqueue_head(&pid->wait_pidfd);
+ INIT_HLIST_HEAD(&pid->inodes);
upid = pid->numbers + ns->level;
spin_lock_irq(&pidmap_lock);
--
2.25.0
^ permalink raw reply related
* Re: [PATCH bpf-next v4 3/8] bpf: lsm: provide attachment points for BPF LSM programs
From: Casey Schaufler @ 2020-02-24 16:32 UTC (permalink / raw)
To: Alexei Starovoitov, Kees Cook
Cc: KP Singh, LKML, Linux Security Module list, Alexei Starovoitov,
James Morris, bpf, netdev, Casey Schaufler
In-Reply-To: <20200223220833.wdhonzvven7payaw@ast-mbp>
On 2/23/2020 2:08 PM, Alexei Starovoitov wrote:
> On Fri, Feb 21, 2020 at 08:22:59PM -0800, Kees Cook wrote:
>> If I'm understanding this correctly, there are two issues:
>>
>> 1- BPF needs to be run last due to fexit trampolines (?)
> no.
> The placement of nop call can be anywhere.
> BPF trampoline is automagically converting nop call into a sequence
> of directly invoked BPF programs.
> No link list traversals and no indirect calls in run-time.
Then why the insistence that it be last?
>> 2- BPF hooks don't know what may be attached at any given time, so
>> ALL LSM hooks need to be universally hooked. THIS turns out to create
>> a measurable performance problem in that the cost of the indirect call
>> on the (mostly/usually) empty BPF policy is too high.
> also no.
Um, then why not use the infrastructure as is?
>> So, trying to avoid the indirect calls is, as you say, an optimization,
>> but it might be a needed one due to the other limitations.
> I'm convinced that avoiding the cost of retpoline in critical path is a
> requirement for any new infrastructure in the kernel.
Sorry, I haven't gotten that memo.
> Not only for security, but for any new infra.
The LSM infrastructure ain't new.
> Networking stack converted all such places to conditional calls.
> In BPF land we converted indirect calls to direct jumps and direct calls.
> It took two years to do so. Adding new indirect calls is not an option.
> I'm eagerly waiting for Peter's static_call patches to land to convert
> a lot more indirect calls. May be existing LSMs will take advantage
> of static_call patches too, but static_call is not an option for BPF.
> That's why we introduced BPF trampoline in the last kernel release.
Sorry, but I don't see how BPF is so overwhelmingly special.
>> b) Would there actually be a global benefit to using the static keys
>> optimization for other LSMs?
> Yes. Just compiling with CONFIG_SECURITY adds "if (hlist_empty)" check
> for every hook.
Err, no, it doesn't. It does an hlish_for_each_entry(), which
may be the equivalent on an empty list, but let's not go around
spreading misinformation.
> Some of those hooks are in critical path. This load+cmp
> can be avoided with static_key optimization. I think it's worth doing.
I admit to being unfamiliar with the static_key implementation,
but if it would work for a list of hooks rather than a singe hook,
I'm all ears.
>> If static keys are justified for KRSI
> I really like that KRSI costs absolutely zero when it's not enabled.
And I dislike that there's security module specific code in security.c,
security.h and/or lsm_hooks.h. KRSI *is not that special*.
> Attaching BPF prog to one hook preserves zero cost for all other hooks.
> And when one hook is BPF powered it's using direct call instead of
> super expensive retpoline.
I'm not objecting to the good it does for KRSI.
I am *strongly* objecting to special casing KRSI.
> Overall this patch set looks good to me. There was a minor issue with prog
> accounting. I expect only that bit to be fixed in v5.
^ permalink raw reply
* [RFC PATCH v14 01/10] landlock: Add object and rule management
From: Mickaël Salaün @ 2020-02-24 16:02 UTC (permalink / raw)
To: linux-kernel
Cc: Mickaël Salaün, Al Viro, Andy Lutomirski, Arnd Bergmann,
Casey Schaufler, Greg Kroah-Hartman, James Morris, Jann Horn,
Jonathan Corbet, Kees Cook, Michael Kerrisk,
Mickaël Salaün, Serge E . Hallyn, Shuah Khan,
Vincent Dagonneau, kernel-hardening, linux-api, linux-arch,
linux-doc, linux-fsdevel, linux-kselftest, linux-security-module,
x86
In-Reply-To: <20200224160215.4136-1-mic@digikod.net>
A Landlock object enables to identify a kernel object (e.g. an inode).
A Landlock rule is a set of access rights allowed on an object. Rules
are grouped in rulesets that may be tied to a set of processes (i.e.
subjects) to enforce a scoped access-control (i.e. a domain).
Because Landlock's goal is to empower any process (especially
unprivileged ones) to sandbox themselves, we can't rely on a system-wide
object identification such as file extended attributes. Indeed, we need
innocuous, composable and modular access-controls.
The main challenge with this constraints is to identify kernel objects
while this identification is useful (i.e. when a security policy makes
use of this object). But this identification data should be freed once
no policy is using it. This ephemeral tagging should not and may not be
written in the filesystem. We then need to manage the lifetime of a
rule according to the lifetime of its object. To avoid a global lock,
this implementation make use of RCU and counters to safely reference
objects.
A following commit uses this generic object management for inodes.
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: James Morris <jmorris@namei.org>
Cc: Kees Cook <keescook@chromium.org>
Cc: Serge E. Hallyn <serge@hallyn.com>
---
Changes since v13:
* New dedicated implementation, removing the need for eBPF.
Previous version:
https://lore.kernel.org/lkml/20190721213116.23476-6-mic@digikod.net/
---
MAINTAINERS | 10 ++
security/Kconfig | 1 +
security/Makefile | 2 +
security/landlock/Kconfig | 15 ++
security/landlock/Makefile | 3 +
security/landlock/object.c | 339 +++++++++++++++++++++++++++++++++++++
security/landlock/object.h | 134 +++++++++++++++
7 files changed, 504 insertions(+)
create mode 100644 security/landlock/Kconfig
create mode 100644 security/landlock/Makefile
create mode 100644 security/landlock/object.c
create mode 100644 security/landlock/object.h
diff --git a/MAINTAINERS b/MAINTAINERS
index fcd79fc38928..206f85768cd9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9360,6 +9360,16 @@ F: net/core/skmsg.c
F: net/core/sock_map.c
F: net/ipv4/tcp_bpf.c
+LANDLOCK SECURITY MODULE
+M: Mickaël Salaün <mic@digikod.net>
+L: linux-security-module@vger.kernel.org
+W: https://landlock.io
+T: git https://github.com/landlock-lsm/linux.git
+S: Supported
+F: security/landlock/
+K: landlock
+K: LANDLOCK
+
LANTIQ / INTEL Ethernet drivers
M: Hauke Mehrtens <hauke@hauke-m.de>
L: netdev@vger.kernel.org
diff --git a/security/Kconfig b/security/Kconfig
index 2a1a2d396228..9d9981394fb0 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -238,6 +238,7 @@ source "security/loadpin/Kconfig"
source "security/yama/Kconfig"
source "security/safesetid/Kconfig"
source "security/lockdown/Kconfig"
+source "security/landlock/Kconfig"
source "security/integrity/Kconfig"
diff --git a/security/Makefile b/security/Makefile
index 746438499029..2472ef96d40a 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -12,6 +12,7 @@ subdir-$(CONFIG_SECURITY_YAMA) += yama
subdir-$(CONFIG_SECURITY_LOADPIN) += loadpin
subdir-$(CONFIG_SECURITY_SAFESETID) += safesetid
subdir-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown
+subdir-$(CONFIG_SECURITY_LANDLOCK) += landlock
# always enable default capabilities
obj-y += commoncap.o
@@ -29,6 +30,7 @@ obj-$(CONFIG_SECURITY_YAMA) += yama/
obj-$(CONFIG_SECURITY_LOADPIN) += loadpin/
obj-$(CONFIG_SECURITY_SAFESETID) += safesetid/
obj-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown/
+obj-$(CONFIG_SECURITY_LANDLOCK) += landlock/
obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o
# Object integrity file lists
diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig
new file mode 100644
index 000000000000..4a321d5b3f67
--- /dev/null
+++ b/security/landlock/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config SECURITY_LANDLOCK
+ bool "Landlock support"
+ depends on SECURITY
+ default n
+ help
+ This selects Landlock, a safe sandboxing mechanism. It enables to
+ restrict processes on the fly (i.e. enforce an access control policy),
+ which can complement seccomp-bpf. The security policy is a set of access
+ rights tied to an object, which could be a file, a socket or a process.
+
+ See Documentation/security/landlock/ for further information.
+
+ If you are unsure how to answer this question, answer N.
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
new file mode 100644
index 000000000000..cb6deefbf4c0
--- /dev/null
+++ b/security/landlock/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
+
+landlock-y := object.o
diff --git a/security/landlock/object.c b/security/landlock/object.c
new file mode 100644
index 000000000000..38fbbb108120
--- /dev/null
+++ b/security/landlock/object.c
@@ -0,0 +1,339 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Object and rule management
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ *
+ * Principles and constraints of the object and rule management:
+ * - Do not leak memory.
+ * - Try as much as possible to free a memory allocation as soon as it is
+ * unused.
+ * - Do not use global lock.
+ * - Do not charge processes other than the one requesting a Landlock
+ * operation.
+ */
+
+#include <linux/bug.h>
+#include <linux/compiler.h>
+#include <linux/compiler_types.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/rbtree.h>
+#include <linux/rcupdate.h>
+#include <linux/refcount.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+
+#include "object.h"
+
+struct landlock_object *landlock_create_object(
+ const enum landlock_object_type type, void *underlying_object)
+{
+ struct landlock_object *object;
+
+ if (WARN_ON_ONCE(!underlying_object))
+ return NULL;
+ object = kzalloc(sizeof(*object), GFP_KERNEL);
+ if (!object)
+ return NULL;
+ refcount_set(&object->usage, 1);
+ refcount_set(&object->cleaners, 1);
+ spin_lock_init(&object->lock);
+ INIT_LIST_HEAD(&object->rules);
+ object->type = type;
+ WRITE_ONCE(object->underlying_object, underlying_object);
+ return object;
+}
+
+struct landlock_object *landlock_get_object(struct landlock_object *object)
+ __acquires(object->usage)
+{
+ __acquire(object->usage);
+ /*
+ * If @object->usage equal 0, then it will be ignored by writers, and
+ * underlying_object->object may be replaced, but this is not an issue
+ * for release_object().
+ */
+ if (object && refcount_inc_not_zero(&object->usage)) {
+ /*
+ * It should not be possible to get a reference to an object if
+ * its underlying object is being terminated (e.g. with
+ * landlock_release_object()), because an object is only
+ * modifiable through such underlying object. This is not the
+ * case with landlock_get_object_cleaner().
+ */
+ WARN_ON_ONCE(!READ_ONCE(object->underlying_object));
+ return object;
+ }
+ return NULL;
+}
+
+static struct landlock_object *get_object_cleaner(
+ struct landlock_object *object)
+ __acquires(object->cleaners)
+{
+ __acquire(object->cleaners);
+ if (object && refcount_inc_not_zero(&object->cleaners))
+ return object;
+ return NULL;
+}
+
+/*
+ * There is two cases when an object should be free and the reference to the
+ * underlying object should be put:
+ * - when the last rule tied to this object is removed, which is handled by
+ * landlock_put_rule() and then release_object();
+ * - when the object is being terminated (e.g. no more reference to an inode),
+ * which is handled by landlock_put_object().
+ */
+static void put_object_free(struct landlock_object *object)
+ __releases(object->cleaners)
+{
+ __release(object->cleaners);
+ if (!refcount_dec_and_test(&object->cleaners))
+ return;
+ WARN_ON_ONCE(refcount_read(&object->usage));
+ /*
+ * Ensures a safe use of @object in the RCU block from
+ * landlock_put_rule().
+ */
+ kfree_rcu(object, rcu_free);
+}
+
+/*
+ * Destroys a newly created and useless object.
+ */
+void landlock_drop_object(struct landlock_object *object)
+{
+ if (WARN_ON_ONCE(!refcount_dec_and_test(&object->usage)))
+ return;
+ __acquire(object->cleaners);
+ put_object_free(object);
+}
+
+/*
+ * Puts the underlying object (e.g. inode) if it is the first request to
+ * release @object, without calling landlock_put_object().
+ *
+ * Return true if this call effectively marks @object as released, false
+ * otherwise.
+ */
+static bool release_object(struct landlock_object *object)
+ __releases(&object->lock)
+{
+ void *underlying_object;
+
+ lockdep_assert_held(&object->lock);
+
+ underlying_object = xchg(&object->underlying_object, NULL);
+ spin_unlock(&object->lock);
+ might_sleep();
+ if (!underlying_object)
+ return false;
+
+ switch (object->type) {
+ case LANDLOCK_OBJECT_INODE:
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ }
+ return true;
+}
+
+static void put_object_cleaner(struct landlock_object *object)
+ __releases(object->cleaners)
+{
+ /* Let's try an early lockless check. */
+ if (list_empty(&object->rules) &&
+ READ_ONCE(object->underlying_object)) {
+ /*
+ * Puts @object if there is no rule tied to it and the
+ * remaining user is the underlying object. This check is
+ * atomic because @object->rules and @object->underlying_object
+ * are protected by @object->lock.
+ */
+ spin_lock(&object->lock);
+ if (list_empty(&object->rules) &&
+ READ_ONCE(object->underlying_object) &&
+ refcount_dec_if_one(&object->usage)) {
+ /*
+ * Releases @object, in place of
+ * landlock_release_object().
+ *
+ * @object is already empty, implying that all its
+ * previous rules are already disabled.
+ *
+ * Unbalance the @object->cleaners counter to reflect
+ * the underlying object release.
+ */
+ if (!WARN_ON_ONCE(!release_object(object))) {
+ __acquire(object->cleaners);
+ put_object_free(object);
+ }
+ } else {
+ spin_unlock(&object->lock);
+ }
+ }
+ put_object_free(object);
+}
+
+/*
+ * Putting an object is easy when the object is being terminated, but it is
+ * much more tricky when the reason is that there is no more rule tied to this
+ * object. Indeed, new rules could be added at the same time.
+ */
+void landlock_put_object(struct landlock_object *object)
+ __releases(object->usage)
+{
+ struct landlock_object *object_cleaner;
+
+ __release(object->usage);
+ might_sleep();
+ if (!object)
+ return;
+ /*
+ * Guards against concurrent termination to be able to terminate
+ * @object if it is empty and not referenced by another rule-appender
+ * other than the underlying object.
+ */
+ object_cleaner = get_object_cleaner(object);
+ if (WARN_ON_ONCE(!object_cleaner)) {
+ __release(object->cleaners);
+ return;
+ }
+ /*
+ * Decrements @object->usage and if it reach zero, also decrement
+ * @object->cleaners. If both reach zero, then release and free
+ * @object.
+ */
+ if (refcount_dec_and_test(&object->usage)) {
+ struct landlock_rule *rule_walker, *rule_walker2;
+
+ spin_lock(&object->lock);
+ /*
+ * Disables all the rules tied to @object when it is forbidden
+ * to add new rule but still allowed to remove them with
+ * landlock_put_rule(). This is crucial to be able to safely
+ * free a rule according to landlock_rule_is_disabled().
+ */
+ list_for_each_entry_safe(rule_walker, rule_walker2,
+ &object->rules, list)
+ list_del_rcu(&rule_walker->list);
+
+ /*
+ * Releases @object if it is not already released (e.g. with
+ * landlock_release_object()).
+ */
+ release_object(object);
+ /*
+ * Unbalances the @object->cleaners counter to reflect the
+ * underlying object release.
+ */
+ __acquire(object->cleaners);
+ put_object_free(object);
+ }
+ put_object_cleaner(object_cleaner);
+}
+
+void landlock_put_rule(struct landlock_object *object,
+ struct landlock_rule *rule)
+{
+ if (!rule)
+ return;
+ WARN_ON_ONCE(!object);
+ /*
+ * Guards against a concurrent @object self-destruction with
+ * landlock_put_object() or put_object_cleaner().
+ */
+ rcu_read_lock();
+ if (landlock_rule_is_disabled(rule)) {
+ rcu_read_unlock();
+ if (refcount_dec_and_test(&rule->usage))
+ kfree_rcu(rule, rcu_free);
+ return;
+ }
+ if (refcount_dec_and_test(&rule->usage)) {
+ struct landlock_object *safe_object;
+
+ /*
+ * Now, @rule may still be enabled, or in the process of being
+ * untied to @object by put_object_cleaner(). However, we know
+ * that @object will not be freed until rcu_read_unlock() and
+ * until @object->cleaners reach zero. Furthermore, we may not
+ * be the only one willing to free a @rule linked with @object.
+ * If we succeed to hold @object with get_object_cleaner(), we
+ * know that until put_object_cleaner(), we can safely use
+ * @object to remove @rule.
+ */
+ safe_object = get_object_cleaner(object);
+ rcu_read_unlock();
+ if (!safe_object) {
+ __release(safe_object->cleaners);
+ /*
+ * We can safely free @rule because it is already
+ * removed from @object's list.
+ */
+ WARN_ON_ONCE(!landlock_rule_is_disabled(rule));
+ kfree_rcu(rule, rcu_free);
+ } else {
+ spin_lock(&safe_object->lock);
+ if (!landlock_rule_is_disabled(rule))
+ list_del(&rule->list);
+ spin_unlock(&safe_object->lock);
+ kfree_rcu(rule, rcu_free);
+ put_object_cleaner(safe_object);
+ }
+ } else {
+ rcu_read_unlock();
+ }
+ /*
+ * put_object_cleaner() might sleep, but it is only reachable if
+ * !landlock_rule_is_disabled(). Therefore, clean_ref() can not sleep.
+ */
+ might_sleep();
+}
+
+void landlock_release_object(struct landlock_object __rcu *rcu_object)
+{
+ struct landlock_object *object;
+
+ if (!rcu_object)
+ return;
+ rcu_read_lock();
+ object = get_object_cleaner(rcu_dereference(rcu_object));
+ rcu_read_unlock();
+ if (unlikely(!object)) {
+ __release(object->cleaners);
+ return;
+ }
+ /*
+ * Makes sure that the underlying object never point to a freed object
+ * by firstly releasing the object (i.e. NULL the reference to it) to
+ * be sure no one could get a new reference to it while it is being
+ * terminated. Secondly, put the object globally (e.g. for the
+ * super-block).
+ *
+ * This can run concurrently with put_object_cleaner(), which may try
+ * to release @object as well.
+ */
+ spin_lock(&object->lock);
+ if (release_object(object)) {
+ /*
+ * Unbalances the object to reflect the underlying object
+ * release.
+ */
+ __acquire(object->usage);
+ landlock_put_object(object);
+ }
+ /*
+ * If a concurrent thread is adding a new rule, the object will be free
+ * at the end of this rule addition, otherwise it will be free with the
+ * following put_object_cleaner() or a remaining one.
+ */
+ put_object_cleaner(object);
+}
diff --git a/security/landlock/object.h b/security/landlock/object.h
new file mode 100644
index 000000000000..15dfc9a75a82
--- /dev/null
+++ b/security/landlock/object.h
@@ -0,0 +1,134 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Object and rule management
+ *
+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
+ * Copyright © 2018-2020 ANSSI
+ */
+
+#ifndef _SECURITY_LANDLOCK_OBJECT_H
+#define _SECURITY_LANDLOCK_OBJECT_H
+
+#include <linux/compiler_types.h>
+#include <linux/list.h>
+#include <linux/poison.h>
+#include <linux/rcupdate.h>
+#include <linux/refcount.h>
+#include <linux/spinlock.h>
+
+struct landlock_access {
+ /*
+ * @self: Bitfield of allowed actions on the kernel object. They are
+ * relative to the object type (e.g. LANDLOCK_ACTION_FS_READ).
+ */
+ u32 self;
+ /*
+ * @beneath: Same as @self, but for the child objects (e.g. a file in a
+ * directory).
+ */
+ u32 beneath;
+};
+
+struct landlock_rule {
+ struct landlock_access access;
+ /*
+ * @list: Linked list with other rules tied to the same object, which
+ * enable to manage their lifetimes. This is also used to identify if
+ * a rule is still valid, thanks to landlock_rule_is_disabled(), which
+ * is important in the matching process because the original object
+ * address might have been recycled.
+ */
+ struct list_head list;
+ union {
+ /*
+ * @usage: Number of rulesets pointing to this rule. This
+ * field is never used by RCU readers.
+ */
+ refcount_t usage;
+ struct rcu_head rcu_free;
+ };
+};
+
+enum landlock_object_type {
+ LANDLOCK_OBJECT_INODE = 1,
+};
+
+struct landlock_object {
+ /*
+ * @usage: Main usage counter, used to tie an object to it's underlying
+ * object (i.e. create a lifetime) and potentially add new rules.
+ */
+ refcount_t usage;
+ /*
+ * @cleaners: Usage counter used to free a rule from @rules (thanks to
+ * put_rule()). Enables to get a reference to this object until it
+ * really become freed. Cf. put_object().
+ */
+ refcount_t cleaners;
+ union {
+ /*
+ * The use of this struct is controlled by @usage and
+ * @cleaners, which makes it safe to union it with @rcu_free.
+ */
+ struct {
+ /*
+ * @underlying_object: Used when cleaning up an object
+ * and to mark an object as tied to its underlying
+ * kernel structure. It must then be atomically read
+ * using READ_ONCE().
+ *
+ * The one who clear @underlying_object must:
+ * 1. clear the object self-reference and
+ * 2. decrement @usage (and potentially free the
+ * object).
+ *
+ * Cf. clean_object().
+ */
+ void *underlying_object;
+ /*
+ * @type: Only used when cleaning up an object.
+ */
+ enum landlock_object_type type;
+ spinlock_t lock;
+ /*
+ * @rules: List of struct landlock_rule linked with
+ * their "list" field. This list is only accessed when
+ * updating the list (to be able to clean up later)
+ * while holding @lock.
+ */
+ struct list_head rules;
+ };
+ struct rcu_head rcu_free;
+ };
+};
+
+void landlock_put_rule(struct landlock_object *object,
+ struct landlock_rule *rule);
+
+void landlock_release_object(struct landlock_object __rcu *rcu_object);
+
+struct landlock_object *landlock_create_object(
+ const enum landlock_object_type type, void *underlying_object);
+
+struct landlock_object *landlock_get_object(struct landlock_object *object)
+ __acquires(object->usage);
+
+void landlock_put_object(struct landlock_object *object)
+ __releases(object->usage);
+
+void landlock_drop_object(struct landlock_object *object);
+
+static inline bool landlock_rule_is_disabled(
+ struct landlock_rule *rule)
+{
+ /*
+ * Disabling (i.e. unlinking) a landlock_rule is a one-way operation.
+ * It is not possible to re-enable such a rule, then there is no need
+ * for smp_load_acquire().
+ *
+ * LIST_POISON2 is set by list_del() and list_del_rcu().
+ */
+ return !rule || READ_ONCE(rule->list.prev) == LIST_POISON2;
+}
+
+#endif /* _SECURITY_LANDLOCK_OBJECT_H */
--
2.25.0
^ permalink raw reply related
* Re: [PATCH bpf-next v4 3/8] bpf: lsm: provide attachment points for BPF LSM programs
From: KP Singh @ 2020-02-24 17:13 UTC (permalink / raw)
To: Casey Schaufler
Cc: Alexei Starovoitov, Kees Cook, LKML, Linux Security Module list,
Alexei Starovoitov, James Morris, bpf, netdev
In-Reply-To: <c5c67ece-e5c1-9e8f-3a2b-60d8d002c894@schaufler-ca.com>
On 24-Feb 08:32, Casey Schaufler wrote:
> On 2/23/2020 2:08 PM, Alexei Starovoitov wrote:
> > On Fri, Feb 21, 2020 at 08:22:59PM -0800, Kees Cook wrote:
> >> If I'm understanding this correctly, there are two issues:
> >>
> >> 1- BPF needs to be run last due to fexit trampolines (?)
> > no.
> > The placement of nop call can be anywhere.
> > BPF trampoline is automagically converting nop call into a sequence
> > of directly invoked BPF programs.
> > No link list traversals and no indirect calls in run-time.
>
> Then why the insistence that it be last?
I think this came out of the discussion about not being able to
override the other LSMs and introduce a new attack vector with some
arguments discussed at:
https://lore.kernel.org/bpf/20200109194302.GA85350@google.com/
Let's say we have SELinux + BPF runnng on the system. BPF should still
respect any decisions made by SELinux. This hasn't got anything to
do with the usage of fexit trampolines.
>
> >> 2- BPF hooks don't know what may be attached at any given time, so
> >> ALL LSM hooks need to be universally hooked. THIS turns out to create
> >> a measurable performance problem in that the cost of the indirect call
> >> on the (mostly/usually) empty BPF policy is too high.
> > also no.
>
> Um, then why not use the infrastructure as is?
I think what Kees meant is that BPF allows hooking to all the LSM
hooks and providing static LSM hook callbacks like traditional LSMs
has a measurable performance overhead and this is indeed correct. This
is why we want provide with the following characteristics:
- Without introducing a new attack surface, this was the reason for
creating a mutable security_hook_heads in v1 which ran the hook
after v1.
This approach still had the issues of an indirect call and an
extra check when not used. So this was not truly zero overhead even
after special casing BPF.
- Having a truly zero performance overhead on the system. There are
other tracing / attachment mechnaisms in the kernel which provide
similar guarrantees (using static keys and direct calls) and it
seems regressive for KRSI to not leverage these known patterns and
sacrifice performance espeically in hotpaths. This provides users
to use KRSI alongside other LSMs without paying extra cost for all
the possible hooks.
>
> >> So, trying to avoid the indirect calls is, as you say, an optimization,
> >> but it might be a needed one due to the other limitations.
> > I'm convinced that avoiding the cost of retpoline in critical path is a
> > requirement for any new infrastructure in the kernel.
>
> Sorry, I haven't gotten that memo.
>
> > Not only for security, but for any new infra.
>
> The LSM infrastructure ain't new.
But the ability to attach BPF programs to LSM hooks is new. Also, we
have not had the whole implementation of the LSM hook be mutable
before and this is essentially what the KRSI provides.
>
> > Networking stack converted all such places to conditional calls.
> > In BPF land we converted indirect calls to direct jumps and direct calls.
> > It took two years to do so. Adding new indirect calls is not an option.
> > I'm eagerly waiting for Peter's static_call patches to land to convert
> > a lot more indirect calls. May be existing LSMs will take advantage
> > of static_call patches too, but static_call is not an option for BPF.
> > That's why we introduced BPF trampoline in the last kernel release.
>
> Sorry, but I don't see how BPF is so overwhelmingly special.
My analogy here is that if every tracepoint in the kernel were of the
type:
if (trace_foo_enabled) // <-- Overhead here, solved with static key
trace_foo(a); // <-- retpoline overhead, solved with fexit trampolines
It would be very hard to justify enabling them on a production system,
and the same can be said for BPF and KRSI.
- KP
>
> >> b) Would there actually be a global benefit to using the static keys
> >> optimization for other LSMs?
> > Yes. Just compiling with CONFIG_SECURITY adds "if (hlist_empty)" check
> > for every hook.
>
> Err, no, it doesn't. It does an hlish_for_each_entry(), which
> may be the equivalent on an empty list, but let's not go around
> spreading misinformation.
>
> > Some of those hooks are in critical path. This load+cmp
> > can be avoided with static_key optimization. I think it's worth doing.
>
> I admit to being unfamiliar with the static_key implementation,
> but if it would work for a list of hooks rather than a singe hook,
> I'm all ears.
>
> >> If static keys are justified for KRSI
> > I really like that KRSI costs absolutely zero when it's not enabled.
>
> And I dislike that there's security module specific code in security.c,
> security.h and/or lsm_hooks.h. KRSI *is not that special*.
>
> > Attaching BPF prog to one hook preserves zero cost for all other hooks.
> > And when one hook is BPF powered it's using direct call instead of
> > super expensive retpoline.
>
> I'm not objecting to the good it does for KRSI.
> I am *strongly* objecting to special casing KRSI.
>
> > Overall this patch set looks good to me. There was a minor issue with prog
> > accounting. I expect only that bit to be fixed in v5.
>
^ permalink raw reply
* Re: [PATCH bpf-next v4 3/8] bpf: lsm: provide attachment points for BPF LSM programs
From: KP Singh @ 2020-02-24 17:23 UTC (permalink / raw)
To: Kees Cook
Cc: Casey Schaufler, LKML, Linux Security Module list,
Alexei Starovoitov, James Morris
In-Reply-To: <202002211946.A23A987@keescook>
Hi Kees,
Thanks for the feedback!
On 21-Feb 20:22, Kees Cook wrote:
> On Thu, Feb 20, 2020 at 03:49:05PM -0800, Casey Schaufler wrote:
> > On 2/20/2020 9:52 AM, KP Singh wrote:
> > > From: KP Singh <kpsingh@google.com>
> >
> > Sorry about the heavy list pruning - the original set
> > blows thunderbird up.
>
> (I've added some people back; I had to dig this thread back out of lkml
> since I didn't get a direct copy...)
>
> > > The BPF LSM programs are implemented as fexit trampolines to avoid the
> > > overhead of retpolines. These programs cannot be attached to security_*
> > > wrappers as there are quite a few security_* functions that do more than
> > > just calling the LSM callbacks.
> > >
> > > This was discussed on the lists in:
> > >
> > > https://lore.kernel.org/bpf/20200123152440.28956-1-kpsingh@chromium.org/T/#m068becce588a0cdf01913f368a97aea4c62d8266
> > >
> > > Adding a NOP callback after all the static LSM callbacks are called has
> > > the following benefits:
> > >
> > > - The BPF programs run at the right stage of the security_* wrappers.
> > > - They run after all the static LSM hooks allowed the operation,
> > > therefore cannot allow an action that was already denied.
> >
> > I still say that the special call-out to BPF is unnecessary.
> > I remain unconvinced by the arguments. You aren't doing anything
> > so special that the general mechanism won't work.
>
> If I'm understanding this correctly, there are two issues:
>
> 1- BPF needs to be run last due to fexit trampolines (?)
>
> 2- BPF hooks don't know what may be attached at any given time, so
> ALL LSM hooks need to be universally hooked. THIS turns out to create
> a measurable performance problem in that the cost of the indirect call
> on the (mostly/usually) empty BPF policy is too high.
>
> "1" can be solved a lot of ways, and doesn't seem to be a debated part
> of this series.
>
> "2" is interesting -- it creates a performance problem for EVERYONE that
> builds in this kernel feature, regardless of them using it. Excepting
> SELinux, "traditional" LSMs tends to be relatively sparse in their hooking:
>
> $ grep '^ struct hlist_head' include/linux/lsm_hooks.h | wc -l
> 230
> $ for i in apparmor loadpin lockdown safesetid selinux smack tomoyo yama ; \
> do echo -n "$i " && (cd $i && git grep LSM_HOOK_INIT | wc -l) ; done
> apparmor 68
> loadpin 3
> lockdown 1
> safesetid 2
> selinux 202
> smack 108
> tomoyo 28
> yama 4
>
> So, trying to avoid the indirect calls is, as you say, an optimization,
> but it might be a needed one due to the other limitations.
>
> To me, some questions present themselves:
>
> a) What, exactly, are the performance characteristics of:
> "before"
> "with indirect calls"
> "with static keys optimization"
Good suggestion!
I will do some analysis and come back with the numbers.
>
> b) Would there actually be a global benefit to using the static keys
> optimization for other LSMs? (Especially given that they're already
> sparsely populated and policy likely determines utility -- all the
> LSMs would just turn ON all their static keys or turn off ALL their
> static keys depending on having policy loaded.)
As Alexei mentioned, we can use the patches for static calls after
they are merged:
https://lore.kernel.org/lkml/8bc857824f82462a296a8a3c4913a11a7f801e74.1547073843.git.jpoimboe@redhat.com/
to make the framework better (as a separate series) especially given
that we are unsure how they work with BPF.
- KP
>
> If static keys are justified for KRSI (by "a") then it seems the approach
> here should stand. If "b" is also true, then we need an additional
> series to apply this optimization for the other LSMs (but that seems
> distinctly separate from THIS series).
>
> --
> Kees Cook
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox