* [PATCH v12 0/5] Reduce overhead of LSMs with static calls
@ 2024-05-16 0:35 KP Singh
2024-05-16 0:35 ` [PATCH v12 1/5] kernel: Add helper macros for loop unrolling KP Singh
` (6 more replies)
0 siblings, 7 replies; 18+ messages in thread
From: KP Singh @ 2024-05-16 0:35 UTC (permalink / raw)
To: linux-security-module, bpf
Cc: ast, paul, casey, andrii, keescook, daniel, renauld, revest, song
# Background
LSM hooks (callbacks) are currently invoked as indirect function calls. These
callbacks are registered into a linked list at boot time as the order of the
LSMs can be configured on the kernel command line with the "lsm=" command line
parameter.
Indirect function calls have a high overhead due to retpoline mitigation for
various speculative execution attacks.
Retpolines remain relevant even with newer generation CPUs as recently
discovered speculative attacks, like Spectre BHB need Retpolines to mitigate
against branch history injection and still need to be used in combination with
newer mitigation features like eIBRS.
This overhead is especially significant for the "bpf" LSM which allows the user
to implement LSM functionality with eBPF program. In order to facilitate this
the "bpf" LSM provides a default callback for all LSM hooks. When enabled,
the "bpf" LSM incurs an unnecessary / avoidable indirect call. This is
especially bad in OS hot paths (e.g. in the networking stack).
This overhead prevents the adoption of bpf LSM on performance critical
systems, and also, in general, slows down all LSMs.
Since we know the address of the enabled LSM callbacks at compile time and only
the order is determined at boot time, the LSM framework can allocate static
calls for each of the possible LSM callbacks and these calls can be updated once
the order is determined at boot.
This series is a respin of the RFC proposed by Paul Renauld (renauld@google.com)
and Brendan Jackman (jackmanb@google.com) [1]
# Performance improvement
With this patch-set some syscalls with lots of LSM hooks in their path
benefitted at an average of ~3% and I/O and Pipe based system calls benefitting
the most.
Here are the results of the relevant Unixbench system benchmarks with BPF LSM
and SELinux enabled with default policies enabled with and without these
patches.
Benchmark Delta(%): (+ is better)
===============================================================================
Execl Throughput +1.9356
File Write 1024 bufsize 2000 maxblocks +6.5953
Pipe Throughput +9.5499
Pipe-based Context Switching +3.0209
Process Creation +2.3246
Shell Scripts (1 concurrent) +1.4975
System Call Overhead +2.7815
System Benchmarks Index Score (Partial Only): +3.4859
In the best case, some syscalls like eventfd_create benefitted to about ~10%.
The full analysis can be viewed at https://kpsingh.ch/lsm-perf
[1] https://lore.kernel.org/linux-security-module/20200820164753.3256899-1-jackmanb@chromium.org/
# BPF LSM Side effects
Patch 4 of the series also addresses the issues with the side effects of the
default value return values of the BPF LSM callbacks and also removes the
overheads associated with them making it deployable at hyperscale.
# v11 to v12
* Casey's feedback on readability of patch 4
* Tetsuo's catch on the bug in Patch 5
* Changed LSM_HOOK_TOGGLEABLE to LSM_HOOK_RUNTIME, the bikeshed is blue now.
* Also, as security_toggle_hook relies on iterating over a
flat lsm_static_calls_table, I added the __packed attribute.
# v10 to v11
* bpf_lsm_toggle_hook to security_toggle_hook with LSM_HOOK_TOGGLEABLE
limiting the hooks as the ones that can be toggled.
# v9 to v10
* Addressed Paul's comments for Patch 3. I did not remove the acks from this one
as changes were minor.
* Moved BPF LSM specific hook toggling logic bpf_lsm_toggle_hook to s
security_toggle_hook as a generic API. I removed the Ack's from this patch
as it's worth another look.
* Refactored the non-standard hooks to use static calls.
# v8 to v9
Paul, I removed the 5th patch about CONFIG_SECURITY_HOOK_LIKELY and went through
all the feedback. I believe it all should be addressed now.
But, please let me know if I missed anything.
The patches are based on https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm.git
(next branch as of 2024-02-07) and resolved a bunch of conflicts.
I also added Andrii's series ack to indidividual patches.
# v7 to v8
* Addressed Andrii's feedback
* Rebased (this seems to have removed the syscall changes). v7 has the required
conflict resolution incase the conflicts need to be resolved again.
# v6 -> v7
* Rebased with latest LSM id changes merged
NOTE: The warning shown by the kernel test bot is spurious, there is no flex array
and it seems to come from an older tool chain.
https://lore.kernel.org/bpf/202310111711.wLbijitj-lkp@intel.com/
# v5 -> v6
* Fix a bug in BPF LSM hook toggle logic.
# v4 -> v5
* Rebase to linux-next/master
* Fixed the case where MAX_LSM_COUNT comes to zero when just CONFIG_SECURITY
is compiled in without any other LSM enabled as reported here:
https://lore.kernel.org/bpf/202309271206.d7fb60f9-oliver.sang@intel.com
# v3 -> v4
* Refactor LSM count macros to use COUNT_ARGS
* Change CONFIG_SECURITY_HOOK_LIKELY likely's default value to be based on
the LSM enabled and have it depend on CONFIG_EXPERT. There are a lot of subtle
options behind CONFIG_EXPERT and this should, hopefully alleviate concerns
about yet another knob.
* __randomize_layout for struct lsm_static_call and, in addition to the cover
letter add performance numbers to 3rd patch and some minor commit message
updates.
* Rebase to linux-next.
# v2 -> v3
* Fixed a build issue on archs which don't have static calls and enable
CONFIG_SECURITY.
* Updated the LSM_COUNT macros based on Andrii's suggestions.
* Changed the security_ prefix to lsm_prefix based on Casey's suggestion.
* Inlined static_branch_maybe into lsm_for_each_hook on Kees' feedback.
# v1 -> v2 (based on linux-next, next-20230614)
* Incorporated suggestions from Kees
* Changed the way MAX_LSMs are counted from a binary based generator to a clever header.
* Add CONFIG_SECURITY_HOOK_LIKELY to configure the likelihood of LSM hooks.
KP Singh (5):
kernel: Add helper macros for loop unrolling
security: Count the LSMs enabled at compile time
security: Replace indirect LSM hook calls with static calls
security: Update non standard hooks to use static calls
bpf: Only enable BPF LSM hooks when an LSM program is attached
include/linux/args.h | 6 +-
include/linux/lsm_count.h | 128 ++++++++++++
include/linux/lsm_hooks.h | 100 +++++++++-
include/linux/unroll.h | 36 ++++
kernel/bpf/trampoline.c | 40 +++-
security/bpf/hooks.c | 2 +-
security/security.c | 403 ++++++++++++++++++++++++++------------
7 files changed, 568 insertions(+), 147 deletions(-)
create mode 100644 include/linux/lsm_count.h
create mode 100644 include/linux/unroll.h
--
2.45.0.rc1.225.g2a3ae87e7f-goog
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v12 1/5] kernel: Add helper macros for loop unrolling
2024-05-16 0:35 [PATCH v12 0/5] Reduce overhead of LSMs with static calls KP Singh
@ 2024-05-16 0:35 ` KP Singh
2024-05-17 8:03 ` John Johansen
2024-05-16 0:35 ` [PATCH v12 2/5] security: Count the LSMs enabled at compile time KP Singh
` (5 subsequent siblings)
6 siblings, 1 reply; 18+ messages in thread
From: KP Singh @ 2024-05-16 0:35 UTC (permalink / raw)
To: linux-security-module, bpf
Cc: ast, paul, casey, andrii, keescook, daniel, renauld, revest, song,
KP Singh
This helps in easily initializing blocks of code (e.g. static calls and
keys).
UNROLL(N, MACRO, __VA_ARGS__) calls MACRO N times with the first
argument as the index of the iteration. This allows string pasting to
create unique tokens for variable names, function calls etc.
As an example:
#include <linux/unroll.h>
#define MACRO(N, a, b) \
int add_##N(int a, int b) \
{ \
return a + b + N; \
}
UNROLL(2, MACRO, x, y)
expands to:
int add_0(int x, int y)
{
return x + y + 0;
}
int add_1(int x, int y)
{
return x + y + 1;
}
Reviewed-by: Kees Cook <keescook@chromium.org>
Reviewed-by: Casey Schaufler <casey@schaufler-ca.com>
Acked-by: Song Liu <song@kernel.org>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: KP Singh <kpsingh@kernel.org>
---
include/linux/unroll.h | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
create mode 100644 include/linux/unroll.h
diff --git a/include/linux/unroll.h b/include/linux/unroll.h
new file mode 100644
index 000000000000..d42fd6366373
--- /dev/null
+++ b/include/linux/unroll.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (C) 2023 Google LLC.
+ */
+
+#ifndef __UNROLL_H
+#define __UNROLL_H
+
+#include <linux/args.h>
+
+#define UNROLL(N, MACRO, args...) CONCATENATE(__UNROLL_, N)(MACRO, args)
+
+#define __UNROLL_0(MACRO, args...)
+#define __UNROLL_1(MACRO, args...) __UNROLL_0(MACRO, args) MACRO(0, args)
+#define __UNROLL_2(MACRO, args...) __UNROLL_1(MACRO, args) MACRO(1, args)
+#define __UNROLL_3(MACRO, args...) __UNROLL_2(MACRO, args) MACRO(2, args)
+#define __UNROLL_4(MACRO, args...) __UNROLL_3(MACRO, args) MACRO(3, args)
+#define __UNROLL_5(MACRO, args...) __UNROLL_4(MACRO, args) MACRO(4, args)
+#define __UNROLL_6(MACRO, args...) __UNROLL_5(MACRO, args) MACRO(5, args)
+#define __UNROLL_7(MACRO, args...) __UNROLL_6(MACRO, args) MACRO(6, args)
+#define __UNROLL_8(MACRO, args...) __UNROLL_7(MACRO, args) MACRO(7, args)
+#define __UNROLL_9(MACRO, args...) __UNROLL_8(MACRO, args) MACRO(8, args)
+#define __UNROLL_10(MACRO, args...) __UNROLL_9(MACRO, args) MACRO(9, args)
+#define __UNROLL_11(MACRO, args...) __UNROLL_10(MACRO, args) MACRO(10, args)
+#define __UNROLL_12(MACRO, args...) __UNROLL_11(MACRO, args) MACRO(11, args)
+#define __UNROLL_13(MACRO, args...) __UNROLL_12(MACRO, args) MACRO(12, args)
+#define __UNROLL_14(MACRO, args...) __UNROLL_13(MACRO, args) MACRO(13, args)
+#define __UNROLL_15(MACRO, args...) __UNROLL_14(MACRO, args) MACRO(14, args)
+#define __UNROLL_16(MACRO, args...) __UNROLL_15(MACRO, args) MACRO(15, args)
+#define __UNROLL_17(MACRO, args...) __UNROLL_16(MACRO, args) MACRO(16, args)
+#define __UNROLL_18(MACRO, args...) __UNROLL_17(MACRO, args) MACRO(17, args)
+#define __UNROLL_19(MACRO, args...) __UNROLL_18(MACRO, args) MACRO(18, args)
+#define __UNROLL_20(MACRO, args...) __UNROLL_19(MACRO, args) MACRO(19, args)
+
+#endif /* __UNROLL_H */
--
2.45.0.rc1.225.g2a3ae87e7f-goog
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v12 2/5] security: Count the LSMs enabled at compile time
2024-05-16 0:35 [PATCH v12 0/5] Reduce overhead of LSMs with static calls KP Singh
2024-05-16 0:35 ` [PATCH v12 1/5] kernel: Add helper macros for loop unrolling KP Singh
@ 2024-05-16 0:35 ` KP Singh
2024-05-17 8:09 ` John Johansen
2024-05-16 0:35 ` [PATCH v12 3/5] security: Replace indirect LSM hook calls with static calls KP Singh
` (4 subsequent siblings)
6 siblings, 1 reply; 18+ messages in thread
From: KP Singh @ 2024-05-16 0:35 UTC (permalink / raw)
To: linux-security-module, bpf
Cc: ast, paul, casey, andrii, keescook, daniel, renauld, revest, song,
Kui-Feng Lee, KP Singh
These macros are a clever trick to determine a count of the number of
LSMs that are enabled in the config to ascertain the maximum number of
static calls that need to be configured per LSM hook.
Without this one would need to generate static calls for the total
number of LSMs in the kernel (even if they are not compiled) times the
number of LSM hooks which ends up being quite wasteful.
Suggested-by: Kui-Feng Lee <sinquersw@gmail.com>
Suggested-by: Andrii Nakryiko <andrii@kernel.org>
Acked-by: Song Liu <song@kernel.org>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Reviewed-by: Kees Cook <keescook@chromium.org>
Reviewed-by: Casey Schaufler <casey@schaufler-ca.com>
Signed-off-by: KP Singh <kpsingh@kernel.org>
---
include/linux/args.h | 6 +-
include/linux/lsm_count.h | 128 ++++++++++++++++++++++++++++++++++++++
2 files changed, 131 insertions(+), 3 deletions(-)
create mode 100644 include/linux/lsm_count.h
diff --git a/include/linux/args.h b/include/linux/args.h
index 8ff60a54eb7d..2e8e65d975c7 100644
--- a/include/linux/args.h
+++ b/include/linux/args.h
@@ -17,9 +17,9 @@
* that as _n.
*/
-/* This counts to 12. Any more, it will return 13th argument. */
-#define __COUNT_ARGS(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _n, X...) _n
-#define COUNT_ARGS(X...) __COUNT_ARGS(, ##X, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
+/* This counts to 15. Any more, it will return 16th argument. */
+#define __COUNT_ARGS(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _n, X...) _n
+#define COUNT_ARGS(X...) __COUNT_ARGS(, ##X, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
/* Concatenate two parameters, but allow them to be expanded beforehand. */
#define __CONCAT(a, b) a ## b
diff --git a/include/linux/lsm_count.h b/include/linux/lsm_count.h
new file mode 100644
index 000000000000..73c7cc81349b
--- /dev/null
+++ b/include/linux/lsm_count.h
@@ -0,0 +1,128 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (C) 2023 Google LLC.
+ */
+
+#ifndef __LINUX_LSM_COUNT_H
+#define __LINUX_LSM_COUNT_H
+
+#include <linux/args.h>
+
+#ifdef CONFIG_SECURITY
+
+/*
+ * Macros to count the number of LSMs enabled in the kernel at compile time.
+ */
+
+/*
+ * Capabilities is enabled when CONFIG_SECURITY is enabled.
+ */
+#if IS_ENABLED(CONFIG_SECURITY)
+#define CAPABILITIES_ENABLED 1,
+#else
+#define CAPABILITIES_ENABLED
+#endif
+
+#if IS_ENABLED(CONFIG_SECURITY_SELINUX)
+#define SELINUX_ENABLED 1,
+#else
+#define SELINUX_ENABLED
+#endif
+
+#if IS_ENABLED(CONFIG_SECURITY_SMACK)
+#define SMACK_ENABLED 1,
+#else
+#define SMACK_ENABLED
+#endif
+
+#if IS_ENABLED(CONFIG_SECURITY_APPARMOR)
+#define APPARMOR_ENABLED 1,
+#else
+#define APPARMOR_ENABLED
+#endif
+
+#if IS_ENABLED(CONFIG_SECURITY_TOMOYO)
+#define TOMOYO_ENABLED 1,
+#else
+#define TOMOYO_ENABLED
+#endif
+
+#if IS_ENABLED(CONFIG_SECURITY_YAMA)
+#define YAMA_ENABLED 1,
+#else
+#define YAMA_ENABLED
+#endif
+
+#if IS_ENABLED(CONFIG_SECURITY_LOADPIN)
+#define LOADPIN_ENABLED 1,
+#else
+#define LOADPIN_ENABLED
+#endif
+
+#if IS_ENABLED(CONFIG_SECURITY_LOCKDOWN_LSM)
+#define LOCKDOWN_ENABLED 1,
+#else
+#define LOCKDOWN_ENABLED
+#endif
+
+#if IS_ENABLED(CONFIG_SECURITY_SAFESETID)
+#define SAFESETID_ENABLED 1,
+#else
+#define SAFESETID_ENABLED
+#endif
+
+#if IS_ENABLED(CONFIG_BPF_LSM)
+#define BPF_LSM_ENABLED 1,
+#else
+#define BPF_LSM_ENABLED
+#endif
+
+#if IS_ENABLED(CONFIG_SECURITY_LANDLOCK)
+#define LANDLOCK_ENABLED 1,
+#else
+#define LANDLOCK_ENABLED
+#endif
+
+#if IS_ENABLED(CONFIG_IMA)
+#define IMA_ENABLED 1,
+#else
+#define IMA_ENABLED
+#endif
+
+#if IS_ENABLED(CONFIG_EVM)
+#define EVM_ENABLED 1,
+#else
+#define EVM_ENABLED
+#endif
+
+/*
+ * There is a trailing comma that we need to be accounted for. This is done by
+ * using a skipped argument in __COUNT_LSMS
+ */
+#define __COUNT_LSMS(skipped_arg, args...) COUNT_ARGS(args...)
+#define COUNT_LSMS(args...) __COUNT_LSMS(args)
+
+#define MAX_LSM_COUNT \
+ COUNT_LSMS( \
+ CAPABILITIES_ENABLED \
+ SELINUX_ENABLED \
+ SMACK_ENABLED \
+ APPARMOR_ENABLED \
+ TOMOYO_ENABLED \
+ YAMA_ENABLED \
+ LOADPIN_ENABLED \
+ LOCKDOWN_ENABLED \
+ SAFESETID_ENABLED \
+ BPF_LSM_ENABLED \
+ LANDLOCK_ENABLED \
+ IMA_ENABLED \
+ EVM_ENABLED)
+
+#else
+
+#define MAX_LSM_COUNT 0
+
+#endif /* CONFIG_SECURITY */
+
+#endif /* __LINUX_LSM_COUNT_H */
--
2.45.0.rc1.225.g2a3ae87e7f-goog
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v12 3/5] security: Replace indirect LSM hook calls with static calls
2024-05-16 0:35 [PATCH v12 0/5] Reduce overhead of LSMs with static calls KP Singh
2024-05-16 0:35 ` [PATCH v12 1/5] kernel: Add helper macros for loop unrolling KP Singh
2024-05-16 0:35 ` [PATCH v12 2/5] security: Count the LSMs enabled at compile time KP Singh
@ 2024-05-16 0:35 ` KP Singh
2024-06-27 20:28 ` Jonathan Corbet
2024-05-16 0:35 ` [PATCH v12 4/5] security: Update non standard hooks to use " KP Singh
` (3 subsequent siblings)
6 siblings, 1 reply; 18+ messages in thread
From: KP Singh @ 2024-05-16 0:35 UTC (permalink / raw)
To: linux-security-module, bpf
Cc: ast, paul, casey, andrii, keescook, daniel, renauld, revest, song,
KP Singh
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:
0xff...0320 <+0>: endbr64
0xff...0324 <+4>: push %rbp
0xff...0325 <+5>: push %r15
0xff...0327 <+7>: push %r14
0xff...0329 <+9>: push %rbx
0xff...032a <+10>: mov %rdx,%rbx
0xff...032d <+13>: mov %esi,%ebp
0xff...032f <+15>: mov %rdi,%r14
0xff...0332 <+18>: mov $0xff...7030,%r15
0xff...0339 <+25>: mov (%r15),%r15
0xff...033c <+28>: test %r15,%r15
0xff...033f <+31>: je 0xff...0358 <security_file_ioctl+56>
0xff...0341 <+33>: mov 0x18(%r15),%r11
0xff...0345 <+37>: mov %r14,%rdi
0xff...0348 <+40>: mov %ebp,%esi
0xff...034a <+42>: mov %rbx,%rdx
0xff...034d <+45>: call 0xff...2e0 <__x86_indirect_thunk_array+352>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Indirect calls that use retpolines leading to overhead, not just due
to extra instruction but also branch misses.
0xff...0352 <+50>: test %eax,%eax
0xff...0354 <+52>: je 0xff...0339 <security_file_ioctl+25>
0xff...0356 <+54>: jmp 0xff...035a <security_file_ioctl+58>
0xff...0358 <+56>: xor %eax,%eax
0xff...035a <+58>: pop %rbx
0xff...035b <+59>: pop %r14
0xff...035d <+61>: pop %r15
0xff...035f <+63>: pop %rbp
0xff...0360 <+64>: jmp 0xff...47c4 <__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:
0xff...0ca0 <+0>: endbr64
0xff...0ca4 <+4>: nopl 0x0(%rax,%rax,1)
0xff...0ca9 <+9>: push %rbp
0xff...0caa <+10>: push %r14
0xff...0cac <+12>: push %rbx
0xff...0cad <+13>: mov %rdx,%rbx
0xff...0cb0 <+16>: mov %esi,%ebp
0xff...0cb2 <+18>: mov %rdi,%r14
0xff...0cb5 <+21>: jmp 0xff...0cc7 <security_file_ioctl+39>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Static key enabled for SELinux
0xffffffff818f0cb7 <+23>: jmp 0xff...0cde <security_file_ioctl+62>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Static key enabled for BPF LSM. This is something that is changed to
default to false to avoid the existing side effect issues of BPF LSM
[1] in a subsequent patch.
0xff...0cb9 <+25>: xor %eax,%eax
0xff...0cbb <+27>: xchg %ax,%ax
0xff...0cbd <+29>: pop %rbx
0xff...0cbe <+30>: pop %r14
0xff...0cc0 <+32>: pop %rbp
0xff...0cc1 <+33>: cs jmp 0xff...0000 <__x86_return_thunk>
0xff...0cc7 <+39>: endbr64
0xff...0ccb <+43>: mov %r14,%rdi
0xff...0cce <+46>: mov %ebp,%esi
0xff...0cd0 <+48>: mov %rbx,%rdx
0xff...0cd3 <+51>: call 0xff...3230 <selinux_file_ioctl>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Direct call to SELinux.
0xff...0cd8 <+56>: test %eax,%eax
0xff...0cda <+58>: jne 0xff...0cbd <security_file_ioctl+29>
0xff...0cdc <+60>: jmp 0xff...0cb7 <security_file_ioctl+23>
0xff...0cde <+62>: endbr64
0xff...0ce2 <+66>: mov %r14,%rdi
0xff...0ce5 <+69>: mov %ebp,%esi
0xff...0ce7 <+71>: mov %rbx,%rdx
0xff...0cea <+74>: call 0xff...e220 <bpf_lsm_file_ioctl>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Direct call to BPF LSM.
0xff...0cef <+79>: test %eax,%eax
0xff...0cf1 <+81>: jne 0xff...0cbd <security_file_ioctl+29>
0xff...0cf3 <+83>: jmp 0xff...0cb9 <security_file_ioctl+25>
0xff...0cf5 <+85>: endbr64
0xff...0cf9 <+89>: mov %r14,%rdi
0xff...0cfc <+92>: mov %ebp,%esi
0xff...0cfe <+94>: mov %rbx,%rdx
0xff...0d01 <+97>: pop %rbx
0xff...0d02 <+98>: pop %r14
0xff...0d04 <+100>: pop %rbp
0xff...0d05 <+101>: ret
0xff...0d06 <+102>: int3
0xff...0d07 <+103>: int3
0xff...0d08 <+104>: int3
0xff...0d09 <+105>: int3
While this patch uses static_branch_unlikely indicating that an LSM hook
is likely to be not present. In most cases this is still a better choice
as even when an LSM with one hook is added, empty slots are created for
all LSM hooks (especially when many LSMs that do not initialize most
hooks are present on the system).
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
lsm_for_each_hook where the lsm_callback is directly invoked as an
indirect call. These are updated in a subsequent patch to also use
static calls.
Below are results of the relevant Unixbench system benchmarks with BPF LSM
and SELinux enabled with default policies enabled with and without these
patches.
Benchmark Delta(%): (+ is better)
===============================================================================
Execl Throughput +1.9356
File Write 1024 bufsize 2000 maxblocks +6.5953
Pipe Throughput +9.5499
Pipe-based Context Switching +3.0209
Process Creation +2.3246
Shell Scripts (1 concurrent) +1.4975
System Call Overhead +2.7815
System Benchmarks Index Score (Partial Only): +3.4859
In the best case, some syscalls like eventfd_create benefitted to about ~10%.
Reviewed-by: Casey Schaufler <casey@schaufler-ca.com>
Reviewed-by: Kees Cook <keescook@chromium.org>
Acked-by: Song Liu <song@kernel.org>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: KP Singh <kpsingh@kernel.org>
---
include/linux/lsm_hooks.h | 72 ++++++++++++--
security/security.c | 198 ++++++++++++++++++++++++++------------
2 files changed, 197 insertions(+), 73 deletions(-)
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index a2ade0ffe9e7..07ecd03d30b0 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -30,19 +30,66 @@
#include <linux/init.h>
#include <linux/rculist.h>
#include <linux/xattr.h>
+#include <linux/static_call.h>
+#include <linux/unroll.h>
+#include <linux/jump_label.h>
+#include <linux/lsm_count.h>
+
+#define SECURITY_HOOK_ACTIVE_KEY(HOOK, IDX) security_hook_active_##HOOK##_##IDX
+
+/*
+ * Identifier for the LSM static calls.
+ * HOOK is an LSM hook as defined in linux/lsm_hookdefs.h
+ * IDX is the index of the static call. 0 <= NUM < MAX_LSM_COUNT
+ */
+#define LSM_STATIC_CALL(HOOK, IDX) lsm_static_call_##HOOK##_##IDX
+
+/*
+ * Call the macro M for each LSM hook MAX_LSM_COUNT times.
+ */
+#define LSM_LOOP_UNROLL(M, ...) \
+do { \
+ UNROLL(MAX_LSM_COUNT, M, __VA_ARGS__) \
+} while (0)
+
+#define LSM_DEFINE_UNROLL(M, ...) UNROLL(MAX_LSM_COUNT, M, __VA_ARGS__)
union security_list_options {
#define LSM_HOOK(RET, DEFAULT, NAME, ...) RET (*NAME)(__VA_ARGS__);
#include "lsm_hook_defs.h"
#undef LSM_HOOK
+ void *lsm_func_addr;
};
-struct security_hook_heads {
- #define LSM_HOOK(RET, DEFAULT, NAME, ...) struct hlist_head NAME;
- #include "lsm_hook_defs.h"
- #undef LSM_HOOK
+/*
+ * @key: static call key as defined by STATIC_CALL_KEY
+ * @trampoline: static call trampoline as defined by STATIC_CALL_TRAMP
+ * @hl: The security_hook_list as initialized by the owning LSM.
+ * @active: Enabled when the static call has an LSM hook associated.
+ */
+struct lsm_static_call {
+ struct static_call_key *key;
+ void *trampoline;
+ struct security_hook_list *hl;
+ /* this needs to be true or false based on what the key defaults to */
+ struct static_key_false *active;
} __randomize_layout;
+/*
+ * Table of the static calls for each LSM hook.
+ * Once the LSMs are initialized, their callbacks will be copied to these
+ * tables such that the calls are filled backwards (from last to first).
+ * This way, we can jump directly to the first used static call, and execute
+ * all of them after. This essentially makes the entry point
+ * dynamic to adapt the number of static calls to the number of callbacks.
+ */
+struct lsm_static_calls_table {
+ #define LSM_HOOK(RET, DEFAULT, NAME, ...) \
+ struct lsm_static_call NAME[MAX_LSM_COUNT];
+ #include <linux/lsm_hook_defs.h>
+ #undef LSM_HOOK
+} __packed __randomize_layout;
+
/**
* struct lsm_id - Identify a Linux Security Module.
* @lsm: name of the LSM, must be approved by the LSM maintainers
@@ -58,10 +105,14 @@ struct lsm_id {
/*
* Security module hook list structure.
* For use with generic list macros for common operations.
+ *
+ * struct security_hook_list - Contents of a cacheable, mappable object.
+ * @scalls: The beginning of the array of static calls assigned to this hook.
+ * @hook: The callback for the hook.
+ * @lsm: The name of the lsm that owns this hook.
*/
struct security_hook_list {
- struct hlist_node list;
- struct hlist_head *head;
+ struct lsm_static_call *scalls;
union security_list_options hook;
const struct lsm_id *lsmid;
} __randomize_layout;
@@ -110,10 +161,12 @@ static inline struct xattr *lsm_get_xattr_slot(struct xattr *xattrs,
* care of the common case and reduces the amount of
* text involved.
*/
-#define LSM_HOOK_INIT(HEAD, HOOK) \
- { .head = &security_hook_heads.HEAD, .hook = { .HEAD = HOOK } }
+#define LSM_HOOK_INIT(NAME, HOOK) \
+ { \
+ .scalls = static_calls_table.NAME, \
+ .hook = { .NAME = HOOK } \
+ }
-extern struct security_hook_heads security_hook_heads;
extern char *lsm_names;
extern void security_add_hooks(struct security_hook_list *hooks, int count,
@@ -151,5 +204,6 @@ extern struct lsm_info __start_early_lsm_info[], __end_early_lsm_info[];
__aligned(sizeof(unsigned long))
extern int lsm_inode_alloc(struct inode *inode);
+extern struct lsm_static_calls_table static_calls_table __ro_after_init;
#endif /* ! __LINUX_LSM_HOOKS_H */
diff --git a/security/security.c b/security/security.c
index 7e118858b545..39ffe949e509 100644
--- a/security/security.c
+++ b/security/security.c
@@ -29,6 +29,8 @@
#include <linux/msg.h>
#include <linux/overflow.h>
#include <net/flow.h>
+#include <linux/static_call.h>
+#include <linux/jump_label.h>
/* How many LSMs were built into the kernel? */
#define LSM_COUNT (__end_lsm_info - __start_lsm_info)
@@ -92,7 +94,6 @@ const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX + 1] = {
[LOCKDOWN_CONFIDENTIALITY_MAX] = "confidentiality",
};
-struct security_hook_heads security_hook_heads __ro_after_init;
static BLOCKING_NOTIFIER_HEAD(blocking_lsm_notifier_chain);
static struct kmem_cache *lsm_file_cache;
@@ -111,6 +112,51 @@ static __initconst const char *const builtin_lsm_order = CONFIG_LSM;
static __initdata struct lsm_info **ordered_lsms;
static __initdata struct lsm_info *exclusive;
+
+#ifdef CONFIG_HAVE_STATIC_CALL
+#define LSM_HOOK_TRAMP(NAME, NUM) \
+ &STATIC_CALL_TRAMP(LSM_STATIC_CALL(NAME, NUM))
+#else
+#define LSM_HOOK_TRAMP(NAME, NUM) NULL
+#endif
+
+/*
+ * Define static calls and static keys for each LSM hook.
+ */
+
+#define DEFINE_LSM_STATIC_CALL(NUM, NAME, RET, ...) \
+ DEFINE_STATIC_CALL_NULL(LSM_STATIC_CALL(NAME, NUM), \
+ *((RET(*)(__VA_ARGS__))NULL)); \
+ DEFINE_STATIC_KEY_FALSE(SECURITY_HOOK_ACTIVE_KEY(NAME, NUM));
+
+#define LSM_HOOK(RET, DEFAULT, NAME, ...) \
+ LSM_DEFINE_UNROLL(DEFINE_LSM_STATIC_CALL, NAME, RET, __VA_ARGS__)
+#include <linux/lsm_hook_defs.h>
+#undef LSM_HOOK
+#undef DEFINE_LSM_STATIC_CALL
+
+/*
+ * Initialise a table of static calls for each LSM hook.
+ * DEFINE_STATIC_CALL_NULL invocation above generates a key (STATIC_CALL_KEY)
+ * and a trampoline (STATIC_CALL_TRAMP) which are used to call
+ * __static_call_update when updating the static call.
+ */
+struct lsm_static_calls_table static_calls_table __ro_after_init = {
+#define INIT_LSM_STATIC_CALL(NUM, NAME) \
+ (struct lsm_static_call) { \
+ .key = &STATIC_CALL_KEY(LSM_STATIC_CALL(NAME, NUM)), \
+ .trampoline = LSM_HOOK_TRAMP(NAME, NUM), \
+ .active = &SECURITY_HOOK_ACTIVE_KEY(NAME, NUM), \
+ },
+#define LSM_HOOK(RET, DEFAULT, NAME, ...) \
+ .NAME = { \
+ LSM_DEFINE_UNROLL(INIT_LSM_STATIC_CALL, NAME) \
+ },
+#include <linux/lsm_hook_defs.h>
+#undef LSM_HOOK
+#undef INIT_LSM_STATIC_CALL
+};
+
static __initdata bool debug;
#define init_debug(...) \
do { \
@@ -171,7 +217,7 @@ static void __init append_ordered_lsm(struct lsm_info *lsm, const char *from)
if (exists_ordered_lsm(lsm))
return;
- if (WARN(last_lsm == LSM_COUNT, "%s: out of LSM slots!?\n", from))
+ if (WARN(last_lsm == LSM_COUNT, "%s: out of LSM static calls!?\n", from))
return;
/* Enable this LSM, if it is not already set. */
@@ -350,6 +396,25 @@ static void __init ordered_lsm_parse(const char *order, const char *origin)
kfree(sep);
}
+static void __init lsm_static_call_init(struct security_hook_list *hl)
+{
+ struct lsm_static_call *scall = hl->scalls;
+ int i;
+
+ for (i = 0; i < MAX_LSM_COUNT; i++) {
+ /* Update the first static call that is not used yet */
+ if (!scall->hl) {
+ __static_call_update(scall->key, scall->trampoline,
+ hl->hook.lsm_func_addr);
+ scall->hl = hl;
+ static_branch_enable(scall->active);
+ return;
+ }
+ scall++;
+ }
+ panic("%s - Ran out of static slots.\n", __func__);
+}
+
static void __init lsm_early_cred(struct cred *cred);
static void __init lsm_early_task(struct task_struct *task);
@@ -429,11 +494,6 @@ int __init early_security_init(void)
{
struct lsm_info *lsm;
-#define LSM_HOOK(RET, DEFAULT, NAME, ...) \
- INIT_HLIST_HEAD(&security_hook_heads.NAME);
-#include "linux/lsm_hook_defs.h"
-#undef LSM_HOOK
-
for (lsm = __start_early_lsm_info; lsm < __end_early_lsm_info; lsm++) {
if (!lsm->enabled)
lsm->enabled = &lsm_enabled_true;
@@ -561,7 +621,7 @@ void __init security_add_hooks(struct security_hook_list *hooks, int count,
for (i = 0; i < count; i++) {
hooks[i].lsmid = lsmid;
- hlist_add_tail_rcu(&hooks[i].list, hooks[i].head);
+ lsm_static_call_init(&hooks[i]);
}
/*
@@ -853,29 +913,43 @@ int lsm_fill_user_ctx(struct lsm_ctx __user *uctx, u32 *uctx_len,
* call_int_hook:
* This is a hook that returns a value.
*/
+#define __CALL_STATIC_VOID(NUM, HOOK, ...) \
+do { \
+ if (static_branch_unlikely(&SECURITY_HOOK_ACTIVE_KEY(HOOK, NUM))) { \
+ static_call(LSM_STATIC_CALL(HOOK, NUM))(__VA_ARGS__); \
+ } \
+} while (0);
-#define call_void_hook(FUNC, ...) \
- do { \
- struct security_hook_list *P; \
- \
- hlist_for_each_entry(P, &security_hook_heads.FUNC, list) \
- P->hook.FUNC(__VA_ARGS__); \
+#define call_void_hook(HOOK, ...) \
+ do { \
+ LSM_LOOP_UNROLL(__CALL_STATIC_VOID, HOOK, __VA_ARGS__); \
} while (0)
-#define call_int_hook(FUNC, ...) ({ \
- int RC = LSM_RET_DEFAULT(FUNC); \
- do { \
- struct security_hook_list *P; \
- \
- hlist_for_each_entry(P, &security_hook_heads.FUNC, list) { \
- RC = P->hook.FUNC(__VA_ARGS__); \
- if (RC != LSM_RET_DEFAULT(FUNC)) \
- break; \
- } \
- } while (0); \
- RC; \
+
+#define __CALL_STATIC_INT(NUM, R, HOOK, LABEL, ...) \
+do { \
+ if (static_branch_unlikely(&SECURITY_HOOK_ACTIVE_KEY(HOOK, NUM))) { \
+ R = static_call(LSM_STATIC_CALL(HOOK, NUM))(__VA_ARGS__); \
+ if (R != LSM_RET_DEFAULT(HOOK)) \
+ goto LABEL; \
+ } \
+} while (0);
+
+#define call_int_hook(HOOK, ...) \
+({ \
+ __label__ out; \
+ int RC = LSM_RET_DEFAULT(HOOK); \
+ \
+ LSM_LOOP_UNROLL(__CALL_STATIC_INT, RC, HOOK, out, __VA_ARGS__); \
+out: \
+ RC; \
})
+#define lsm_for_each_hook(scall, NAME) \
+ for (scall = static_calls_table.NAME; \
+ scall - static_calls_table.NAME < MAX_LSM_COUNT; scall++) \
+ if (static_key_enabled(&scall->active->key))
+
/* Security operations */
/**
@@ -1110,7 +1184,7 @@ int security_settime64(const struct timespec64 *ts, const struct timezone *tz)
*/
int security_vm_enough_memory_mm(struct mm_struct *mm, long pages)
{
- struct security_hook_list *hp;
+ struct lsm_static_call *scall;
int cap_sys_admin = 1;
int rc;
@@ -1121,8 +1195,8 @@ int security_vm_enough_memory_mm(struct mm_struct *mm, long pages)
* agree that it should be set it will. If any module
* thinks it should not be set it won't.
*/
- hlist_for_each_entry(hp, &security_hook_heads.vm_enough_memory, list) {
- rc = hp->hook.vm_enough_memory(mm, pages);
+ lsm_for_each_hook(scall, vm_enough_memory) {
+ rc = scall->hl->hook.vm_enough_memory(mm, pages);
if (rc <= 0) {
cap_sys_admin = 0;
break;
@@ -1269,13 +1343,12 @@ int security_fs_context_dup(struct fs_context *fc, struct fs_context *src_fc)
int security_fs_context_parse_param(struct fs_context *fc,
struct fs_parameter *param)
{
- struct security_hook_list *hp;
+ struct lsm_static_call *scall;
int trc;
int rc = -ENOPARAM;
- hlist_for_each_entry(hp, &security_hook_heads.fs_context_parse_param,
- list) {
- trc = hp->hook.fs_context_parse_param(fc, param);
+ lsm_for_each_hook(scall, fs_context_parse_param) {
+ trc = scall->hl->hook.fs_context_parse_param(fc, param);
if (trc == 0)
rc = 0;
else if (trc != -ENOPARAM)
@@ -1505,12 +1578,11 @@ int security_sb_set_mnt_opts(struct super_block *sb,
unsigned long kern_flags,
unsigned long *set_kern_flags)
{
- struct security_hook_list *hp;
+ struct lsm_static_call *scall;
int rc = mnt_opts ? -EOPNOTSUPP : LSM_RET_DEFAULT(sb_set_mnt_opts);
- hlist_for_each_entry(hp, &security_hook_heads.sb_set_mnt_opts,
- list) {
- rc = hp->hook.sb_set_mnt_opts(sb, mnt_opts, kern_flags,
+ lsm_for_each_hook(scall, sb_set_mnt_opts) {
+ rc = scall->hl->hook.sb_set_mnt_opts(sb, mnt_opts, kern_flags,
set_kern_flags);
if (rc != LSM_RET_DEFAULT(sb_set_mnt_opts))
break;
@@ -1705,7 +1777,7 @@ int security_inode_init_security(struct inode *inode, struct inode *dir,
const struct qstr *qstr,
const initxattrs initxattrs, void *fs_data)
{
- struct security_hook_list *hp;
+ struct lsm_static_call *scall;
struct xattr *new_xattrs = NULL;
int ret = -EOPNOTSUPP, xattr_count = 0;
@@ -1723,9 +1795,8 @@ int security_inode_init_security(struct inode *inode, struct inode *dir,
return -ENOMEM;
}
- hlist_for_each_entry(hp, &security_hook_heads.inode_init_security,
- list) {
- ret = hp->hook.inode_init_security(inode, dir, qstr, new_xattrs,
+ lsm_for_each_hook(scall, inode_init_security) {
+ ret = scall->hl->hook.inode_init_security(inode, dir, qstr, new_xattrs,
&xattr_count);
if (ret && ret != -EOPNOTSUPP)
goto out;
@@ -3530,10 +3601,10 @@ int security_task_prctl(int option, unsigned long arg2, unsigned long arg3,
{
int thisrc;
int rc = LSM_RET_DEFAULT(task_prctl);
- struct security_hook_list *hp;
+ struct lsm_static_call *scall;
- hlist_for_each_entry(hp, &security_hook_heads.task_prctl, list) {
- thisrc = hp->hook.task_prctl(option, arg2, arg3, arg4, arg5);
+ lsm_for_each_hook(scall, task_prctl) {
+ thisrc = scall->hl->hook.task_prctl(option, arg2, arg3, arg4, arg5);
if (thisrc != LSM_RET_DEFAULT(task_prctl)) {
rc = thisrc;
if (thisrc != 0)
@@ -3939,7 +4010,7 @@ EXPORT_SYMBOL(security_d_instantiate);
int security_getselfattr(unsigned int attr, struct lsm_ctx __user *uctx,
u32 __user *size, u32 flags)
{
- struct security_hook_list *hp;
+ struct lsm_static_call *scall;
struct lsm_ctx lctx = { .id = LSM_ID_UNDEF, };
u8 __user *base = (u8 __user *)uctx;
u32 entrysize;
@@ -3977,13 +4048,13 @@ int security_getselfattr(unsigned int attr, struct lsm_ctx __user *uctx,
* In the usual case gather all the data from the LSMs.
* In the single case only get the data from the LSM specified.
*/
- hlist_for_each_entry(hp, &security_hook_heads.getselfattr, list) {
- if (single && lctx.id != hp->lsmid->id)
+ lsm_for_each_hook(scall, getselfattr) {
+ if (single && lctx.id != scall->hl->lsmid->id)
continue;
entrysize = left;
if (base)
uctx = (struct lsm_ctx __user *)(base + total);
- rc = hp->hook.getselfattr(attr, uctx, &entrysize, flags);
+ rc = scall->hl->hook.getselfattr(attr, uctx, &entrysize, flags);
if (rc == -EOPNOTSUPP) {
rc = 0;
continue;
@@ -4032,7 +4103,7 @@ int security_getselfattr(unsigned int attr, struct lsm_ctx __user *uctx,
int security_setselfattr(unsigned int attr, struct lsm_ctx __user *uctx,
u32 size, u32 flags)
{
- struct security_hook_list *hp;
+ struct lsm_static_call *scall;
struct lsm_ctx *lctx;
int rc = LSM_RET_DEFAULT(setselfattr);
u64 required_len;
@@ -4055,9 +4126,9 @@ int security_setselfattr(unsigned int attr, struct lsm_ctx __user *uctx,
goto free_out;
}
- hlist_for_each_entry(hp, &security_hook_heads.setselfattr, list)
- if ((hp->lsmid->id) == lctx->id) {
- rc = hp->hook.setselfattr(attr, lctx, size, flags);
+ lsm_for_each_hook(scall, setselfattr)
+ if ((scall->hl->lsmid->id) == lctx->id) {
+ rc = scall->hl->hook.setselfattr(attr, lctx, size, flags);
break;
}
@@ -4080,12 +4151,12 @@ int security_setselfattr(unsigned int attr, struct lsm_ctx __user *uctx,
int security_getprocattr(struct task_struct *p, int lsmid, const char *name,
char **value)
{
- struct security_hook_list *hp;
+ struct lsm_static_call *scall;
- hlist_for_each_entry(hp, &security_hook_heads.getprocattr, list) {
- if (lsmid != 0 && lsmid != hp->lsmid->id)
+ lsm_for_each_hook(scall, getprocattr) {
+ if (lsmid != 0 && lsmid != scall->hl->lsmid->id)
continue;
- return hp->hook.getprocattr(p, name, value);
+ return scall->hl->hook.getprocattr(p, name, value);
}
return LSM_RET_DEFAULT(getprocattr);
}
@@ -4104,12 +4175,12 @@ int security_getprocattr(struct task_struct *p, int lsmid, const char *name,
*/
int security_setprocattr(int lsmid, const char *name, void *value, size_t size)
{
- struct security_hook_list *hp;
+ struct lsm_static_call *scall;
- hlist_for_each_entry(hp, &security_hook_heads.setprocattr, list) {
- if (lsmid != 0 && lsmid != hp->lsmid->id)
+ lsm_for_each_hook(scall, setprocattr) {
+ if (lsmid != 0 && lsmid != scall->hl->lsmid->id)
continue;
- return hp->hook.setprocattr(name, value, size);
+ return scall->hl->hook.setprocattr(name, value, size);
}
return LSM_RET_DEFAULT(setprocattr);
}
@@ -5196,7 +5267,7 @@ int security_xfrm_state_pol_flow_match(struct xfrm_state *x,
struct xfrm_policy *xp,
const struct flowi_common *flic)
{
- struct security_hook_list *hp;
+ struct lsm_static_call *scall;
int rc = LSM_RET_DEFAULT(xfrm_state_pol_flow_match);
/*
@@ -5208,9 +5279,8 @@ int security_xfrm_state_pol_flow_match(struct xfrm_state *x,
* For speed optimization, we explicitly break the loop rather than
* using the macro
*/
- hlist_for_each_entry(hp, &security_hook_heads.xfrm_state_pol_flow_match,
- list) {
- rc = hp->hook.xfrm_state_pol_flow_match(x, xp, flic);
+ lsm_for_each_hook(scall, xfrm_state_pol_flow_match) {
+ rc = scall->hl->hook.xfrm_state_pol_flow_match(x, xp, flic);
break;
}
return rc;
--
2.45.0.rc1.225.g2a3ae87e7f-goog
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v12 4/5] security: Update non standard hooks to use static calls
2024-05-16 0:35 [PATCH v12 0/5] Reduce overhead of LSMs with static calls KP Singh
` (2 preceding siblings ...)
2024-05-16 0:35 ` [PATCH v12 3/5] security: Replace indirect LSM hook calls with static calls KP Singh
@ 2024-05-16 0:35 ` KP Singh
2024-05-16 0:35 ` [PATCH v12 5/5] bpf: Only enable BPF LSM hooks when an LSM program is attached KP Singh
` (2 subsequent siblings)
6 siblings, 0 replies; 18+ messages in thread
From: KP Singh @ 2024-05-16 0:35 UTC (permalink / raw)
To: linux-security-module, bpf
Cc: ast, paul, casey, andrii, keescook, daniel, renauld, revest, song,
KP Singh
There are some LSM hooks which do not use the common pattern followed
by other LSM hooks and thus cannot use call_{int, void}_hook macros and
instead use lsm_for_each_hook macro which still results in indirect
call.
There is one additional generalizable pattern where a hook matching an
lsmid is called and the indirect calls for these are addressed with the
newly added call_hook_with_lsmid macro which internally uses an
implementation similar to call_int_hook but has an additional check that
matches the lsmid.
For the generic case the lsm_for_each_hook macro is updated to accept
logic before and after the invocation of the LSM hook (static call) in
the unrolled loop.
Reviewed-by: Casey Schaufler <casey@schaufler-ca.com>
Reviewed-by: Kees Cook <keescook@chromium.org>
Signed-off-by: KP Singh <kpsingh@kernel.org>
---
security/security.c | 248 +++++++++++++++++++++++++-------------------
1 file changed, 144 insertions(+), 104 deletions(-)
diff --git a/security/security.c b/security/security.c
index 39ffe949e509..9654ca074aed 100644
--- a/security/security.c
+++ b/security/security.c
@@ -945,10 +945,48 @@ out: \
RC; \
})
-#define lsm_for_each_hook(scall, NAME) \
- for (scall = static_calls_table.NAME; \
- scall - static_calls_table.NAME < MAX_LSM_COUNT; scall++) \
- if (static_key_enabled(&scall->active->key))
+/*
+ * Can be used in the context passed to lsm_for_each_hook to get the lsmid of the
+ * current hook
+ */
+#define current_lsmid() _hook_lsmid
+
+#define __CALL_HOOK(NUM, HOOK, RC, BLOCK_BEFORE, BLOCK_AFTER, ...) \
+do { \
+ int __maybe_unused _hook_lsmid; \
+ \
+ if (static_branch_unlikely(&SECURITY_HOOK_ACTIVE_KEY(HOOK, NUM))) { \
+ _hook_lsmid = static_calls_table.HOOK[NUM].hl->lsmid->id; \
+ BLOCK_BEFORE \
+ RC = static_call(LSM_STATIC_CALL(HOOK, NUM))(__VA_ARGS__); \
+ BLOCK_AFTER \
+ } \
+} while (0);
+
+#define lsm_for_each_hook(HOOK, RC, BLOCK_AFTER, ...) \
+ LSM_LOOP_UNROLL(__CALL_HOOK, HOOK, RC, ;, BLOCK_AFTER, __VA_ARGS__)
+
+#define call_hook_with_lsmid(HOOK, LSMID, ...) \
+({ \
+ __label__ out; \
+ int RC = LSM_RET_DEFAULT(HOOK); \
+ \
+ LSM_LOOP_UNROLL(__CALL_HOOK, HOOK, RC, \
+ /* BLOCK BEFORE INVOCATION */ \
+ { \
+ if (current_lsmid() != LSMID) \
+ continue; \
+ }, \
+ /* END BLOCK BEFORE INVOCATION */ \
+ /* BLOCK AFTER INVOCATION */ \
+ { \
+ goto out; \
+ }, \
+ /* END BLOCK AFTER INVOCATION */ \
+ __VA_ARGS__); \
+out: \
+ RC; \
+})
/* Security operations */
@@ -1184,7 +1222,6 @@ int security_settime64(const struct timespec64 *ts, const struct timezone *tz)
*/
int security_vm_enough_memory_mm(struct mm_struct *mm, long pages)
{
- struct lsm_static_call *scall;
int cap_sys_admin = 1;
int rc;
@@ -1195,13 +1232,20 @@ int security_vm_enough_memory_mm(struct mm_struct *mm, long pages)
* agree that it should be set it will. If any module
* thinks it should not be set it won't.
*/
- lsm_for_each_hook(scall, vm_enough_memory) {
- rc = scall->hl->hook.vm_enough_memory(mm, pages);
- if (rc <= 0) {
- cap_sys_admin = 0;
- break;
- }
- }
+
+ lsm_for_each_hook(
+ vm_enough_memory, rc,
+ /* BLOCK AFTER INVOCATION */
+ {
+ if (rc <= 0) {
+ cap_sys_admin = 0;
+ goto out;
+ }
+ },
+ /* END BLOCK AFTER INVOCATION */
+ mm, pages);
+
+out:
return __vm_enough_memory(mm, pages, cap_sys_admin);
}
@@ -1343,17 +1387,21 @@ int security_fs_context_dup(struct fs_context *fc, struct fs_context *src_fc)
int security_fs_context_parse_param(struct fs_context *fc,
struct fs_parameter *param)
{
- struct lsm_static_call *scall;
- int trc;
+ int trc = LSM_RET_DEFAULT(fs_context_parse_param);
int rc = -ENOPARAM;
- lsm_for_each_hook(scall, fs_context_parse_param) {
- trc = scall->hl->hook.fs_context_parse_param(fc, param);
- if (trc == 0)
- rc = 0;
- else if (trc != -ENOPARAM)
- return trc;
- }
+ lsm_for_each_hook(
+ fs_context_parse_param, trc,
+ /* BLOCK AFTER INVOCATION */
+ {
+ if (trc == 0)
+ rc = 0;
+ else if (trc != -ENOPARAM)
+ return trc;
+ },
+ /* END BLOCK AFTER INVOCATION */
+ fc, param);
+
return rc;
}
@@ -1578,15 +1626,19 @@ int security_sb_set_mnt_opts(struct super_block *sb,
unsigned long kern_flags,
unsigned long *set_kern_flags)
{
- struct lsm_static_call *scall;
int rc = mnt_opts ? -EOPNOTSUPP : LSM_RET_DEFAULT(sb_set_mnt_opts);
- lsm_for_each_hook(scall, sb_set_mnt_opts) {
- rc = scall->hl->hook.sb_set_mnt_opts(sb, mnt_opts, kern_flags,
- set_kern_flags);
- if (rc != LSM_RET_DEFAULT(sb_set_mnt_opts))
- break;
- }
+ lsm_for_each_hook(
+ sb_set_mnt_opts, rc,
+ /* BLOCK AFTER INVOCATION */
+ {
+ if (rc != LSM_RET_DEFAULT(sb_set_mnt_opts))
+ goto out;
+ },
+ /* END BLOCK AFTER INVOCATION */
+ sb, mnt_opts, kern_flags, set_kern_flags);
+
+out:
return rc;
}
EXPORT_SYMBOL(security_sb_set_mnt_opts);
@@ -1777,7 +1829,6 @@ int security_inode_init_security(struct inode *inode, struct inode *dir,
const struct qstr *qstr,
const initxattrs initxattrs, void *fs_data)
{
- struct lsm_static_call *scall;
struct xattr *new_xattrs = NULL;
int ret = -EOPNOTSUPP, xattr_count = 0;
@@ -1795,18 +1846,21 @@ int security_inode_init_security(struct inode *inode, struct inode *dir,
return -ENOMEM;
}
- lsm_for_each_hook(scall, inode_init_security) {
- ret = scall->hl->hook.inode_init_security(inode, dir, qstr, new_xattrs,
- &xattr_count);
- if (ret && ret != -EOPNOTSUPP)
- goto out;
+ lsm_for_each_hook(
+ inode_init_security, ret,
+ /* BLOCK AFTER INVOCATION */
+ {
/*
* As documented in lsm_hooks.h, -EOPNOTSUPP in this context
* means that the LSM is not willing to provide an xattr, not
* that it wants to signal an error. Thus, continue to invoke
* the remaining LSMs.
*/
- }
+ if (ret && ret != -EOPNOTSUPP)
+ goto out;
+ },
+ /* END BLOCK AFTER INVOCATION */
+ inode, dir, qstr, new_xattrs, &xattr_count);
/* If initxattrs() is NULL, xattr_count is zero, skip the call. */
if (!xattr_count)
@@ -3601,16 +3655,21 @@ int security_task_prctl(int option, unsigned long arg2, unsigned long arg3,
{
int thisrc;
int rc = LSM_RET_DEFAULT(task_prctl);
- struct lsm_static_call *scall;
-
- lsm_for_each_hook(scall, task_prctl) {
- thisrc = scall->hl->hook.task_prctl(option, arg2, arg3, arg4, arg5);
- if (thisrc != LSM_RET_DEFAULT(task_prctl)) {
- rc = thisrc;
- if (thisrc != 0)
- break;
- }
- }
+
+ lsm_for_each_hook(
+ task_prctl, thisrc,
+ /* BLOCK AFTER INVOCATION */
+ {
+ if (thisrc != LSM_RET_DEFAULT(task_prctl)) {
+ rc = thisrc;
+ if (thisrc != 0)
+ goto out;
+ }
+ },
+ /* END BLOCK AFTER INVOCATION */
+ option, arg2, arg3, arg4, arg5);
+
+out:
return rc;
}
@@ -4010,7 +4069,6 @@ EXPORT_SYMBOL(security_d_instantiate);
int security_getselfattr(unsigned int attr, struct lsm_ctx __user *uctx,
u32 __user *size, u32 flags)
{
- struct lsm_static_call *scall;
struct lsm_ctx lctx = { .id = LSM_ID_UNDEF, };
u8 __user *base = (u8 __user *)uctx;
u32 entrysize;
@@ -4048,31 +4106,42 @@ int security_getselfattr(unsigned int attr, struct lsm_ctx __user *uctx,
* In the usual case gather all the data from the LSMs.
* In the single case only get the data from the LSM specified.
*/
- lsm_for_each_hook(scall, getselfattr) {
- if (single && lctx.id != scall->hl->lsmid->id)
- continue;
- entrysize = left;
- if (base)
- uctx = (struct lsm_ctx __user *)(base + total);
- rc = scall->hl->hook.getselfattr(attr, uctx, &entrysize, flags);
- if (rc == -EOPNOTSUPP) {
- rc = 0;
- continue;
- }
- if (rc == -E2BIG) {
- rc = 0;
- left = 0;
- toobig = true;
- } else if (rc < 0)
- return rc;
- else
- left -= entrysize;
+ LSM_LOOP_UNROLL(
+ __CALL_HOOK, getselfattr, rc,
+ /* BLOCK BEFORE INVOCATION */
+ {
+ if (single && lctx.id != current_lsmid())
+ continue;
+ entrysize = left;
+ if (base)
+ uctx = (struct lsm_ctx __user *)(base + total);
+ },
+ /* END BLOCK BEFORE INVOCATION */
+ /* BLOCK AFTER INVOCATION */
+ {
+ if (rc == -EOPNOTSUPP) {
+ rc = 0;
+ } else {
+ if (rc == -E2BIG) {
+ rc = 0;
+ left = 0;
+ toobig = true;
+ } else if (rc < 0)
+ return rc;
+ else
+ left -= entrysize;
+
+ total += entrysize;
+ count += rc;
+ if (single)
+ goto out;
+ }
+ },
+ /* END BLOCK AFTER INVOCATION */
+ attr, uctx, &entrysize, flags);
+
+out:
- total += entrysize;
- count += rc;
- if (single)
- break;
- }
if (put_user(total, size))
return -EFAULT;
if (toobig)
@@ -4103,9 +4172,8 @@ int security_getselfattr(unsigned int attr, struct lsm_ctx __user *uctx,
int security_setselfattr(unsigned int attr, struct lsm_ctx __user *uctx,
u32 size, u32 flags)
{
- struct lsm_static_call *scall;
struct lsm_ctx *lctx;
- int rc = LSM_RET_DEFAULT(setselfattr);
+ int rc;
u64 required_len;
if (flags)
@@ -4126,11 +4194,7 @@ int security_setselfattr(unsigned int attr, struct lsm_ctx __user *uctx,
goto free_out;
}
- lsm_for_each_hook(scall, setselfattr)
- if ((scall->hl->lsmid->id) == lctx->id) {
- rc = scall->hl->hook.setselfattr(attr, lctx, size, flags);
- break;
- }
+ rc = call_hook_with_lsmid(setselfattr, lctx->id, attr, lctx, size, flags);
free_out:
kfree(lctx);
@@ -4151,14 +4215,7 @@ int security_setselfattr(unsigned int attr, struct lsm_ctx __user *uctx,
int security_getprocattr(struct task_struct *p, int lsmid, const char *name,
char **value)
{
- struct lsm_static_call *scall;
-
- lsm_for_each_hook(scall, getprocattr) {
- if (lsmid != 0 && lsmid != scall->hl->lsmid->id)
- continue;
- return scall->hl->hook.getprocattr(p, name, value);
- }
- return LSM_RET_DEFAULT(getprocattr);
+ return call_hook_with_lsmid(getprocattr, lsmid, p, name, value);
}
/**
@@ -4175,14 +4232,7 @@ int security_getprocattr(struct task_struct *p, int lsmid, const char *name,
*/
int security_setprocattr(int lsmid, const char *name, void *value, size_t size)
{
- struct lsm_static_call *scall;
-
- lsm_for_each_hook(scall, setprocattr) {
- if (lsmid != 0 && lsmid != scall->hl->lsmid->id)
- continue;
- return scall->hl->hook.setprocattr(name, value, size);
- }
- return LSM_RET_DEFAULT(setprocattr);
+ return call_hook_with_lsmid(setprocattr, lsmid, name, value, size);
}
/**
@@ -5267,23 +5317,13 @@ int security_xfrm_state_pol_flow_match(struct xfrm_state *x,
struct xfrm_policy *xp,
const struct flowi_common *flic)
{
- struct lsm_static_call *scall;
- int rc = LSM_RET_DEFAULT(xfrm_state_pol_flow_match);
-
/*
* Since this function is expected to return 0 or 1, the judgment
* becomes difficult if multiple LSMs supply this call. Fortunately,
* we can use the first LSM's judgment because currently only SELinux
* supplies this call.
- *
- * For speed optimization, we explicitly break the loop rather than
- * using the macro
*/
- lsm_for_each_hook(scall, xfrm_state_pol_flow_match) {
- rc = scall->hl->hook.xfrm_state_pol_flow_match(x, xp, flic);
- break;
- }
- return rc;
+ return call_int_hook(xfrm_state_pol_flow_match, x, xp, flic);
}
/**
--
2.45.0.rc1.225.g2a3ae87e7f-goog
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v12 5/5] bpf: Only enable BPF LSM hooks when an LSM program is attached
2024-05-16 0:35 [PATCH v12 0/5] Reduce overhead of LSMs with static calls KP Singh
` (3 preceding siblings ...)
2024-05-16 0:35 ` [PATCH v12 4/5] security: Update non standard hooks to use " KP Singh
@ 2024-05-16 0:35 ` KP Singh
2024-06-11 1:05 ` Paul Moore
2024-05-18 6:01 ` [PATCH v12 0/5] Reduce overhead of LSMs with static calls Tetsuo Handa
2024-06-06 15:58 ` Kees Cook
6 siblings, 1 reply; 18+ messages in thread
From: KP Singh @ 2024-05-16 0:35 UTC (permalink / raw)
To: linux-security-module, bpf
Cc: ast, paul, casey, andrii, keescook, daniel, renauld, revest, song,
KP Singh
BPF LSM hooks have side-effects (even when a default value's returned)
as some hooks end up behaving differently due to the very presence of
the hook.
The static keys guarding the BPF LSM hooks are disabled by default and
enabled only when a BPF program is attached implementing the hook
logic. This avoids the issue of the side-effects and also the minor
overhead associated with the empty callback.
security_file_ioctl:
0xff...0e30 <+0>: endbr64
0xff...0e34 <+4>: nopl 0x0(%rax,%rax,1)
0xff...0e39 <+9>: push %rbp
0xff...0e3a <+10>: push %r14
0xff...0e3c <+12>: push %rbx
0xff...0e3d <+13>: mov %rdx,%rbx
0xff...0e40 <+16>: mov %esi,%ebp
0xff...0e42 <+18>: mov %rdi,%r14
0xff...0e45 <+21>: jmp 0xff...0e57 <security_file_ioctl+39>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Static key enabled for SELinux
0xff...0e47 <+23>: xchg %ax,%ax
^^^^^^^^^^^^^^
Static key disabled for BPF. This gets patched when a BPF LSM
program is attached
0xff...0e49 <+25>: xor %eax,%eax
0xff...0e4b <+27>: xchg %ax,%ax
0xff...0e4d <+29>: pop %rbx
0xff...0e4e <+30>: pop %r14
0xff...0e50 <+32>: pop %rbp
0xff...0e51 <+33>: cs jmp 0xff...0000 <__x86_return_thunk>
0xff...0e57 <+39>: endbr64
0xff...0e5b <+43>: mov %r14,%rdi
0xff...0e5e <+46>: mov %ebp,%esi
0xff...0e60 <+48>: mov %rbx,%rdx
0xff...0e63 <+51>: call 0xff...33c0 <selinux_file_ioctl>
0xff...0e68 <+56>: test %eax,%eax
0xff...0e6a <+58>: jne 0xff...0e4d <security_file_ioctl+29>
0xff...0e6c <+60>: jmp 0xff...0e47 <security_file_ioctl+23>
0xff...0e6e <+62>: endbr64
0xff...0e72 <+66>: mov %r14,%rdi
0xff...0e75 <+69>: mov %ebp,%esi
0xff...0e77 <+71>: mov %rbx,%rdx
0xff...0e7a <+74>: call 0xff...e3b0 <bpf_lsm_file_ioctl>
0xff...0e7f <+79>: test %eax,%eax
0xff...0e81 <+81>: jne 0xff...0e4d <security_file_ioctl+29>
0xff...0e83 <+83>: jmp 0xff...0e49 <security_file_ioctl+25>
0xff...0e85 <+85>: endbr64
0xff...0e89 <+89>: mov %r14,%rdi
0xff...0e8c <+92>: mov %ebp,%esi
0xff...0e8e <+94>: mov %rbx,%rdx
0xff...0e91 <+97>: pop %rbx
0xff...0e92 <+98>: pop %r14
0xff...0e94 <+100>: pop %rbp
0xff...0e95 <+101>: ret
This patch enables this by providing a LSM_HOOK_INIT_RUNTIME variant
that allows the LSMs to opt-in to hooks which can be toggled at runtime
which with security_toogle_hook.
Reviewed-by: Kees Cook <keescook@chromium.org>
Acked-by: Casey Schaufler <casey@schaufler-ca.com>
Signed-off-by: KP Singh <kpsingh@kernel.org>
---
include/linux/lsm_hooks.h | 30 ++++++++++++++++++++++++++++-
kernel/bpf/trampoline.c | 40 +++++++++++++++++++++++++++++++++++----
security/bpf/hooks.c | 2 +-
security/security.c | 35 +++++++++++++++++++++++++++++++++-
4 files changed, 100 insertions(+), 7 deletions(-)
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index 07ecd03d30b0..8e15fafd6258 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -110,11 +110,14 @@ struct lsm_id {
* @scalls: The beginning of the array of static calls assigned to this hook.
* @hook: The callback for the hook.
* @lsm: The name of the lsm that owns this hook.
+ * @default_state: The state of the LSM hook when initialized. If set to false,
+ * the static key guarding the hook will be set to disabled.
*/
struct security_hook_list {
struct lsm_static_call *scalls;
union security_list_options hook;
const struct lsm_id *lsmid;
+ bool runtime;
} __randomize_layout;
/*
@@ -164,7 +167,19 @@ static inline struct xattr *lsm_get_xattr_slot(struct xattr *xattrs,
#define LSM_HOOK_INIT(NAME, HOOK) \
{ \
.scalls = static_calls_table.NAME, \
- .hook = { .NAME = HOOK } \
+ .hook = { .NAME = HOOK }, \
+ .runtime = false \
+ }
+
+/*
+ * Initialize hooks that are inactive by default and
+ * enabled at runtime with security_toggle_hook.
+ */
+#define LSM_HOOK_INIT_RUNTIME(NAME, HOOK) \
+ { \
+ .scalls = static_calls_table.NAME, \
+ .hook = { .NAME = HOOK }, \
+ .runtime = true \
}
extern char *lsm_names;
@@ -206,4 +221,17 @@ extern struct lsm_info __start_early_lsm_info[], __end_early_lsm_info[];
extern int lsm_inode_alloc(struct inode *inode);
extern struct lsm_static_calls_table static_calls_table __ro_after_init;
+#ifdef CONFIG_SECURITY
+
+int security_toggle_hook(void *addr, bool value);
+
+#else
+
+static inline int security_toggle_hook(void *addr, bool value)
+{
+ return -EINVAL;
+}
+
+#endif /* CONFIG_SECURITY */
+
#endif /* ! __LINUX_LSM_HOOKS_H */
diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
index db7599c59c78..5758c5681023 100644
--- a/kernel/bpf/trampoline.c
+++ b/kernel/bpf/trampoline.c
@@ -521,6 +521,21 @@ static enum bpf_tramp_prog_type bpf_attach_type_to_tramp(struct bpf_prog *prog)
}
}
+static int bpf_trampoline_toggle_lsm(struct bpf_trampoline *tr,
+ enum bpf_tramp_prog_type kind)
+{
+ struct bpf_tramp_link *link;
+ bool found = false;
+
+ hlist_for_each_entry(link, &tr->progs_hlist[kind], tramp_hlist) {
+ if (link->link.prog->type == BPF_PROG_TYPE_LSM) {
+ found = true;
+ break;
+ }
+ }
+ return security_toggle_hook(tr->func.addr, found);
+}
+
static int __bpf_trampoline_link_prog(struct bpf_tramp_link *link, struct bpf_trampoline *tr)
{
enum bpf_tramp_prog_type kind;
@@ -560,11 +575,22 @@ static int __bpf_trampoline_link_prog(struct bpf_tramp_link *link, struct bpf_tr
hlist_add_head(&link->tramp_hlist, &tr->progs_hlist[kind]);
tr->progs_cnt[kind]++;
- err = bpf_trampoline_update(tr, true /* lock_direct_mutex */);
- if (err) {
- hlist_del_init(&link->tramp_hlist);
- tr->progs_cnt[kind]--;
+
+ if (link->link.prog->type == BPF_PROG_TYPE_LSM) {
+ err = bpf_trampoline_toggle_lsm(tr, kind);
+ if (err)
+ goto cleanup;
}
+
+ err = bpf_trampoline_update(tr, true /* lock_direct_mutex */);
+ if (err)
+ goto cleanup;
+
+ return 0;
+
+cleanup:
+ hlist_del_init(&link->tramp_hlist);
+ tr->progs_cnt[kind]--;
return err;
}
@@ -593,6 +619,12 @@ static int __bpf_trampoline_unlink_prog(struct bpf_tramp_link *link, struct bpf_
}
hlist_del_init(&link->tramp_hlist);
tr->progs_cnt[kind]--;
+
+ if (link->link.prog->type == BPF_PROG_TYPE_LSM) {
+ err = bpf_trampoline_toggle_lsm(tr, kind);
+ WARN(err, "BUG: unable to toggle BPF LSM hook");
+ }
+
return bpf_trampoline_update(tr, true /* lock_direct_mutex */);
}
diff --git a/security/bpf/hooks.c b/security/bpf/hooks.c
index 57b9ffd53c98..8452e0835f56 100644
--- a/security/bpf/hooks.c
+++ b/security/bpf/hooks.c
@@ -9,7 +9,7 @@
static struct security_hook_list bpf_lsm_hooks[] __ro_after_init = {
#define LSM_HOOK(RET, DEFAULT, NAME, ...) \
- LSM_HOOK_INIT(NAME, bpf_lsm_##NAME),
+ LSM_HOOK_INIT_RUNTIME(NAME, bpf_lsm_##NAME),
#include <linux/lsm_hook_defs.h>
#undef LSM_HOOK
LSM_HOOK_INIT(inode_free_security, bpf_inode_storage_free),
diff --git a/security/security.c b/security/security.c
index 9654ca074aed..2f8bcacf1fb4 100644
--- a/security/security.c
+++ b/security/security.c
@@ -407,7 +407,9 @@ static void __init lsm_static_call_init(struct security_hook_list *hl)
__static_call_update(scall->key, scall->trampoline,
hl->hook.lsm_func_addr);
scall->hl = hl;
- static_branch_enable(scall->active);
+ /* Runtime hooks are inactive by default */
+ if (!hl->runtime)
+ static_branch_enable(scall->active);
return;
}
scall++;
@@ -885,6 +887,37 @@ int lsm_fill_user_ctx(struct lsm_ctx __user *uctx, u32 *uctx_len,
return rc;
}
+/**
+ * security_toggle_hook - Toggle the state of the LSM hook.
+ * @hook_addr: The address of the hook to be toggled.
+ * @state: Whether to enable for disable the hook.
+ *
+ * Returns 0 on success, -EINVAL if the address is not found.
+ */
+int security_toggle_hook(void *hook_addr, bool state)
+{
+ struct lsm_static_call *scalls = ((void *)&static_calls_table);
+ unsigned long num_entries =
+ (sizeof(static_calls_table) / sizeof(struct lsm_static_call));
+ int i;
+
+ for (i = 0; i < num_entries; i++) {
+
+ if (!scalls[i].hl || !scalls[i].hl->runtime)
+ continue;
+
+ if (scalls[i].hl->hook.lsm_func_addr != hook_addr)
+ continue;
+
+ if (state)
+ static_branch_enable(scalls[i].active);
+ else
+ static_branch_disable(scalls[i].active);
+ return 0;
+ }
+ return -EINVAL;
+}
+
/*
* The default value of the LSM hook is defined in linux/lsm_hook_defs.h and
* can be accessed with:
--
2.45.0.rc1.225.g2a3ae87e7f-goog
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH v12 1/5] kernel: Add helper macros for loop unrolling
2024-05-16 0:35 ` [PATCH v12 1/5] kernel: Add helper macros for loop unrolling KP Singh
@ 2024-05-17 8:03 ` John Johansen
0 siblings, 0 replies; 18+ messages in thread
From: John Johansen @ 2024-05-17 8:03 UTC (permalink / raw)
To: KP Singh, linux-security-module, bpf
Cc: ast, paul, casey, andrii, keescook, daniel, renauld, revest, song
On 5/15/24 17:35, KP Singh wrote:
> This helps in easily initializing blocks of code (e.g. static calls and
> keys).
>
> UNROLL(N, MACRO, __VA_ARGS__) calls MACRO N times with the first
> argument as the index of the iteration. This allows string pasting to
> create unique tokens for variable names, function calls etc.
>
> As an example:
>
> #include <linux/unroll.h>
>
> #define MACRO(N, a, b) \
> int add_##N(int a, int b) \
> { \
> return a + b + N; \
> }
>
> UNROLL(2, MACRO, x, y)
>
> expands to:
>
> int add_0(int x, int y)
> {
> return x + y + 0;
> }
>
> int add_1(int x, int y)
> {
> return x + y + 1;
> }
>
> Reviewed-by: Kees Cook <keescook@chromium.org>
> Reviewed-by: Casey Schaufler <casey@schaufler-ca.com>
> Acked-by: Song Liu <song@kernel.org>
> Acked-by: Andrii Nakryiko <andrii@kernel.org>
> Signed-off-by: KP Singh <kpsingh@kernel.org>
looks good
Reviewed-by: John Johansen <john.johansen@canonical.com>
> ---
> include/linux/unroll.h | 36 ++++++++++++++++++++++++++++++++++++
> 1 file changed, 36 insertions(+)
> create mode 100644 include/linux/unroll.h
>
> diff --git a/include/linux/unroll.h b/include/linux/unroll.h
> new file mode 100644
> index 000000000000..d42fd6366373
> --- /dev/null
> +++ b/include/linux/unroll.h
> @@ -0,0 +1,36 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +
> +/*
> + * Copyright (C) 2023 Google LLC.
> + */
> +
> +#ifndef __UNROLL_H
> +#define __UNROLL_H
> +
> +#include <linux/args.h>
> +
> +#define UNROLL(N, MACRO, args...) CONCATENATE(__UNROLL_, N)(MACRO, args)
> +
> +#define __UNROLL_0(MACRO, args...)
> +#define __UNROLL_1(MACRO, args...) __UNROLL_0(MACRO, args) MACRO(0, args)
> +#define __UNROLL_2(MACRO, args...) __UNROLL_1(MACRO, args) MACRO(1, args)
> +#define __UNROLL_3(MACRO, args...) __UNROLL_2(MACRO, args) MACRO(2, args)
> +#define __UNROLL_4(MACRO, args...) __UNROLL_3(MACRO, args) MACRO(3, args)
> +#define __UNROLL_5(MACRO, args...) __UNROLL_4(MACRO, args) MACRO(4, args)
> +#define __UNROLL_6(MACRO, args...) __UNROLL_5(MACRO, args) MACRO(5, args)
> +#define __UNROLL_7(MACRO, args...) __UNROLL_6(MACRO, args) MACRO(6, args)
> +#define __UNROLL_8(MACRO, args...) __UNROLL_7(MACRO, args) MACRO(7, args)
> +#define __UNROLL_9(MACRO, args...) __UNROLL_8(MACRO, args) MACRO(8, args)
> +#define __UNROLL_10(MACRO, args...) __UNROLL_9(MACRO, args) MACRO(9, args)
> +#define __UNROLL_11(MACRO, args...) __UNROLL_10(MACRO, args) MACRO(10, args)
> +#define __UNROLL_12(MACRO, args...) __UNROLL_11(MACRO, args) MACRO(11, args)
> +#define __UNROLL_13(MACRO, args...) __UNROLL_12(MACRO, args) MACRO(12, args)
> +#define __UNROLL_14(MACRO, args...) __UNROLL_13(MACRO, args) MACRO(13, args)
> +#define __UNROLL_15(MACRO, args...) __UNROLL_14(MACRO, args) MACRO(14, args)
> +#define __UNROLL_16(MACRO, args...) __UNROLL_15(MACRO, args) MACRO(15, args)
> +#define __UNROLL_17(MACRO, args...) __UNROLL_16(MACRO, args) MACRO(16, args)
> +#define __UNROLL_18(MACRO, args...) __UNROLL_17(MACRO, args) MACRO(17, args)
> +#define __UNROLL_19(MACRO, args...) __UNROLL_18(MACRO, args) MACRO(18, args)
> +#define __UNROLL_20(MACRO, args...) __UNROLL_19(MACRO, args) MACRO(19, args)
> +
> +#endif /* __UNROLL_H */
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v12 2/5] security: Count the LSMs enabled at compile time
2024-05-16 0:35 ` [PATCH v12 2/5] security: Count the LSMs enabled at compile time KP Singh
@ 2024-05-17 8:09 ` John Johansen
0 siblings, 0 replies; 18+ messages in thread
From: John Johansen @ 2024-05-17 8:09 UTC (permalink / raw)
To: KP Singh, linux-security-module, bpf
Cc: ast, paul, casey, andrii, keescook, daniel, renauld, revest, song,
Kui-Feng Lee
On 5/15/24 17:35, KP Singh wrote:
> These macros are a clever trick to determine a count of the number of
> LSMs that are enabled in the config to ascertain the maximum number of
> static calls that need to be configured per LSM hook.
>
> Without this one would need to generate static calls for the total
> number of LSMs in the kernel (even if they are not compiled) times the
> number of LSM hooks which ends up being quite wasteful.
>
> Suggested-by: Kui-Feng Lee <sinquersw@gmail.com>
> Suggested-by: Andrii Nakryiko <andrii@kernel.org>
> Acked-by: Song Liu <song@kernel.org>
> Acked-by: Andrii Nakryiko <andrii@kernel.org>
> Reviewed-by: Kees Cook <keescook@chromium.org>
> Reviewed-by: Casey Schaufler <casey@schaufler-ca.com>
> Signed-off-by: KP Singh <kpsingh@kernel.org>
looks good
Reviewed-by: John Johansen <john.johansen@canonical.com>
> ---
> include/linux/args.h | 6 +-
> include/linux/lsm_count.h | 128 ++++++++++++++++++++++++++++++++++++++
> 2 files changed, 131 insertions(+), 3 deletions(-)
> create mode 100644 include/linux/lsm_count.h
>
> diff --git a/include/linux/args.h b/include/linux/args.h
> index 8ff60a54eb7d..2e8e65d975c7 100644
> --- a/include/linux/args.h
> +++ b/include/linux/args.h
> @@ -17,9 +17,9 @@
> * that as _n.
> */
>
> -/* This counts to 12. Any more, it will return 13th argument. */
> -#define __COUNT_ARGS(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _n, X...) _n
> -#define COUNT_ARGS(X...) __COUNT_ARGS(, ##X, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
> +/* This counts to 15. Any more, it will return 16th argument. */
> +#define __COUNT_ARGS(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _n, X...) _n
> +#define COUNT_ARGS(X...) __COUNT_ARGS(, ##X, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
>
> /* Concatenate two parameters, but allow them to be expanded beforehand. */
> #define __CONCAT(a, b) a ## b
> diff --git a/include/linux/lsm_count.h b/include/linux/lsm_count.h
> new file mode 100644
> index 000000000000..73c7cc81349b
> --- /dev/null
> +++ b/include/linux/lsm_count.h
> @@ -0,0 +1,128 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +
> +/*
> + * Copyright (C) 2023 Google LLC.
> + */
> +
> +#ifndef __LINUX_LSM_COUNT_H
> +#define __LINUX_LSM_COUNT_H
> +
> +#include <linux/args.h>
> +
> +#ifdef CONFIG_SECURITY
> +
> +/*
> + * Macros to count the number of LSMs enabled in the kernel at compile time.
> + */
> +
> +/*
> + * Capabilities is enabled when CONFIG_SECURITY is enabled.
> + */
> +#if IS_ENABLED(CONFIG_SECURITY)
> +#define CAPABILITIES_ENABLED 1,
> +#else
> +#define CAPABILITIES_ENABLED
> +#endif
> +
> +#if IS_ENABLED(CONFIG_SECURITY_SELINUX)
> +#define SELINUX_ENABLED 1,
> +#else
> +#define SELINUX_ENABLED
> +#endif
> +
> +#if IS_ENABLED(CONFIG_SECURITY_SMACK)
> +#define SMACK_ENABLED 1,
> +#else
> +#define SMACK_ENABLED
> +#endif
> +
> +#if IS_ENABLED(CONFIG_SECURITY_APPARMOR)
> +#define APPARMOR_ENABLED 1,
> +#else
> +#define APPARMOR_ENABLED
> +#endif
> +
> +#if IS_ENABLED(CONFIG_SECURITY_TOMOYO)
> +#define TOMOYO_ENABLED 1,
> +#else
> +#define TOMOYO_ENABLED
> +#endif
> +
> +#if IS_ENABLED(CONFIG_SECURITY_YAMA)
> +#define YAMA_ENABLED 1,
> +#else
> +#define YAMA_ENABLED
> +#endif
> +
> +#if IS_ENABLED(CONFIG_SECURITY_LOADPIN)
> +#define LOADPIN_ENABLED 1,
> +#else
> +#define LOADPIN_ENABLED
> +#endif
> +
> +#if IS_ENABLED(CONFIG_SECURITY_LOCKDOWN_LSM)
> +#define LOCKDOWN_ENABLED 1,
> +#else
> +#define LOCKDOWN_ENABLED
> +#endif
> +
> +#if IS_ENABLED(CONFIG_SECURITY_SAFESETID)
> +#define SAFESETID_ENABLED 1,
> +#else
> +#define SAFESETID_ENABLED
> +#endif
> +
> +#if IS_ENABLED(CONFIG_BPF_LSM)
> +#define BPF_LSM_ENABLED 1,
> +#else
> +#define BPF_LSM_ENABLED
> +#endif
> +
> +#if IS_ENABLED(CONFIG_SECURITY_LANDLOCK)
> +#define LANDLOCK_ENABLED 1,
> +#else
> +#define LANDLOCK_ENABLED
> +#endif
> +
> +#if IS_ENABLED(CONFIG_IMA)
> +#define IMA_ENABLED 1,
> +#else
> +#define IMA_ENABLED
> +#endif
> +
> +#if IS_ENABLED(CONFIG_EVM)
> +#define EVM_ENABLED 1,
> +#else
> +#define EVM_ENABLED
> +#endif
> +
> +/*
> + * There is a trailing comma that we need to be accounted for. This is done by
> + * using a skipped argument in __COUNT_LSMS
> + */
> +#define __COUNT_LSMS(skipped_arg, args...) COUNT_ARGS(args...)
> +#define COUNT_LSMS(args...) __COUNT_LSMS(args)
> +
> +#define MAX_LSM_COUNT \
> + COUNT_LSMS( \
> + CAPABILITIES_ENABLED \
> + SELINUX_ENABLED \
> + SMACK_ENABLED \
> + APPARMOR_ENABLED \
> + TOMOYO_ENABLED \
> + YAMA_ENABLED \
> + LOADPIN_ENABLED \
> + LOCKDOWN_ENABLED \
> + SAFESETID_ENABLED \
> + BPF_LSM_ENABLED \
> + LANDLOCK_ENABLED \
> + IMA_ENABLED \
> + EVM_ENABLED)
> +
> +#else
> +
> +#define MAX_LSM_COUNT 0
> +
> +#endif /* CONFIG_SECURITY */
> +
> +#endif /* __LINUX_LSM_COUNT_H */
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v12 0/5] Reduce overhead of LSMs with static calls
2024-05-16 0:35 [PATCH v12 0/5] Reduce overhead of LSMs with static calls KP Singh
` (4 preceding siblings ...)
2024-05-16 0:35 ` [PATCH v12 5/5] bpf: Only enable BPF LSM hooks when an LSM program is attached KP Singh
@ 2024-05-18 6:01 ` Tetsuo Handa
2024-06-06 15:58 ` Kees Cook
6 siblings, 0 replies; 18+ messages in thread
From: Tetsuo Handa @ 2024-05-18 6:01 UTC (permalink / raw)
To: KP Singh, linux-security-module, bpf
Cc: ast, paul, casey, andrii, keescook, daniel, renauld, revest, song
On 2024/05/16 9:35, KP Singh wrote:
> Since we know the address of the enabled LSM callbacks at compile time and only
> the order is determined at boot time, the LSM framework can allocate static
> calls for each of the possible LSM callbacks and these calls can be updated once
> the order is determined at boot.
I don't like this assumption. None of built-in LSMs is used by (or affordable for)
my customers. There is a reality that only out-of-tree security modules which the
distributor (namely, Red Hat) cannot support (and therefore cannot be built into
RHEL kernels) are used by (or affordable for) such customers.
Therefore, without giving room for allowing such security modules to load after
boot, I consider this change a regression.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v12 0/5] Reduce overhead of LSMs with static calls
2024-05-16 0:35 [PATCH v12 0/5] Reduce overhead of LSMs with static calls KP Singh
` (5 preceding siblings ...)
2024-05-18 6:01 ` [PATCH v12 0/5] Reduce overhead of LSMs with static calls Tetsuo Handa
@ 2024-06-06 15:58 ` Kees Cook
2024-06-06 16:36 ` Paul Moore
6 siblings, 1 reply; 18+ messages in thread
From: Kees Cook @ 2024-06-06 15:58 UTC (permalink / raw)
To: KP Singh, paul
Cc: linux-security-module, bpf, ast, casey, andrii, daniel, renauld,
revest, song
On Thu, May 16, 2024 at 02:35:19AM +0200, KP Singh wrote:
> This series is a respin of the RFC proposed by Paul Renauld (renauld@google.com)
> and Brendan Jackman (jackmanb@google.com) [1]
>
> # Performance improvement
>
> With this patch-set some syscalls with lots of LSM hooks in their path
> benefitted at an average of ~3% and I/O and Pipe based system calls benefitting
> the most.
Hi Paul,
With the merge window closed now, can we please land this in -next so we
can get any glitches found/hammered out with maximal time before the
next merge window?
-Kees
--
Kees Cook
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v12 0/5] Reduce overhead of LSMs with static calls
2024-06-06 15:58 ` Kees Cook
@ 2024-06-06 16:36 ` Paul Moore
2024-06-06 18:07 ` Kees Cook
0 siblings, 1 reply; 18+ messages in thread
From: Paul Moore @ 2024-06-06 16:36 UTC (permalink / raw)
To: Kees Cook
Cc: KP Singh, linux-security-module, bpf, ast, casey, andrii, daniel,
renauld, revest, song
On Thu, Jun 6, 2024 at 11:58 AM Kees Cook <kees@kernel.org> wrote:
> On Thu, May 16, 2024 at 02:35:19AM +0200, KP Singh wrote:
> > This series is a respin of the RFC proposed by Paul Renauld (renauld@google.com)
> > and Brendan Jackman (jackmanb@google.com) [1]
> >
> > # Performance improvement
> >
> > With this patch-set some syscalls with lots of LSM hooks in their path
> > benefitted at an average of ~3% and I/O and Pipe based system calls benefitting
> > the most.
>
> Hi Paul,
>
> With the merge window closed now, can we please land this in -next so we
> can get any glitches found/hammered out with maximal time before the
> next merge window?
It's in the queue, I've been swamped lately as you'll likely notice I
haven't really had a chance to process patches yet during this cycle.
We've also been over this soo many times that I've lost count - you
know this is on my list, I've mentioned it multiple times that this is
now high on the todo list, and your continued nagging does nothing
other than make me want to look at other patches first. You're not
helping your cause Kees.
--
paul-moore.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v12 0/5] Reduce overhead of LSMs with static calls
2024-06-06 16:36 ` Paul Moore
@ 2024-06-06 18:07 ` Kees Cook
2024-06-06 20:07 ` Paul Moore
0 siblings, 1 reply; 18+ messages in thread
From: Kees Cook @ 2024-06-06 18:07 UTC (permalink / raw)
To: Paul Moore
Cc: KP Singh, linux-security-module, bpf, ast, casey, andrii, daniel,
renauld, revest, song
On Thu, Jun 06, 2024 at 12:36:03PM -0400, Paul Moore wrote:
> It's in the queue, I've been swamped lately as you'll likely notice I
> haven't really had a chance to process patches yet during this cycle.
I get that you're busy, and I understand that situation -- I get swamped
too. The latency on LSM patch review has been very high lately, and I'm
hoping there's some way we could help. I assume other folks could jump
in and help with the queue[1], but I'm not sure if that would satisfy
your requirements? Other subsystems have been switching more and more to
a group maintainership system, etc. Are there other people you'd be
comfortable sharing the load with? (Currently James and Serge are also
listed as "M:" in MAINTAINERS, but I don't think the 3 of you have a
shared git.kernel.org tree, for example...)
And yes, there are a lot of patches up for review. I'm antsy about this
series in particular because I'm worried inaction is going to create
larger problems for the LSM as a whole. We've already had Linus drop
in and tell us to do better; I'd really like to avoid having him make
unilateral changes to the LSM, especially when we have a solution on
deck that has been reviewed by many people.
I will go back to being anxious/patient... ;)
-Kees
[1] https://patchwork.kernel.org/project/linux-security-module/list/
--
Kees Cook
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v12 0/5] Reduce overhead of LSMs with static calls
2024-06-06 18:07 ` Kees Cook
@ 2024-06-06 20:07 ` Paul Moore
0 siblings, 0 replies; 18+ messages in thread
From: Paul Moore @ 2024-06-06 20:07 UTC (permalink / raw)
To: Kees Cook
Cc: KP Singh, linux-security-module, bpf, ast, casey, andrii, daniel,
renauld, revest, song
On Thu, Jun 6, 2024 at 2:07 PM Kees Cook <kees@kernel.org> wrote:
> On Thu, Jun 06, 2024 at 12:36:03PM -0400, Paul Moore wrote:
> > It's in the queue, I've been swamped lately as you'll likely notice I
> > haven't really had a chance to process patches yet during this cycle.
>
> I get that you're busy, and I understand that situation -- I get swamped
> too. The latency on LSM patch review has been very high lately, and I'm
> hoping there's some way we could help. I assume other folks could jump
> in and help with the queue[1], but I'm not sure if that would satisfy
> your requirements? Other subsystems have been switching more and more to
> a group maintainership system, etc. Are there other people you'd be
> comfortable sharing the load with? (Currently James and Serge are also
> listed as "M:" in MAINTAINERS, but I don't think the 3 of you have a
> shared git.kernel.org tree, for example...)
We already have a fairly distributed model with each LSM sending
directly to Linus; the issue we are seeing now is an odd combination
of multiple things: 1) $DAYJOB has had a sudden explosion of high
priority internal work which has siphoned away a good chunk of my time
2) we have seen an historically unusual amount of development at the
LSM layer itself (as opposed to the individual LSMs) 3) we had a long
holiday weekend here in the US and I decided to do what normal people
do and not spend all weekend arguing with people on the Internet.
Without going into details on #1, let's just say it is transient and
not something I expect to be a long term issue (if it does become a
long term issue I'll start looking for a new $DAYJOB). I suspect
issue #2 is a byproduct of some of the efforts around reinvigorating
LSM development, clearing up some long standing warts, etc. and also
not something I expect to last for an extended period of time. Issue
#3 is a direct result of ... well, far too many threads like this,
both from well and poorly intentioned authors.
As an aside, things like this typically work better if you have
another email setup so you can do the good-cop/bad-cop bit more
convincingly.
> And yes, there are a lot of patches up for review. I'm antsy about this
> series in particular because I'm worried inaction is going to create
> larger problems for the LSM as a whole. We've already had Linus drop
> in and tell us to do better; I'd really like to avoid having him make
> unilateral changes to the LSM, especially when we have a solution on
> deck that has been reviewed by many people.
You'll note that I'm one of those "many people" that has reviewed it
(and had my feedback ignored for several rounds). I simply haven't
had the chance to review the latest revision.
In my opinion the LSM's largest issue has nothing to do with code, and
everything to do with the mentality that a hardware flaw is a LSM bug.
If that same mentality wants to ignore decades of work, in a construct
it insisted on, and break the user experience for billions of users
(and entire usage scenarios) in order to satisfy some misdirected
need, then I seriously doubt one patchset is going to solve anything.
> I will go back to being anxious/patient... ;)
"do better" ;)
--
paul-moore.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v12 5/5] bpf: Only enable BPF LSM hooks when an LSM program is attached
2024-05-16 0:35 ` [PATCH v12 5/5] bpf: Only enable BPF LSM hooks when an LSM program is attached KP Singh
@ 2024-06-11 1:05 ` Paul Moore
2024-06-29 8:13 ` KP Singh
0 siblings, 1 reply; 18+ messages in thread
From: Paul Moore @ 2024-06-11 1:05 UTC (permalink / raw)
To: KP Singh, linux-security-module, bpf
Cc: ast, casey, andrii, keescook, daniel, renauld, revest, song,
KP Singh
On May 15, 2024 KP Singh <kpsingh@kernel.org> wrote:
>
> BPF LSM hooks have side-effects (even when a default value's returned)
> as some hooks end up behaving differently due to the very presence of
> the hook.
>
> The static keys guarding the BPF LSM hooks are disabled by default and
> enabled only when a BPF program is attached implementing the hook
> logic. This avoids the issue of the side-effects and also the minor
> overhead associated with the empty callback.
>
> security_file_ioctl:
> 0xff...0e30 <+0>: endbr64
> 0xff...0e34 <+4>: nopl 0x0(%rax,%rax,1)
> 0xff...0e39 <+9>: push %rbp
> 0xff...0e3a <+10>: push %r14
> 0xff...0e3c <+12>: push %rbx
> 0xff...0e3d <+13>: mov %rdx,%rbx
> 0xff...0e40 <+16>: mov %esi,%ebp
> 0xff...0e42 <+18>: mov %rdi,%r14
> 0xff...0e45 <+21>: jmp 0xff...0e57 <security_file_ioctl+39>
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>
> Static key enabled for SELinux
>
> 0xff...0e47 <+23>: xchg %ax,%ax
> ^^^^^^^^^^^^^^
>
> Static key disabled for BPF. This gets patched when a BPF LSM
> program is attached
>
> 0xff...0e49 <+25>: xor %eax,%eax
> 0xff...0e4b <+27>: xchg %ax,%ax
> 0xff...0e4d <+29>: pop %rbx
> 0xff...0e4e <+30>: pop %r14
> 0xff...0e50 <+32>: pop %rbp
> 0xff...0e51 <+33>: cs jmp 0xff...0000 <__x86_return_thunk>
> 0xff...0e57 <+39>: endbr64
> 0xff...0e5b <+43>: mov %r14,%rdi
> 0xff...0e5e <+46>: mov %ebp,%esi
> 0xff...0e60 <+48>: mov %rbx,%rdx
> 0xff...0e63 <+51>: call 0xff...33c0 <selinux_file_ioctl>
> 0xff...0e68 <+56>: test %eax,%eax
> 0xff...0e6a <+58>: jne 0xff...0e4d <security_file_ioctl+29>
> 0xff...0e6c <+60>: jmp 0xff...0e47 <security_file_ioctl+23>
> 0xff...0e6e <+62>: endbr64
> 0xff...0e72 <+66>: mov %r14,%rdi
> 0xff...0e75 <+69>: mov %ebp,%esi
> 0xff...0e77 <+71>: mov %rbx,%rdx
> 0xff...0e7a <+74>: call 0xff...e3b0 <bpf_lsm_file_ioctl>
> 0xff...0e7f <+79>: test %eax,%eax
> 0xff...0e81 <+81>: jne 0xff...0e4d <security_file_ioctl+29>
> 0xff...0e83 <+83>: jmp 0xff...0e49 <security_file_ioctl+25>
> 0xff...0e85 <+85>: endbr64
> 0xff...0e89 <+89>: mov %r14,%rdi
> 0xff...0e8c <+92>: mov %ebp,%esi
> 0xff...0e8e <+94>: mov %rbx,%rdx
> 0xff...0e91 <+97>: pop %rbx
> 0xff...0e92 <+98>: pop %r14
> 0xff...0e94 <+100>: pop %rbp
> 0xff...0e95 <+101>: ret
>
> This patch enables this by providing a LSM_HOOK_INIT_RUNTIME variant
> that allows the LSMs to opt-in to hooks which can be toggled at runtime
> which with security_toogle_hook.
>
> Reviewed-by: Kees Cook <keescook@chromium.org>
> Acked-by: Casey Schaufler <casey@schaufler-ca.com>
> Signed-off-by: KP Singh <kpsingh@kernel.org>
> ---
> include/linux/lsm_hooks.h | 30 ++++++++++++++++++++++++++++-
> kernel/bpf/trampoline.c | 40 +++++++++++++++++++++++++++++++++++----
> security/bpf/hooks.c | 2 +-
> security/security.c | 35 +++++++++++++++++++++++++++++++++-
> 4 files changed, 100 insertions(+), 7 deletions(-)
...
> diff --git a/security/security.c b/security/security.c
> index 9654ca074aed..2f8bcacf1fb4 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -885,6 +887,37 @@ int lsm_fill_user_ctx(struct lsm_ctx __user *uctx, u32 *uctx_len,
> return rc;
> }
>
> +/**
> + * security_toggle_hook - Toggle the state of the LSM hook.
> + * @hook_addr: The address of the hook to be toggled.
> + * @state: Whether to enable for disable the hook.
> + *
> + * Returns 0 on success, -EINVAL if the address is not found.
> + */
> +int security_toggle_hook(void *hook_addr, bool state)
> +{
> + struct lsm_static_call *scalls = ((void *)&static_calls_table);
GCC (v14.1.1 if that matters) is complaining about casting randomized
structs. Looking quickly at the two structs, lsm_static_call and
lsm_static_calls_table, I suspect the cast is harmless even if the
randstruct case, but I would like to see some sort of fix for this so
I don't get spammed by GCC every time I do a build. On the other hand,
if this cast really is a problem in the randstruct case we obviously
need to fix that.
Either way, resolve this and make sure you test with GCC/randstruct
enabled.
> + unsigned long num_entries =
> + (sizeof(static_calls_table) / sizeof(struct lsm_static_call));
> + int i;
> +
> + for (i = 0; i < num_entries; i++) {
> +
> + if (!scalls[i].hl || !scalls[i].hl->runtime)
> + continue;
> +
> + if (scalls[i].hl->hook.lsm_func_addr != hook_addr)
> + continue;
> +
> + if (state)
> + static_branch_enable(scalls[i].active);
> + else
> + static_branch_disable(scalls[i].active);
> + return 0;
> + }
> + return -EINVAL;
> +}
> +
> /*
> * The default value of the LSM hook is defined in linux/lsm_hook_defs.h and
> * can be accessed with:
> --
> 2.45.0.rc1.225.g2a3ae87e7f-goog
--
paul-moore.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v12 3/5] security: Replace indirect LSM hook calls with static calls
2024-05-16 0:35 ` [PATCH v12 3/5] security: Replace indirect LSM hook calls with static calls KP Singh
@ 2024-06-27 20:28 ` Jonathan Corbet
2024-06-29 8:28 ` KP Singh
0 siblings, 1 reply; 18+ messages in thread
From: Jonathan Corbet @ 2024-06-27 20:28 UTC (permalink / raw)
To: KP Singh, linux-security-module, bpf
Cc: ast, paul, casey, andrii, keescook, daniel, renauld, revest, song,
KP Singh
KP Singh <kpsingh@kernel.org> writes:
> 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:
I hate to bug you with a changelog nit, but this is the sort of thing
that might save others some work..
[...]
> 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].
I looked in vain for [1] to see what these bugs were. After sufficient
digging, I found that the relevant URL:
https://lore.kernel.org/linux-security-module/20220609234601.2026362-1-kpsingh@kernel.org/
was evidently dropped in v4 of the patch set last September, and nobody
evidently noticed. If there's a v13, I might humbly suggest putting it
back :)
Thanks,
jon
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v12 5/5] bpf: Only enable BPF LSM hooks when an LSM program is attached
2024-06-11 1:05 ` Paul Moore
@ 2024-06-29 8:13 ` KP Singh
2024-07-01 23:40 ` Paul Moore
0 siblings, 1 reply; 18+ messages in thread
From: KP Singh @ 2024-06-29 8:13 UTC (permalink / raw)
To: Paul Moore
Cc: linux-security-module, bpf, ast, casey, andrii, keescook, daniel,
renauld, revest, song
On Tue, Jun 11, 2024 at 6:35 AM Paul Moore <paul@paul-moore.com> wrote:
>
> On May 15, 2024 KP Singh <kpsingh@kernel.org> wrote:
> >
> >
[...]
> > +/**
> > + * security_toggle_hook - Toggle the state of the LSM hook.
> > + * @hook_addr: The address of the hook to be toggled.
> > + * @state: Whether to enable for disable the hook.
> > + *
> > + * Returns 0 on success, -EINVAL if the address is not found.
> > + */
> > +int security_toggle_hook(void *hook_addr, bool state)
> > +{
> > + struct lsm_static_call *scalls = ((void *)&static_calls_table);
>
> GCC (v14.1.1 if that matters) is complaining about casting randomized
> structs. Looking quickly at the two structs, lsm_static_call and
> lsm_static_calls_table, I suspect the cast is harmless even if the
> randstruct case, but I would like to see some sort of fix for this so
> I don't get spammed by GCC every time I do a build. On the other hand,
> if this cast really is a problem in the randstruct case we obviously
> need to fix that.
>
The cast is not a problem with rand struct, we are iterating through a
2 dimensional array and it does not matter in which order we iterate
the first dimension.
diff --git a/security/security.c b/security/security.c
index 2ee880b3a39a..4cc0e368d07f 100644
--- a/security/security.c
+++ b/security/security.c
@@ -899,23 +899,24 @@ int lsm_fill_user_ctx(struct lsm_ctx __user
*uctx, u32 *uctx_len,
*/
int security_toggle_hook(void *hook_addr, bool state)
{
- struct lsm_static_call *scalls = ((void *)&static_calls_table);
+ struct lsm_static_call *scall;
+ void *scalls_table = ((void *)&static_calls_table);
unsigned long num_entries =
(sizeof(static_calls_table) / sizeof(struct lsm_static_call));
int i;
for (i = 0; i < num_entries; i++) {
-
- if (!scalls[i].hl || !scalls[i].hl->runtime)
+ scall = scalls_table + (i * sizeof(struct lsm_static_call));
+ if (!scall->hl || !scall->hl->runtime)
continue;
- if (scalls[i].hl->hook.lsm_func_addr != hook_addr)
+ if (scall->hl->hook.lsm_func_addr != hook_addr)
continue;
if (state)
- static_branch_enable(scalls[i].active);
+ static_branch_enable(scall->active);
else
- static_branch_disable(scalls[i].active);
+ static_branch_disable(scall->active);
return 0;
}
return -EINVAL;
fixes the error. I will respin.
> Either way, resolve this and make sure you test with GCC/randstruct
> enabled.
>
> > + unsigned long num_entries =
> > + (sizeof(static_calls_table) / sizeof(struct lsm_static_call));
> > + int i;
> > +
> > + for (i = 0; i < num_entries; i++) {
> > +
> > + if (!scalls[i].hl || !scalls[i].hl->runtime)
> > + continue;
> > +
> > + if (scalls[i].hl->hook.lsm_func_addr != hook_addr)
> > + continue;
> > +
> > + if (state)
> > + static_branch_enable(scalls[i].active);
> > + else
> > + static_branch_disable(scalls[i].active);
> > + return 0;
> > + }
> > + return -EINVAL;
> > +}
> > +
> > /*
> > * The default value of the LSM hook is defined in linux/lsm_hook_defs.h and
> > * can be accessed with:
> > --
> > 2.45.0.rc1.225.g2a3ae87e7f-goog
>
> --
> paul-moore.com
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH v12 3/5] security: Replace indirect LSM hook calls with static calls
2024-06-27 20:28 ` Jonathan Corbet
@ 2024-06-29 8:28 ` KP Singh
0 siblings, 0 replies; 18+ messages in thread
From: KP Singh @ 2024-06-29 8:28 UTC (permalink / raw)
To: Jonathan Corbet
Cc: linux-security-module, bpf, ast, paul, casey, andrii, keescook,
daniel, renauld, revest, song
On Fri, Jun 28, 2024 at 1:58 AM Jonathan Corbet <corbet@lwn.net> wrote:
>
> KP Singh <kpsingh@kernel.org> writes:
>
> > 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:
>
> I hate to bug you with a changelog nit, but this is the sort of thing
> that might save others some work..
>
> [...]
>
> > 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].
>
> I looked in vain for [1] to see what these bugs were. After sufficient
> digging, I found that the relevant URL:
>
> https://lore.kernel.org/linux-security-module/20220609234601.2026362-1-kpsingh@kernel.org/
>
Thank you so much Jon, appreciate the attention to detail, I have
fixed it in v13.
- KP
> was evidently dropped in v4 of the patch set last September, and nobody
> evidently noticed. If there's a v13, I might humbly suggest putting it
> back :)
>
> Thanks,
>
> jon
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v12 5/5] bpf: Only enable BPF LSM hooks when an LSM program is attached
2024-06-29 8:13 ` KP Singh
@ 2024-07-01 23:40 ` Paul Moore
0 siblings, 0 replies; 18+ messages in thread
From: Paul Moore @ 2024-07-01 23:40 UTC (permalink / raw)
To: KP Singh
Cc: linux-security-module, bpf, ast, casey, andrii, keescook, daniel,
renauld, revest, song
On Sat, Jun 29, 2024 at 4:13 AM KP Singh <kpsingh@kernel.org> wrote:
> On Tue, Jun 11, 2024 at 6:35 AM Paul Moore <paul@paul-moore.com> wrote:
> > On May 15, 2024 KP Singh <kpsingh@kernel.org> wrote:
> > >
>
> [...]
>
> > > +/**
> > > + * security_toggle_hook - Toggle the state of the LSM hook.
> > > + * @hook_addr: The address of the hook to be toggled.
> > > + * @state: Whether to enable for disable the hook.
> > > + *
> > > + * Returns 0 on success, -EINVAL if the address is not found.
> > > + */
> > > +int security_toggle_hook(void *hook_addr, bool state)
> > > +{
> > > + struct lsm_static_call *scalls = ((void *)&static_calls_table);
> >
> > GCC (v14.1.1 if that matters) is complaining about casting randomized
> > structs. Looking quickly at the two structs, lsm_static_call and
> > lsm_static_calls_table, I suspect the cast is harmless even if the
> > randstruct case, but I would like to see some sort of fix for this so
> > I don't get spammed by GCC every time I do a build. On the other hand,
> > if this cast really is a problem in the randstruct case we obviously
> > need to fix that.
> >
>
> The cast is not a problem with rand struct, we are iterating through a
> 2 dimensional array and it does not matter in which order we iterate
> the first dimension.
That was my suspicion when I looked at it quickly after the gcc
complained, but if nothing else the compiler splat needed to be
resolved. Based on your comment it looks like you've fixed that in
v13, that's good. Please make sure to test with both gcc and clang in
the future.
In an effort to avoid the "merge this now!" shouts and any other
attempted maintainer manipulations using Linus' email as
justification, I want to make it clear that the earliest the v13 would
possibly be merged is after the upcoming merge window. See the
"Kernel Source Branches and Development Process" in the LSM guidance
document below:
https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm.git/tree/README.md
--
paul-moore.com
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2024-07-01 23:41 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-05-16 0:35 [PATCH v12 0/5] Reduce overhead of LSMs with static calls KP Singh
2024-05-16 0:35 ` [PATCH v12 1/5] kernel: Add helper macros for loop unrolling KP Singh
2024-05-17 8:03 ` John Johansen
2024-05-16 0:35 ` [PATCH v12 2/5] security: Count the LSMs enabled at compile time KP Singh
2024-05-17 8:09 ` John Johansen
2024-05-16 0:35 ` [PATCH v12 3/5] security: Replace indirect LSM hook calls with static calls KP Singh
2024-06-27 20:28 ` Jonathan Corbet
2024-06-29 8:28 ` KP Singh
2024-05-16 0:35 ` [PATCH v12 4/5] security: Update non standard hooks to use " KP Singh
2024-05-16 0:35 ` [PATCH v12 5/5] bpf: Only enable BPF LSM hooks when an LSM program is attached KP Singh
2024-06-11 1:05 ` Paul Moore
2024-06-29 8:13 ` KP Singh
2024-07-01 23:40 ` Paul Moore
2024-05-18 6:01 ` [PATCH v12 0/5] Reduce overhead of LSMs with static calls Tetsuo Handa
2024-06-06 15:58 ` Kees Cook
2024-06-06 16:36 ` Paul Moore
2024-06-06 18:07 ` Kees Cook
2024-06-06 20:07 ` Paul Moore
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).