lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <201aa88f5c476ba56aa23183d74643275f8b2b41.1763492585.git.chris@chrisdown.name>
Date: Wed, 19 Nov 2025 03:07:06 +0800
From: Chris Down <chris@...isdown.name>
To: Petr Mladek <pmladek@...e.com>
Cc: linux-kernel@...r.kernel.org,
	Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
	Sergey Senozhatsky <senozhatsky@...omium.org>,
	Steven Rostedt <rostedt@...dmis.org>,
	John Ogness <john.ogness@...utronix.de>,
	Geert Uytterhoeven <geert@...ux-m68k.org>,
	Tony Lindgren <tony.lindgren@...ux.intel.com>, kernel-team@...com
Subject: [PATCH v7 03/13] printk: console: Implement core per-console
 loglevel infrastructure

Consoles can have vastly different latencies and throughputs. For
example, writing a message to the serial console can take on the order
of tens of milliseconds to get the UART to successfully write a message.
While this might be fine for a single, one-off message, this can cause
significant application-level stalls in situations where the kernel
writes large amounts of information to the console.

This means that while you might want to send at least INFO level
messages to (for example) netconsole, which is relatively fast, you may
only want to send at least WARN level messages to the serial console.
Such an implementation would permit debugging using the serial console
in cases that netconsole doesn't receive messages during particularly
bad system issues, while still keeping the noise low enough to avoid
inducing latency in userspace applications. To mitigate this, add such
an interface, extending the existing console loglevel controls to allow
each console to have its own loglevel.

One can't just disable the serial console, because one may actually need
it in situations where the machine is in a bad enough state that nothing
is received on netconsole. One also can't just bump the loglevel at
runtime after the issue, because usually the machine is already so
wedged by this point that it isn't responsive to such requests.

This commit adds the internal infrastructure to support per-console
log levels, which will be configurable through sysfs and the kernel
command line in future commits.

The global console_loglevel is preserved and used as the default log
level for all consoles. Each console can override this global level
with its own specific log level stored in struct console. To override
the global level, the per-console log level must be greater than 0;
otherwise, the default value of LOGLEVEL_DEFAULT (-1) ensures the
global level is used.

The existing ignore_loglevel command line parameter will override
both the global and per-console log levels.

Signed-off-by: Chris Down <chris@...isdown.name>
---
 include/linux/console.h  |  41 +++++++++--
 include/linux/printk.h   |   6 +-
 kernel/printk/internal.h |  12 ++++
 kernel/printk/nbcon.c    |   4 +-
 kernel/printk/printk.c   | 150 ++++++++++++++++++++++++++++++++++++++-
 5 files changed, 203 insertions(+), 10 deletions(-)

