diff mbox series

tty: sysrq: Introduce compile-time crash-only mode

Message ID 20250607151957.222347-1-marwanmhks@gmail.com
State New
Headers show
Series tty: sysrq: Introduce compile-time crash-only mode | expand

Commit Message

Marwan Seliem June 7, 2025, 3:19 p.m. UTC
This commit introduces a new Kconfig option, CONFIG_MAGIC_SYSRQ_CRASH_ONLY,
which allows for a significant hardening of the system by restricting
the Magic SysRq functionality at compile time.

Security Impact:
- Reduces attack surface by disabling non-essential SysRq commands
- Maintains critical crash-dump capability required for debugging
- Eliminates runtime configuration vulnerabilities

When CONFIG_MAGIC_SYSRQ_CRASH_ONLY is enabled:

1.  Restricted Commands: Only the 'c' (trigger a system crash/dump)
    SysRq command remains operational. All other built-in SysRq commands
    (e.g., reboot, sync, show-memory, SAK) are disabled.

2.  Runtime Registration Disabled: The kernel will no longer allow
    the registration of new SysRq key operations at runtime via
    register_sysrq_key(). Attempts to do so will return -EPERM and
    a warning will be logged.

3.  Crash Command Unregistration Prevented: The 'c' (crash) command
    cannot be unregistered at runtime if this Kconfig option is active.

4.  Proc Interface Hardening: The /proc/sys/kernel/sysrq interface,
    which normally allows runtime enabling/disabling of SysRq features,
    is effectively neutered for non-crash commands.
    - Writing to /proc/sys/kernel/sysrq to enable features other than
      the crash dump will be blocked (returns -EPERM with a warning).
    - The sysrq_on_mask() function, which checks if a specific SysRq
      operation is permitted, will only return true for the crash dump
      operation, regardless of the /proc/sys/kernel/sysrq bitmask or
      the sysrq_always_enabled kernel command line parameter.

5.  Restricted Help Output: When an invalid SysRq key is pressed, the
    help message printed to the console will only list the 'c' (crash)
    command, reflecting the restricted functionality.

6.  Compile-Time Table Modification: The sysrq_key_table is
    initialized at boot time by sysrq_init_crash_only_table() to
    contain only the sysrq_crash_op for the 'c' key. All other
    entries are set to NULL.

This feature provides a strong compile-time mechanism to reduce the
attack surface associated with the Magic SysRq key, limiting its use
to critical crash dump generation for debugging purposes, which is often
essential even in highly secure environments.

The sysrq_on() function is modified to always return true when
CONFIG_MAGIC_SYSRQ_CRASH_ONLY is set. This ensures that the SysRq

input handler is registered, allowing the Alt+SysRq+C key combination
to be processed, while the actual command filtering occurs deeper
within the SysRq logic.

Usage Recommendation:
For systems requiring:
1. Guaranteed crash-dump capability
2. Elimination of debug backdoors
3. Compliance with strict security requirements

Affected files:
lib/Kconfig.debug: Added CONFIG_MAGIC_SYSRQ_CRASH_ONLY.
drivers/tty/sysrq.c: Implemented the conditional logic
for restricted mode.

Signed-off-by: Marwan Seliem <marwanmhks@gmail.com>
---
 drivers/tty/sysrq.c | 79 ++++++++++++++++++++++++++++++++++++++++++++-
 lib/Kconfig.debug   | 13 ++++++++
 2 files changed, 91 insertions(+), 1 deletion(-)
diff mbox series

Patch

diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c
index 6853c4660e7c..2e574380bec4 100644
--- a/drivers/tty/sysrq.c
+++ b/drivers/tty/sysrq.c
@@ -61,7 +61,18 @@  static bool __read_mostly sysrq_always_enabled;
 
 static bool sysrq_on(void)
 {
+#ifdef CONFIG_MAGIC_SYSRQ_CRASH_ONLY
+	/*
+	 * In CRASH_ONLY mode, sysrq is considered "on" only for the purpose
+	 * of allowing the crash command. The actual check for individual
+	 * commands happens in sysrq_on_mask().
+	 * For general "is sysrq on?" queries (like for input handler reg),
+	 * it should reflect that at least something (crash) is possible.
+	 */
+	return true;
+#else
 	return sysrq_enabled || sysrq_always_enabled;
+#endif
 }
 
 /**
@@ -82,9 +93,19 @@  EXPORT_SYMBOL_GPL(sysrq_mask);
  */
 static bool sysrq_on_mask(int mask)
 {
+#ifdef CONFIG_MAGIC_SYSRQ_CRASH_ONLY
+	/*
+	 * If CRASH_ONLY is set, only allow operations that have the
+	 * SYSRQ_ENABLE_DUMP mask (which sysrq_crash_op uses).
+	 * This makes sysrq_enabled and sysrq_always_enabled irrelevant
+	 * for other operations.
+	 */
+	return mask == SYSRQ_ENABLE_DUMP;
+#else
 	return sysrq_always_enabled ||
 	       sysrq_enabled == 1 ||
 	       (sysrq_enabled & mask);
+#endif
 }
 
 static int __init sysrq_always_enabled_setup(char *str)
@@ -557,6 +578,21 @@  static int sysrq_key_table_key2index(u8 key)
 	}
 }
 
+/*
+ * Initialize the sysrq_key_table at boot time if CRASH_ONLY is set.
+ * This ensures only the crash handler is active.
+ */
+static void __init sysrq_init_crash_only_table(void)
+{
+#ifdef CONFIG_MAGIC_SYSRQ_CRASH_ONLY
+	int i;
+	const struct sysrq_key_op *crash_op = &sysrq_crash_op;
+	for (i = 0; i < ARRAY_SIZE(sysrq_key_table); i++)
+		sysrq_key_table[i] = NULL;
+	sysrq_key_table[sysrq_key_table_key2index('c')] = crash_op;
+#endif
+};
+
 /*
  * get and put functions for the table, exposed to modules.
  */
@@ -584,7 +620,6 @@  void __handle_sysrq(u8 key, bool check_mask)
 {
 	const struct sysrq_key_op *op_p;
 	int orig_suppress_printk;
-	int i;
 
 	orig_suppress_printk = suppress_printk;
 	suppress_printk = 0;
@@ -599,7 +634,15 @@  void __handle_sysrq(u8 key, bool check_mask)
 	 */
 	printk_force_console_enter();
 
+#ifdef CONFIG_MAGIC_SYSRQ_CRASH_ONLY
+	if (key != 'c') { /* In CRASH_ONLY mode, only 'c' is considered */
+		op_p = NULL;
+	} else {
+		op_p = __sysrq_get_key_op(key);
+	}
+#else
 	op_p = __sysrq_get_key_op(key);
+#endif
 	if (op_p) {
 		/*
 		 * Should we check for enabled operations (/proc/sysrq-trigger
@@ -615,6 +658,15 @@  void __handle_sysrq(u8 key, bool check_mask)
 		}
 	} else {
 		pr_info("HELP : ");
+#ifdef CONFIG_MAGIC_SYSRQ_CRASH_ONLY
+		/* Check if the crash op is actually in the table and is the crash_op. */
+		if (sysrq_key_table_key2index('c') != -1 &&
+		    sysrq_key_table[sysrq_key_table_key2index('c')] == &sysrq_crash_op)
+			pr_cont("%s ", sysrq_crash_op.help_msg);
+		else /* Should not happen if table is defined correctly */
+			pr_cont("[Crash command not available] ");
+#else
+		int i;
 		/* Only print the help msg once per handler */
 		for (i = 0; i < ARRAY_SIZE(sysrq_key_table); i++) {
 			if (sysrq_key_table[i]) {
@@ -628,6 +680,7 @@  void __handle_sysrq(u8 key, bool check_mask)
 				pr_cont("%s ", sysrq_key_table[i]->help_msg);
 			}
 		}
+#endif
 		pr_cont("\n");
 		printk_force_console_exit();
 	}
@@ -1104,6 +1157,10 @@  static inline void sysrq_unregister_handler(void)
 
 int sysrq_toggle_support(int enable_mask)
 {
+#ifdef CONFIG_MAGIC_SYSRQ_CRASH_ONLY
+	pr_warn("SysRq: CONFIG_MAGIC_SYSRQ_CRASH_ONLY is set. Runtime toggle is not allowed.\n");
+	return -EPERM;
+#else
 	bool was_enabled = sysrq_on();
 
 	sysrq_enabled = enable_mask;
@@ -1116,6 +1173,7 @@  int sysrq_toggle_support(int enable_mask)
 	}
 
 	return 0;
+#endif
 }
 EXPORT_SYMBOL_GPL(sysrq_toggle_support);
 
