[PATCH bpf-next 3/4] security: Replace indirect LSM hook calls with static calls

KP Singh kpsingh at kernel.org
Fri Jan 20 02:13:37 UTC 2023


On Fri, Jan 20, 2023 at 2:43 AM Casey Schaufler <casey at schaufler-ca.com> wrote:
>
> On 1/19/2023 3:10 PM, KP Singh wrote:
> > LSM hooks are currently invoked from a linked list as indirect calls
> > which are invoked using retpolines as a mitigation for speculative
> > attacks (Branch History / Target injection)  and add extra overhead which
> > is especially bad in kernel hot paths:
> >
> > security_file_ioctl:
> >    0xffffffff814f0320 <+0>:   endbr64
> >    0xffffffff814f0324 <+4>:   push   %rbp
> >    0xffffffff814f0325 <+5>:   push   %r15
> >    0xffffffff814f0327 <+7>:   push   %r14
> >    0xffffffff814f0329 <+9>:   push   %rbx
> >    0xffffffff814f032a <+10>:  mov    %rdx,%rbx
> >    0xffffffff814f032d <+13>:  mov    %esi,%ebp
> >    0xffffffff814f032f <+15>:  mov    %rdi,%r14
> >    0xffffffff814f0332 <+18>:  mov    $0xffffffff834a7030,%r15
> >    0xffffffff814f0339 <+25>:  mov    (%r15),%r15
> >    0xffffffff814f033c <+28>:  test   %r15,%r15
> >    0xffffffff814f033f <+31>:  je     0xffffffff814f0358 <security_file_ioctl+56>
> >    0xffffffff814f0341 <+33>:  mov    0x18(%r15),%r11
> >    0xffffffff814f0345 <+37>:  mov    %r14,%rdi
> >    0xffffffff814f0348 <+40>:  mov    %ebp,%esi
> >    0xffffffff814f034a <+42>:  mov    %rbx,%rdx
> >
> >>>> 0xffffffff814f034d <+45>:  call   0xffffffff81f742e0 <__x86_indirect_thunk_array+352>
> >     Indirect calls that use retpolines leading to overhead, not just due
> >     to extra instruction but also branch misses.
> >
> >    0xffffffff814f0352 <+50>:  test   %eax,%eax
> >    0xffffffff814f0354 <+52>:  je     0xffffffff814f0339 <security_file_ioctl+25>
> >    0xffffffff814f0356 <+54>:  jmp    0xffffffff814f035a <security_file_ioctl+58>
> >    0xffffffff814f0358 <+56>:  xor    %eax,%eax
> >    0xffffffff814f035a <+58>:  pop    %rbx
> >    0xffffffff814f035b <+59>:  pop    %r14
> >    0xffffffff814f035d <+61>:  pop    %r15
> >    0xffffffff814f035f <+63>:  pop    %rbp
> >    0xffffffff814f0360 <+64>:  jmp    0xffffffff81f747c4 <__x86_return_thunk>
> >
> > The indirect calls are not really needed as one knows the addresses of
> > enabled LSM callbacks at boot time and only the order can possibly
> > change at boot time with the lsm= kernel command line parameter.
> >
> > An array of static calls is defined per LSM hook and the static calls
> > are updated at boot time once the order has been determined.
> >
> > A static key guards whether an LSM static call is enabled or not,
> > without this static key, for LSM hooks that return an int, the presence
> > of the hook that returns a default value can create side-effects which
> > has resulted in bugs [1].
> >
> > With the hook now exposed as a static call, one can see that the
> > retpolines are no longer there and the LSM callbacks are invoked
> > directly:
> >
> > security_file_ioctl:
> >    0xffffffff814f0dd0 <+0>:   endbr64
> >    0xffffffff814f0dd4 <+4>:   push   %rbp
> >    0xffffffff814f0dd5 <+5>:   push   %r14
> >    0xffffffff814f0dd7 <+7>:   push   %rbx
> >    0xffffffff814f0dd8 <+8>:   mov    %rdx,%rbx
> >    0xffffffff814f0ddb <+11>:  mov    %esi,%ebp
> >    0xffffffff814f0ddd <+13>:  mov    %rdi,%r14
> >
> >>>> 0xffffffff814f0de0 <+16>:  jmp    0xffffffff814f0df1 <security_file_ioctl+33>
> >     Static key enabled for selinux_file_ioctl
> >
> >>>> 0xffffffff814f0de2 <+18>:  jmp    0xffffffff814f0e08 <security_file_ioctl+56>
> >    Static key enabled for bpf_lsm_file_ioctl. This is something that is
> >    changed to default to false to avoid the existing side effect issues
> >    of BPF LSM [1]
> >
> >    0xffffffff814f0de4 <+20>:  xor    %eax,%eax
> >    0xffffffff814f0de6 <+22>:  xchg   %ax,%ax
> >    0xffffffff814f0de8 <+24>:  pop    %rbx
> >    0xffffffff814f0de9 <+25>:  pop    %r14
> >    0xffffffff814f0deb <+27>:  pop    %rbp
> >    0xffffffff814f0dec <+28>:  jmp    0xffffffff81f767c4 <__x86_return_thunk>
> >    0xffffffff814f0df1 <+33>:  endbr64
> >    0xffffffff814f0df5 <+37>:  mov    %r14,%rdi
> >    0xffffffff814f0df8 <+40>:  mov    %ebp,%esi
> >    0xffffffff814f0dfa <+42>:  mov    %rbx,%rdx
> >
> >>>>   0xffffffff814f0dfd <+45>:        call   0xffffffff814fe820 <selinux_file_ioctl>
> >    Direct call to SELinux.
> >
> >    0xffffffff814f0e02 <+50>:  test   %eax,%eax
> >    0xffffffff814f0e04 <+52>:  jne    0xffffffff814f0de8 <security_file_ioctl+24>
> >    0xffffffff814f0e06 <+54>:  jmp    0xffffffff814f0de2 <security_file_ioctl+18>
> >    0xffffffff814f0e08 <+56>:  endbr64
> >    0xffffffff814f0e0c <+60>:  mov    %r14,%rdi
> >    0xffffffff814f0e0f <+63>:  mov    %ebp,%esi
> >    0xffffffff814f0e11 <+65>:  mov    %rbx,%rdx
> >
> >>>>  0xffffffff814f0e14 <+68>: call   0xffffffff8123b7d0 <bpf_lsm_file_ioctl>
> >    Direct call to bpf_lsm_file_ioctl
> >
> >    0xffffffff814f0e19 <+73>:  test   %eax,%eax
> >    0xffffffff814f0e1b <+75>:  jne    0xffffffff814f0de8 <security_file_ioctl+24>
> >    0xffffffff814f0e1d <+77>:  jmp    0xffffffff814f0de4 <security_file_ioctl+20>
> >    0xffffffff814f0e1f <+79>:  endbr64
> >    0xffffffff814f0e23 <+83>:  mov    %r14,%rdi
> >    0xffffffff814f0e26 <+86>:  mov    %ebp,%esi
> >    0xffffffff814f0e28 <+88>:  mov    %rbx,%rdx
> >    0xffffffff814f0e2b <+91>:  pop    %rbx
> >    0xffffffff814f0e2c <+92>:  pop    %r14
> >    0xffffffff814f0e2e <+94>:  pop    %rbp
> >    0xffffffff814f0e2f <+95>:  ret
> >    0xffffffff814f0e30 <+96>:  int3
> >    0xffffffff814f0e31 <+97>:  int3
> >    0xffffffff814f0e32 <+98>:  int3
> >    0xffffffff814f0e33 <+99>:  int3
> >
> > There are some hooks that don't use the call_int_hook and
> > call_void_hook. These hooks are updated to use a new macro called
> > security_for_each_hook
>
> There has got to be a simpler way to do this. Putting all the code
> for an extraordinary hook into a macro scares the bedickens out of me.
> The call_{int,void}_hook macros work fine for the simple cases, but
> that's because they are the simple cases. What would the hooks that use
> your new macro look like if you coded them directly?

