2020-10-15 12:04:43

by Aleksandr Nogikh

[permalink] [raw]
Subject: [RFC PATCH 0/1] security: add fault injection to LSM hooks

From: Aleksandr Nogikh <[email protected]>

Fault injection capabilities[Documentation/fault-injection/fault-injection.rst]
facilitate testing of the stability of the Linux kernel by providing
means to force a number of kernel interfaces to return error
codes. This RFC proposes adding such fault injection capability into
LSM hooks.

The intent is to make it possible to test whether the existing kernel
code properly handles negative return values of LSM hooks. Syzbot
[https://github.com/google/syzkaller/blob/master/docs/syzbot.md] will
automatically do that with the aid of instrumentation tools once these
changes are merged.

Is the attached implementation consistent with the ideas behind LSM
stacking in its current state? In particular, is it reasonable to
expect the existing LSMs to operate normally when they are active and
such fault injection happens?

Local fuzzing of a Linux kernel with this patch has almost instantly
led to two crashes. I'm not sure whether they correspond to actual
issues as the LSM fault injection implementation (and the concept
itself) can be wrong. Here they are:

1. "general protection fault in selinux_inode_free_security". This is
caused by executing security_inode_free() when a fault was injected to
inode_alloc_security() and therefore selinux_inode_alloc_security()
was not executed. In this case, the subsequent inode_free_security()
call executes list_del_init() on an uninitialized list. Theoretically,
this may happen if some other LSM precedes selinux in the hooks list
and its inode_alloc_security hook fails.

A fault was injected to this call_int_hook():
https://elixir.bootlin.com/linux/v5.9/source/security/security.c#L975

Below you can find a call trace for the subsequent crash.
__list_del_entry include/linux/list.h:132 [inline]
list_del_init include/linux/list.h:204 [inline]
inode_free_security security/selinux/hooks.c:337 [inline]
selinux_inode_free_security+0xf0/0x290 security/selinux/hooks.c:2839
security_inode_free+0x46/0xc0 security/security.c:1042
security_inode_alloc+0x161/0x1a0 security/security.c:1027
inode_init_always+0x5a7/0xd10 fs/inode.c:171
alloc_inode+0x82/0x230 fs/inode.c:239
new_inode_pseudo+0x14/0xe0 fs/inode.c:928
sock_alloc+0x3c/0x260 net/socket.c:573
__sock_create+0xb9/0x780 net/socket.c:1391
sock_create net/socket.c:1478 [inline]
__sys_socket+0xef/0x200 net/socket.c:1520
__do_sys_socket net/socket.c:1529 [inline]
__se_sys_socket net/socket.c:1527 [inline]
__x64_sys_socket+0x6f/0xb0 net/socket.c:1527
do_syscall_64+0x2d/0x70 arch/x86/entry/common.c:46
entry_SYSCALL_64_after_hwframe+0x44/0xa9

2. BUG_ON inside security_skb_classify_flow(). Why is it needed there?
https://elixir.bootlin.com/linux/v5.9/source/security/security.c#L2426

Aleksandr Nogikh (1):
security: add fault injection capability

lib/Kconfig.debug | 6 +++++
security/security.c | 53 ++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 56 insertions(+), 3 deletions(-)


base-commit: f2fb1afc57304f9dd68c20a08270e287470af2eb
--
2.28.0.1011.ga647a8990f-goog


2020-10-15 17:59:03

by Aleksandr Nogikh

[permalink] [raw]
Subject: [RFC PATCH 1/1] security: add fault injection capability

From: Aleksandr Nogikh <[email protected]>

Add a fault injection capability to call_int_hook macro. This will
facilitate testing of fault tolerance of the code that invokes
security hooks as well as the fault tolerance of the LSM
implementations themselves.

Add a KConfig option (CONFIG_FAIL_LSM_HOOKS) that controls whether the
capability is enabled. In order to enable configuration from the user
space, add the standard debugfs entries for fault injection (if
CONFIG_FAULT_INJECTION_DEBUG_FS is enabled).

Signed-off-by: Aleksandr Nogikh <[email protected]>
---
lib/Kconfig.debug | 6 +++++
security/security.c | 53 ++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 56 insertions(+), 3 deletions(-)

diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 08c82666cf3e..0c9913ebe1c1 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -1803,6 +1803,12 @@ config FAIL_MAKE_REQUEST
help
Provide fault-injection capability for disk IO.

+config FAIL_LSM_HOOKS
+ bool "Fault-injection capability for LSM hooks"
+ depends on FAULT_INJECTION
+ help
+ Provide fault-injection capability for LSM hooks.
+
config FAIL_IO_TIMEOUT
bool "Fault-injection capability for faking disk interrupts"
depends on FAULT_INJECTION && BLOCK
diff --git a/security/security.c b/security/security.c
index 69ff6e2e2cd4..bd4dbe720098 100644
--- a/security/security.c
+++ b/security/security.c
@@ -28,6 +28,7 @@
#include <linux/backing-dev.h>
#include <linux/string.h>
#include <linux/msg.h>
+#include <linux/fault-inject.h>
#include <net/flow.h>

#define MAX_LSM_EVM_XATTR 2
@@ -669,6 +670,51 @@ static void __init lsm_early_task(struct task_struct *task)
panic("%s: Early task alloc failed.\n", __func__);
}

+
+#ifdef CONFIG_FAIL_LSM_HOOKS
+
+static struct {
+ struct fault_attr attr;
+ int retval;
+} fail_lsm_hooks = {
+ .attr = FAULT_ATTR_INITIALIZER,
+ .retval = -EACCES
+};
+
+static int __init setup_fail_lsm_hooks(char *str)
+{
+ return setup_fault_attr(&fail_lsm_hooks.attr, str);
+}
+__setup("fail_lsm_hooks=", setup_fail_lsm_hooks);
+
+static int should_fail_lsm_hook(void)
+{
+ return should_fail(&fail_lsm_hooks.attr, 1) ? fail_lsm_hooks.retval : 0;
+}
+
+#ifdef CONFIG_FAULT_INJECTION_DEBUG_FS
+
+static int __init fail_lsm_hooks_debugfs(void)
+{
+ umode_t mode = S_IFREG | 0600;
+ struct dentry *dir;
+
+ dir = fault_create_debugfs_attr("fail_lsm_hooks", NULL,
+ &fail_lsm_hooks.attr);
+ debugfs_create_u32("retval", mode, dir, &fail_lsm_hooks.retval);
+ return 0;
+}
+
+late_initcall(fail_lsm_hooks_debugfs);
+
+#endif /* CONFIG_FAULT_INJECTION_DEBUG_FS */
+
+#else
+
+static inline int should_fail_lsm_hook(void) { return 0; }
+
+#endif /* CONFIG_FAIL_LSM_HOOKS */
+
/*
* The default value of the LSM hook is defined in linux/lsm_hook_defs.h and
* can be accessed with:
@@ -707,16 +753,17 @@ static void __init lsm_early_task(struct task_struct *task)
} while (0)

#define call_int_hook(FUNC, IRC, ...) ({ \
- int RC = IRC; \
- do { \
+ int RC = should_fail_lsm_hook(); \
+ if (RC == 0) { \
struct security_hook_list *P; \
+ RC = IRC; \
\
hlist_for_each_entry(P, &security_hook_heads.FUNC, list) { \
RC = P->hook.FUNC(__VA_ARGS__); \
if (RC != 0) \
break; \
} \
- } while (0); \
+ } \
RC; \
})

--
2.28.0.1011.ga647a8990f-goog