<-- Use http://vger.kernel.org/~acme/bpf/eBPF-lockdown-lpc-2020/?print-pdf#/ then control+P to print to pdf file -->

BPF lockdown


Using eBPF in kernel lockdown mode


Arnaldo Carvalho de Melo
acme@kernel.org
Red Hat Inc.



https://twitter.com/acmel

What is this about?



  • kernel lockdown
  • Cryptographic signature of eBPF bytecode
  • Limiting access to confidential information
  • libbpf code patching
  • Problem statement
  • No code written so far

kernel lockdown mode



  • Barrier between root and the kernel
  • Integrity mode
  • Confidentiality mode
  • Initial merge: v5.4 (August 2019)

Integrity mode



  • kernel and modules signed
  • eBPF bytecode signed
  • XDP offload
  • Host checks signature
  • Was it tested by a trusted party?

Confidentiality mode



  • Integrity mode
  • Plus restrictions to accessing memory

Examples

  • bpf_probe_read_kernel() and bpf_read_kernel_str()
  • Should be already in compliance
  • Restrictions to accessing memory
  • But not bpf_probe_read_kernel_user()?

static __always_inline int
bpf_probe_read_kernel_common(void *dst, u32 size, const void *unsafe_ptr)
{
        int ret = security_locked_down(LOCKDOWN_BPF_READ);
        if (unlikely(ret < 0))
                goto fail;
        ret = copy_from_kernel_nofault(dst, unsafe_ptr, size);
        if (unlikely(ret < 0))
                goto fail;
        return ret;
fail:	memset(dst, 0, size);
        return ret;
}
					

SELinux lockdown


Sample AVC audit output from denials:

avc:  denied  { integrity } for pid=3402 comm="fwupd"
 lockdown_reason="/dev/mem,kmem,port" scontext=system_u:system_r:fwupd_t:s0
 tcontext=system_u:system_r:fwupd_t:s0 tclass=lockdown permissive=0

avc:  denied  { confidentiality } for pid=4628 comm="cp"
 lockdown_reason="/proc/kcore access"
 scontext=unconfined_u:unconfined_r:test_lockdown_integrity_t:s0-s0:c0.c1023
 tcontext=unconfined_u:unconfined_r:test_lockdown_integrity_t:s0-s0:c0.c1023
 tclass=lockdown permissive=0
					

Lockdown check points


$ find . -name "*.c" | xargs grep security_locked_down  | wc -l
59
$ find . -name "*.c" | xargs grep security_locked_down  | head
./arch/x86/kernel/ioport.c:			security_locked_down(LOCKDOWN_IOPORT)))
./arch/x86/kernel/ioport.c:		    security_locked_down(LOCKDOWN_IOPORT))
./arch/x86/kernel/msr.c:	err = security_locked_down(LOCKDOWN_MSR);
./arch/x86/kernel/msr.c:		err = security_locked_down(LOCKDOWN_MSR);
./arch/x86/mm/testmmiotrace.c:	int ret = security_locked_down(LOCKDOWN_MMIOTRACE);
./arch/powerpc/xmon/xmon.c:		lockdown = !!security_locked_down(LOCKDOWN_XMON_RW);
./arch/powerpc/xmon/xmon.c:		xmon_is_ro = !!security_locked_down(LOCKDOWN_XMON_WR);
./fs/proc/kcore.c:	int ret = security_locked_down(LOCKDOWN_KCORE);
./fs/debugfs/file.c:	if (security_locked_down(LOCKDOWN_DEBUGFS))
./fs/debugfs/inode.c:	int ret = security_locked_down(LOCKDOWN_DEBUGFS);
$

lockdown reasons


enum lockdown_reason {
        LOCKDOWN_NONE,              LOCKDOWN_MODULE_SIGNATURE,
        LOCKDOWN_DEV_MEM,           LOCKDOWN_EFI_TEST,
        LOCKDOWN_KEXEC,             LOCKDOWN_HIBERNATION,
        LOCKDOWN_PCI_ACCESS,        LOCKDOWN_IOPORT,
        LOCKDOWN_MSR,               LOCKDOWN_ACPI_TABLES,
        LOCKDOWN_PCMCIA_CIS,        LOCKDOWN_TIOCSSERIAL,
        LOCKDOWN_MODULE_PARAMETERS, LOCKDOWN_MMIOTRACE,
        LOCKDOWN_DEBUGFS,           LOCKDOWN_XMON_WR,
        LOCKDOWN_INTEGRITY_MAX,
	LOCKDOWN_KCORE,             LOCKDOWN_KPROBES,
	LOCKDOWN_BPF_READ,          LOCKDOWN_PERF,
	LOCKDOWN_TRACEFS,           LOCKDOWN_XMON_RW,
	LOCKDOWN_CONFIDENTIALITY_MAX,
};
					

What other BPF helpers to limit?



  • When in doubt: restrict
  • Open as we understand use case

Signing BPF



  • Initially for pre-built bytecode
  • Like tools/bpf/runqslower/
  • bpftrace-like, dynamic, later
  • Reuse module signing utility
  • Add signature to bpf_attr
  • Reuse module verification in kernel

bpftool



  • New 'sign' command
  • Sign the ELF file
  • Sign each ELF section
  • Reuse scripts/sign-file.c
scripts/sign-file.c
$ ../build/v5.9-rc2+/scripts/sign-file 
Usage: scripts/sign-file [-dp] <hash algo> <key> <x509> <module> [<dest>]
       scripts/sign-file -s <raw sig> <hash algo> <x509> <module> [<dest>]
$
					

libbpf



  • Notices signature
  • Adds it to the PROG_LOAD bpf_attr

kernel



  • Notices signature
  • Checks it like with kernel modules
  • Hopefully shares the same signature verifier
module_sig_check()
static int module_sig_check(struct load_info *info, int flags)
{
	int err = info->len ? mod_verify_sig(info->hdr, info) : -ENODATA;
        switch (err) {
        case 0: info->sig_ok = true; return 0;
        case -ENODATA: reason = "Loading of unsigned module";		     goto decide;
        case -ENOPKG:  reason = "Loading of module with unsupported crypto"; goto decide;
        case -ENOKEY:  reason = "Loading of module with unavailable key";
        decide:
                if (is_module_sig_enforced()) {
                        pr_notice("%s: %s is rejected\n", info->name, reason);
			return -EKEYREJECTED;
                }
                return security_locked_down(LOCKDOWN_MODULE_SIGNATURE);
					

No problems?



  • No code patching
  • CO-RE not involved
  • Signature verified
  • Bytecode proceeds to the eBPF verifier
  • End of story

BZZT: New class of failure!



  • Tainted signature
  • Code patching, CO-RE
  • BTF adjustments to struct fields
  • enumerator fixups
  • Fallback to bpf_probe_read
  • bpf-to-bpf calls, dead code elimination
  • Others, more to come

Working around



  • Code patching highlighted
  • libbpf logs changes made
  • Rebuild + re-sign for that kernel?
  • To avoid CO-RE?

Not possible?



  • Move parts of libbpf to kernel
  • Code patching
  • After signature verification
  • User mode helper/driver?

User mode helper/driver



  • Like with bpfilter
  • Using a binary format
  • bpf.o executable
  • Running it triggers bytecode load
  • Signature verification
  • Code patching
  • Hand it off to the verifier

Questions?


Suggestions?


Solutions?

THE END