It cannot be coded directly, the number of possible LSMs is determined
at compile time using CONFIG_* macros, so directly coding the slots
would become even more cumbersome and error prone (e.g. missing out
stuff when a new hook is added etc) and updating the static calls at
boot time without a compile time enforced macro would make the logic
even more complex.

Also note, what I dumped here is assembly at runtime, this looks very
different at compile time: Here's the compile time assembly:

ecurity_file_ioctl:
   0xffffffff814f0e90 <+0>: endbr64
   0xffffffff814f0e94 <+4>: push   %rbp
   0xffffffff814f0e95 <+5>: push   %r14
   0xffffffff814f0e97 <+7>: push   %rbx
   0xffffffff814f0e98 <+8>: mov    %rdx,%rbx
   0xffffffff814f0e9b <+11>: mov    %esi,%ebp
   0xffffffff814f0e9d <+13>: mov    %rdi,%r14
   0xffffffff814f0ea0 <+16>: xchg   %ax,%ax
   0xffffffff814f0ea2 <+18>: xchg   %ax,%ax
   0xffffffff814f0ea4 <+20>: xor    %eax,%eax
   0xffffffff814f0ea6 <+22>: xchg   %ax,%ax
   0xffffffff814f0ea8 <+24>: pop    %rbx
   0xffffffff814f0ea9 <+25>: pop    %r14
   0xffffffff814f0eab <+27>: pop    %rbp
   0xffffffff814f0eac <+28>: jmp    0xffffffff81f767c4 <__x86_return_thunk>
   0xffffffff814f0eb1 <+33>: endbr64
   0xffffffff814f0eb5 <+37>: mov    %r14,%rdi
   0xffffffff814f0eb8 <+40>: mov    %ebp,%esi
   0xffffffff814f0eba <+42>: mov    %rbx,%rdx
   0xffffffff814f0ebd <+45>: call   0xffffffff81f784f0