diff --git a/include/linux/console.h b/include/linux/console.h
index 8f10d0a85bb4..c0749a48fc3f 100644
--- a/include/linux/console.h
+++ b/include/linux/console.h
@@ -314,6 +314,8 @@ struct nbcon_write_context {
  * @match:		Callback for matching a console (Optional)
  * @flags:		Console flags. See enum cons_flags
  * @index:		Console index, e.g. port number
+ * @level:		Per-console loglevel. -1 means use global console_loglevel,
+ *			values > 0 specify console-specific filtering level
  * @cflag:		TTY control mode flags
  * @ispeed:		TTY input speed
  * @ospeed:		TTY output speed
@@ -342,6 +344,7 @@ struct console {
 	int			(*match)(struct console *co, char *name, int idx, char *options);
 	short			flags;
 	short			index;
+	int			level;
 	int			cflag;
 	uint			ispeed;
 	uint			ospeed;
@@ -518,11 +521,6 @@ extern struct hlist_head console_list;
 static inline short console_srcu_read_flags(const struct console *con)
 {
 	WARN_ON_ONCE(!console_srcu_read_lock_is_held());
-
-	/*
-	 * The READ_ONCE() matches the WRITE_ONCE() when @flags are modified
-	 * for registered consoles with console_srcu_write_flags().
-	 */
 	return data_race(READ_ONCE(con->flags));
 }
 
@@ -544,6 +542,39 @@ static inline void console_srcu_write_flags(struct console *con, short flags)
 	WRITE_ONCE(con->flags, flags);
 }
 
+/**
+ * console_srcu_read_loglevel - Locklessly read the console specific loglevel
+ *				of a possibly registered console
+ * @con:	struct console pointer of console to read loglevel from
+ *
+ * Locklessly reading @con->level provides a consistent read value because
+ * there is at most one CPU modifying @con->level and that CPU is using only
+ * read-modify-write operations to do so.
+ *
+ * Requires console_srcu_read_lock to be held, which implies that @con might
+ * be a registered console. The purpose of holding console_srcu_read_lock is
+ * to guarantee that the console state is valid (CON_SUSPENDED/CON_ENABLED)
+ * and that no exit/cleanup routines will run if the console is currently
+ * undergoing unregistration.
+ *
+ * If the caller is holding the console_list_lock or it is _certain_ that
+ * @con is not and will not become registered, the caller may read
+ * @con->level directly instead.
+ *
+ * Context: Any context.
+ * Return: The current value of the @con->level field.
+ */
+static inline int console_srcu_read_loglevel(const struct console *con)
+{
+	WARN_ON_ONCE(!console_srcu_read_lock_is_held());
+
+	/*
+	 * The READ_ONCE() matches the WRITE_ONCE() when @level is modified
+	 * for registered consoles.
+	 */
+	return data_race(READ_ONCE(con->level));
+}
+
 /* Variant of console_is_registered() when the console_list_lock is held. */
 static inline bool console_is_registered_locked(const struct console *con)
 {
diff --git a/include/linux/printk.h b/include/linux/printk.h
index 45c663124c9b..644584edf3e9 100644
--- a/include/linux/printk.h
+++ b/include/linux/printk.h
@@ -209,6 +209,7 @@ void printk_legacy_allow_panic_sync(void);
 extern bool nbcon_device_try_acquire(struct console *con);
 extern void nbcon_device_release(struct console *con);
 void nbcon_atomic_flush_unsafe(void);
+bool has_per_console_loglevel(const struct console *con);
 bool pr_flush(int timeout_ms, bool reset_on_progress);
 #else
 static inline __printf(1, 0)
@@ -322,7 +323,10 @@ static inline void nbcon_device_release(struct console *con)
 static inline void nbcon_atomic_flush_unsafe(void)
 {
 }
-
+static inline bool has_per_console_loglevel(const struct console *con)
+{
+	return false;
+}
 static inline bool pr_flush(int timeout_ms, bool reset_on_progress)
 {
 	return true;
diff --git a/kernel/printk/internal.h b/kernel/printk/internal.h
index 1ed86577896c..41e37b44778f 100644
--- a/kernel/printk/internal.h
+++ b/kernel/printk/internal.h
@@ -15,6 +15,18 @@ int devkmsg_sysctl_set_loglvl(const struct ctl_table *table, int write,
 #define printk_sysctl_init() do { } while (0)
 #endif
 
+enum loglevel_source {
+	LLS_GLOBAL,
+	LLS_LOCAL,
+	LLS_IGNORE_LOGLEVEL,
+};
+
+int console_clamp_loglevel(int level);
+
+enum loglevel_source
+console_effective_loglevel_source(int con_level);
+int console_effective_loglevel(int con_level);
+
 #define con_printk(lvl, con, fmt, ...)				\
 	printk(lvl pr_fmt("%s%sconsole [%s%d] " fmt),		\
 		(con->flags & CON_NBCON) ? "" : "legacy ",	\
diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c
index eb4c8faa213d..fb99aaa8c5d8 100644
--- a/kernel/printk/nbcon.c
+++ b/kernel/printk/nbcon.c
@@ -961,6 +961,7 @@ static bool nbcon_emit_next_record(struct nbcon_write_context *wctxt, bool use_a
 	struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt);
 	struct console *con = ctxt->console;
 	bool is_extended = console_srcu_read_flags(con) & CON_EXTENDED;
+	int con_level = console_srcu_read_loglevel(con);
 	struct printk_message pmsg = {
 		.pbufs = ctxt->pbufs,
 	};
@@ -993,7 +994,8 @@ static bool nbcon_emit_next_record(struct nbcon_write_context *wctxt, bool use_a
 	if (!nbcon_context_enter_unsafe(ctxt))
 		return false;
 
-	ctxt->backlog = printk_get_next_message(&pmsg, ctxt->seq, is_extended, console_loglevel);
+	ctxt->backlog = printk_get_next_message(&pmsg, ctxt->seq, is_extended,
+						console_effective_loglevel(con_level));
 	if (!ctxt->backlog)
 		return nbcon_context_exit_unsafe(ctxt);
 
diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index 745f89a18189..928d77c56c77 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -197,6 +197,24 @@ static int __init control_devkmsg(char *str)
 }
 __setup("printk.devkmsg=", control_devkmsg);
 
+/**
+ * console_clamp_loglevel - Clamp a loglevel to valid console loglevel range
+ *
+ * @level: The loglevel to clamp
+ *
+ * Console loglevels must be within the range [LOGLEVEL_ALERT, LOGLEVEL_DEBUG + 1].
+ * This function clamps a given level to this valid range.
+ *
+ * Note: This does not allow LOGLEVEL_EMERG (0) for per-console loglevels, as
+ * level 0 is reserved for emergency messages that should always go to all consoles.
+ *
+ * Return: The clamped loglevel value
+ */
+int console_clamp_loglevel(int level)
+{
+	return clamp(level, LOGLEVEL_ALERT, LOGLEVEL_DEBUG + 1);
+}
+
 char devkmsg_log_str[DEVKMSG_STR_MAX_SIZE] = "ratelimit";
 #if defined(CONFIG_PRINTK) && defined(CONFIG_SYSCTL)
 int devkmsg_sysctl_set_loglvl(const struct ctl_table *table, int write,
@@ -1280,11 +1298,120 @@ module_param(ignore_loglevel, bool, S_IRUGO | S_IWUSR);
 MODULE_PARM_DESC(ignore_loglevel,
 		 "ignore loglevel setting (prints all kernel messages to the console)");
 
+bool has_per_console_loglevel(const struct console *con)
+{
+	return con && (console_srcu_read_loglevel(con) > 0);
+}
+
+/**
+ * is_valid_per_console_loglevel - Check if a loglevel is valid for per-console
+ *
+ * @con_level: The loglevel to check
+ *
+ * Per-console loglevels must be strictly positive (> 0). Level 0 (KERN_EMERG)
+ * is reserved for emergency messages that should go to all consoles (and so is
+ * disallowed), and -1 (LOGLEVEL_DEFAULT) means use the global console_loglevel.
+ *
+ * Return: true if con_level is a valid per-console loglevel (> 0), false
+ * otherwise
+ */
+static bool is_valid_per_console_loglevel(int con_level)
+{
+	return (con_level > 0);
+}
+
+/**
+ * console_effective_loglevel_source - Determine the source of effective loglevel
+ *
+ * @con_level: The console's per-console loglevel value
+ *
+ * This function determines which loglevel authority is in effect for a console,
+ * based on the hierarchy of controls:
+ *
+ * 1. ignore_loglevel (overrides everything - prints all messages)
+ * 2. per-console loglevel (if set and not ignored)
+ * 3. global console_loglevel (fallback)
+ *
+ * Return: The loglevel source (LLS_IGNORE_LOGLEVEL, LLS_LOCAL, or LLS_GLOBAL)
+ */
+enum loglevel_source
+console_effective_loglevel_source(int con_level)
+{
+	if (ignore_loglevel)
+		return LLS_IGNORE_LOGLEVEL;
+
+	if (is_valid_per_console_loglevel(con_level))
+		return LLS_LOCAL;
+
+	return LLS_GLOBAL;
+}
+
+/**
+ * console_effective_loglevel - Get the effective loglevel for a console
+ *
+ * @con_level: The console's per-console loglevel value
+ *
+ * This function returns the actual loglevel value that should be used for
+ * message filtering for a console, taking into account all loglevel controls
+ * (global, per-console, and ignore_loglevel).
+ *
+ * The effective loglevel is used to determine which messages get printed to
+ * the console. Messages with priority less than the effective level are printed.
+ *
+ * Return: The effective loglevel value to use for filtering
+ */
+int console_effective_loglevel(int con_level)
+{
+	enum loglevel_source source;
+	int level;
+
+	source = console_effective_loglevel_source(con_level);
+
+	switch (source) {
+	case LLS_IGNORE_LOGLEVEL:
+		level = CONSOLE_LOGLEVEL_MOTORMOUTH;
+		break;
+	case LLS_LOCAL:
+		level = con_level;
+		break;
+	case LLS_GLOBAL:
+		level = console_loglevel;
+		break;
+	default:
+		pr_warn("Unhandled console loglevel source: %d", source);
+		level = console_loglevel;
+		break;
+	}
+
+	return level;
+}
+
 static bool suppress_message_printing(int level, int con_eff_level)
 {
 	return (level >= con_eff_level && !ignore_loglevel);
 }
 
+static bool suppress_message_printing_everywhere(int level)
+{
+	bool suppress_everywhere = true;
+	struct console *con;
+	int cookie;
+
+	cookie = console_srcu_read_lock();
+
+	for_each_console_srcu(con) {
+		int con_level = console_srcu_read_loglevel(con);
+
+		if (!suppress_message_printing(level, console_effective_loglevel(con_level))) {
+			suppress_everywhere = false;
+			break;
+		}
+	}
+	console_srcu_read_unlock(cookie);
+
+	return suppress_everywhere;
+}
+
 #ifdef CONFIG_BOOT_PRINTK_DELAY
 
 static int boot_delay; /* msecs delay after each printk during bootup */
@@ -2116,7 +2243,8 @@ int printk_delay_msec __read_mostly;
 static inline void printk_delay(int level)
 {
 	/* If the message is forced (e.g. panic), we must delay */
-	if (!is_printk_force_console() && suppress_message_printing(level, console_loglevel))
+	if (!is_printk_force_console() &&
+	    suppress_message_printing_everywhere(level))
 		return;
 
 	boot_delay_msec();
@@ -3059,6 +3187,7 @@ struct printk_buffers printk_shared_pbufs;
 static bool console_emit_next_record(struct console *con, bool *handover, int cookie)
 {
 	bool is_extended = console_srcu_read_flags(con) & CON_EXTENDED;
+	int con_level = console_srcu_read_loglevel(con);
 	char *outbuf = &printk_shared_pbufs.outbuf[0];
 	struct printk_message pmsg = {
 		.pbufs = &printk_shared_pbufs,
@@ -3068,7 +3197,7 @@ static bool console_emit_next_record(struct console *con, bool *handover, int co
 	*handover = false;
 
 	if (!printk_get_next_message(&pmsg, con->seq, is_extended,
-				     console_loglevel))
+				     console_effective_loglevel(con_level)))
 		return false;
 
 	con->dropped += pmsg.dropped;
@@ -3817,6 +3946,9 @@ static int try_enable_preferred_console(struct console *newcon,
 			if (newcon->index < 0)
 				newcon->index = c->index;
 
+			/* TODO: will be configurable in a later patch */
+			newcon->level = LOGLEVEL_DEFAULT;
+
 			if (_braille_register_console(newcon, c))
 				return 0;
 
@@ -3835,8 +3967,12 @@ static int try_enable_preferred_console(struct console *newcon,
 	 * without matching. Accept the pre-enabled consoles only when match()
 	 * and setup() had a chance to be called.
 	 */
-	if (newcon->flags & CON_ENABLED && c->user_specified ==	user_specified)
+	if (newcon->flags & CON_ENABLED && c->user_specified ==	user_specified) {
+		/* Ensure level is initialized for pre-enabled consoles */
+		if (newcon->level == 0)
+			newcon->level = LOGLEVEL_DEFAULT;
 		return 0;
+	}
 
 	return -ENOENT;
 }
@@ -4039,6 +4175,14 @@ void register_console(struct console *newcon)
 	}
 
 	newcon->dropped = 0;
+
+	/*
+	 * Don't unconditionally overwrite, it may have been set on the command
+	 * line already.
+	 */
+	if (newcon->level == 0)
+		newcon->level = LOGLEVEL_DEFAULT;
+
 	init_seq = get_init_console_seq(newcon, bootcon_registered);
 
 	if (newcon->flags & CON_NBCON) {
-- 
2.51.2


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