Re: [PATCH bpf-next v3 04/10] bpf: lsm: Add mutable hooks list for the BPF LSM

From: Alexei Starovoitov
Date: Mon Feb 10 2020 - 22:12:19 EST


On Thu, Jan 23, 2020 at 07:24:34AM -0800, KP Singh wrote:
> +#define CALL_BPF_LSM_INT_HOOKS(FUNC, ...) ({ \
> + int _ret = 0; \
> + do { \
> + struct security_hook_list *P; \
> + int _idx; \
> + \
> + if (hlist_empty(&bpf_lsm_hook_heads.FUNC)) \
> + break; \
> + \
> + _idx = bpf_lsm_srcu_read_lock(); \
> + \
> + hlist_for_each_entry(P, \
> + &bpf_lsm_hook_heads.FUNC, list) { \
> + _ret = P->hook.FUNC(__VA_ARGS__); \
> + if (_ret && IS_ENABLED(CONFIG_SECURITY_BPF_ENFORCE)) \
> + break; \
> + } \
> + bpf_lsm_srcu_read_unlock(_idx); \
> + } while (0); \
> + IS_ENABLED(CONFIG_SECURITY_BPF_ENFORCE) ? _ret : 0; \
> +})

This extra CONFIG_SECURITY_BPF_ENFORCE doesn't make sense to me.
Why do all the work for bpf-lsm and ignore return code? Such framework already
exists. For audit only case the user could have kprobed security_*() in
security/security.c and had access to exactly the same data. There is no need
in any of these patches if audit the only use case.
Obviously bpf-lsm has to be capable of making go/no-go decision, so
my preference is to drop this extra kconfig knob.
I think the agreement seen in earlier comments in this thread that the prefered
choice is to always have bpf-based lsm to be equivalent to LSM_ORDER_LAST. In
such case how about using bpf-trampoline fexit logic instead?
Pros:
- no changes to security/ directory
- no changes to call_int_hook() macro
- patches 4, 5, 6 no longer necessary
- when security is off all security_*() hooks do single
if (hlist_empty(&bpf_lsm_hook_heads.FUNC)) check.
With patch 4 there will two such checks. Tiny perf penalty.
With fexit approach there will be no extra check.
- fexit approach is fast even on kernels compiled with retpoline, since
its using direct calls
Cons:
- bpf trampoline so far is x86 only and arm64 support is wip

By plugging into fexit I'm proposing to let bpf-lsm prog type modify return
value. Currently bpf-fexit prog type has read-only access to it. Adding write
access is a straightforward verifier change. The bpf progs from patch 9 will
still look exactly the same way:
SEC("lsm/file_mprotect")
int BPF_PROG(mprotect_audit, struct vm_area_struct *vma,
unsigned long reqprot, unsigned long prot) { ... }
The difference that libbpf will be finding btf_id of security_file_mprotect()
function and adding fexit trampoline to it instead of going via
security_list_options and its own lsm_hook_idx uapi. I think reusing existing
tracing facilities to attach and multiplex multiple programs is cleaner. More
code reuse. Unified testing of lsm and tracing, etc.