<__SCT__lsm_static_call_file_ioctl_0>
   0xffffffff814f0ec2 <+50>: test   %eax,%eax
   0xffffffff814f0ec4 <+52>: jne    0xffffffff814f0ea8 <security_file_ioctl+24>
   0xffffffff814f0ec6 <+54>: jmp    0xffffffff814f0ea2 <security_file_ioctl+18>
   0xffffffff814f0ec8 <+56>: endbr64
   0xffffffff814f0ecc <+60>: mov    %r14,%rdi
   0xffffffff814f0ecf <+63>: mov    %ebp,%esi
   0xffffffff814f0ed1 <+65>: mov    %rbx,%rdx
   0xffffffff814f0ed4 <+68>: call   0xffffffff81f784f8
<__SCT__lsm_static_call_file_ioctl_1>
   0xffffffff814f0ed9 <+73>: test   %eax,%eax
   0xffffffff814f0edb <+75>: jne    0xffffffff814f0ea8 <security_file_ioctl+24>
   0xffffffff814f0edd <+77>: jmp    0xffffffff814f0ea4 <security_file_ioctl+20>
   0xffffffff814f0edf <+79>: endbr64
   0xffffffff814f0ee3 <+83>: mov    %r14,%rdi
   0xffffffff814f0ee6 <+86>: mov    %ebp,%esi
   0xffffffff814f0ee8 <+88>: mov    %rbx,%rdx
   0xffffffff814f0eeb <+91>: pop    %rbx
   0xffffffff814f0eec <+92>: pop    %r14
   0xffffffff814f0eee <+94>: pop    %rbp
   0xffffffff814f0eef <+95>: jmp    0xffffffff81f78500
<__SCT__lsm_static_call_file_ioctl_2>


>
> >  where the lsm_callback is directly invoked as an
> > indirect call. Currently, there are no performance sensitive hooks that
> > use the security_for_each_hook macro. However, if, some performance
> > sensitive hooks are discovered, these can be updated to use
> > static calls with loop unrolling as well using a custom macro.
>
> Or how about no macro at all? I would really like to see what the code
> would look like without this level of macro processing.
>

One can inline the static slot generation logic, but the code would
become quite complex and tricky to write without macros. Open for
suggestions.



More information about the Linux-security-module-archive mailing list