@@ -1145,12 +1203,30 @@  static int __sysrq_swap_key_ops(u8 key, const struct sysrq_key_op *insert_op_p,
 
 int register_sysrq_key(u8 key, const struct sysrq_key_op *op_p)
 {
+#ifdef CONFIG_MAGIC_SYSRQ_CRASH_ONLY
+	/*
+	 * In CRASH_ONLY mode, do not allow registering new SysRq ops.
+	 */
+	pr_warn("SysRq: CONFIG_MAGIC_SYSRQ_CRASH_ONLY is set. Cannot register new SysRq key '%c'.\n", key);
+	return -EPERM;
+#endif
 	return __sysrq_swap_key_ops(key, op_p, NULL);
 }
 EXPORT_SYMBOL(register_sysrq_key);
 
 int unregister_sysrq_key(u8 key, const struct sysrq_key_op *op_p)
 {
+#ifdef CONFIG_MAGIC_SYSRQ_CRASH_ONLY
+	/*
+	 * In CRASH_ONLY mode, do not allow unregistering the crash op.
+	 * Other ops should be NULL anyway due to sysrq_init_crash_only_table.
+	 */
+	if (op_p == &sysrq_crash_op) {
+		pr_warn("SysRq: CONFIG_MAGIC_SYSRQ_CRASH_ONLY is set. Cannot unregister the crash SysRq key '%c'.\n", key);
+		return -EPERM;
+	}
+	return -EPERM; /* Attempt to unregister anything else is also an error */
+#endif
 	return __sysrq_swap_key_ops(key, NULL, op_p);
 }
 EXPORT_SYMBOL(unregister_sysrq_key);
@@ -1209,6 +1285,7 @@  static inline void sysrq_init_procfs(void)
 static int __init sysrq_init(void)
 {
 	sysrq_init_procfs();
+	sysrq_init_crash_only_table();
 
 	if (sysrq_on())
 		sysrq_register_handler();
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index ebe33181b6e6..c05b80cfb8aa 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -640,6 +640,19 @@  config MAGIC_SYSRQ_DEFAULT_ENABLE
 	  This may be set to 1 or 0 to enable or disable them all, or
 	  to a bitmask as described in Documentation/admin-guide/sysrq.rst.
 
+config MAGIC_SYSRQ_CRASH_ONLY
+	bool "Restrict Magic SysRq to crash command only"
+	depends on MAGIC_SYSRQ
+	default n
+	help
+	  If you say Y here, the Magic SysRq key functionality will be
+	  severely restricted at compile time. Only the 'c' command (trigger
+	  a system crash) will be available. All other SysRq commands will be
+	  disabled, and no new SysRq commands can be registered at runtime.
+	  The /proc/sys/kernel/sysrq setting will be ineffective for
+	  non-crash commands, and attempts to change it may be blocked.
+	  This is a security hardening option.
+
 config MAGIC_SYSRQ_SERIAL
 	bool "Enable magic SysRq key over serial"
 	depends on MAGIC_SYSRQ