[PATCH 1/2] printk: nbcon: Flush unsafe consoles from printk() in panic

From: Breno Leitao

Date: Mon Nov 02 2026 - 15:55:06 EST


netconsole registers with CON_NBCON_ATOMIC_UNSAFE since commit
7eab73b18630 ("netconsole: convert to NBCON console infrastructure"), so
its write_atomic() only runs from nbcon_atomic_flush_unsafe(). vpanic()
calls that right before its final busy loop, after emergency_restart()
and after __crash_kexec().

A panic that reboots (panic_timeout != 0) or boots a crash kernel
therefore never reaches the netconsole target. Before the conversion,
netconsole was a legacy console and printk() wrote each panic message to
it as it was printed.

Flush the unsafe consoles from nbcon_atomic_flush_pending() as well,
right after the safe ones, when it runs on the panic CPU with
oops_in_progress set. Every printk() on the panic CPU then reaches
netconsole again, "Rebooting in" included.

An unsafe write_atomic() that hangs now stops the panic CPU at the first
message, before the other consoles print the rest and before the reboot
or the crash kernel. Getting the messages out is worth that.

Fixes: 187de7c212e5 ("printk: nbcon: Allow unsafe write_atomic() for panic")
Reported-by: Ryan Roberts <ryan.roberts@xxxxxxx>
Closes: https://lore.kernel.org/all/20260915100140.3631650-1-ryan.roberts@xxxxxxx/
Suggested-by: Petr Mladek <pmladek@xxxxxxxx>
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Breno Leitao <leitao@xxxxxxxxxx>
---
include/linux/console.h | 7 ++++---
kernel/printk/nbcon.c | 24 ++++++++++++++++++------
2 files changed, 22 insertions(+), 9 deletions(-)

diff --git a/include/linux/console.h b/include/linux/console.h
index cb8be9ea4a767b..934fe72978b0e5 100644
--- a/include/linux/console.h
+++ b/include/linux/console.h
@@ -183,7 +183,8 @@ static inline void con_debug_leave(void) { }
* @CON_NBCON: Console can operate outside of the legacy style console_lock
* constraints.
* @CON_NBCON_ATOMIC_UNSAFE: The write_atomic() callback is not safe and is
- * therefore only used by nbcon_atomic_flush_unsafe().
+ * therefore only used in panic, when unsafe takeovers
+ * are allowed.
*/
enum cons_flags {
CON_PRINTBUFFER = BIT(0),
@@ -246,8 +247,8 @@ static_assert(sizeof(struct nbcon_state) <= sizeof(int));
* @NBCON_PRIO_MAX: The number of priority levels
*
* A higher priority context can takeover the console when it is
- * in the safe state. The final attempt to flush consoles in panic()
- * can be allowed to do so even in an unsafe state (Hope and pray).
+ * in the safe state. The panic CPU can be allowed to do so even in
+ * an unsafe state (Hope and pray).
*/
enum nbcon_prio {
NBCON_PRIO_NONE = 0,
diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c
index 5f8afaca886650..19fa9f79cac3cb 100644
--- a/kernel/printk/nbcon.c
+++ b/kernel/printk/nbcon.c
@@ -84,8 +84,8 @@
* b) Gives up console ownership by clearing the 'prio' field.
*
* 3) Unsafe hostile takeover allows to take over the lock even when the
- * console is an unsafe state. It is used only in panic() by the final
- * attempt to flush consoles in a try and hope mode.
+ * console is an unsafe state. It is used only on the panic CPU, in a
+ * try and hope mode.
*
* Note that separate record buffers are used in panic(). As a result,
* the messages can be read and formatted without any risk even after
@@ -543,7 +543,7 @@ static int nbcon_context_try_acquire_handover(struct nbcon_context *ctxt,
* Acquire the console even in the unsafe state.
*
* It can be permitted by setting the 'allow_unsafe_takeover' field only
- * by the final attempt to flush messages in panic().
+ * on the panic CPU.
*
* Return: 0 on success. -EPERM when not allowed by the context.
*/
@@ -1653,8 +1653,9 @@ static void nbcon_atomic_flush_pending_con(struct console *con, u64 stop_seq)
* __nbcon_atomic_flush_pending - Flush all nbcon consoles using their
* write_atomic() callback
* @stop_seq: Flush up until this record
+ * @unsafe_only: Flush only the CON_NBCON_ATOMIC_UNSAFE consoles
*/
-static void __nbcon_atomic_flush_pending(u64 stop_seq)
+static void __nbcon_atomic_flush_pending(u64 stop_seq, bool unsafe_only)
{
struct console *con;
int cookie;
@@ -1666,6 +1667,9 @@ static void __nbcon_atomic_flush_pending(u64 stop_seq)
if (!(flags & CON_NBCON))
continue;

+ if (unsafe_only && !(flags & CON_NBCON_ATOMIC_UNSAFE))
+ continue;
+
if (!console_is_usable(con, flags, true))
continue;

@@ -1688,7 +1692,15 @@ static void __nbcon_atomic_flush_pending(u64 stop_seq)
*/
void nbcon_atomic_flush_pending(void)
{
- __nbcon_atomic_flush_pending(prb_next_reserve_seq(prb));
+ u64 stop_seq = prb_next_reserve_seq(prb);
+
+ __nbcon_atomic_flush_pending(stop_seq, false);
+
+ if (panic_on_this_cpu() && oops_in_progress) {
+ panic_nbcon_allow_unsafe_takeover = true;
+ __nbcon_atomic_flush_pending(stop_seq, true);
+ panic_nbcon_allow_unsafe_takeover = false;
+ }
}

/**
@@ -1701,7 +1713,7 @@ void nbcon_atomic_flush_pending(void)
void nbcon_atomic_flush_unsafe(void)
{
panic_nbcon_allow_unsafe_takeover = true;
- __nbcon_atomic_flush_pending(prb_next_reserve_seq(prb));
+ __nbcon_atomic_flush_pending(prb_next_reserve_seq(prb), false);
panic_nbcon_allow_unsafe_takeover = false;
}


--
2.53.0-Meta