]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
netconsole: Fix race condition in between reader and writer of userdata
authorGustavo Luiz Duarte <gustavold@gmail.com>
Tue, 28 Oct 2025 22:06:32 +0000 (15:06 -0700)
committerJakub Kicinski <kuba@kernel.org>
Thu, 30 Oct 2025 00:41:00 +0000 (17:41 -0700)
The update_userdata() function constructs the complete userdata string
in nt->extradata_complete and updates nt->userdata_length. This data
is then read by write_msg() and write_ext_msg() when sending netconsole
messages. However, update_userdata() was not holding target_list_lock
during this process, allowing concurrent message transmission to read
partially updated userdata.

This race condition could result in netconsole messages containing
incomplete or inconsistent userdata - for example, reading the old
userdata_length with new extradata_complete content, or vice versa,
leading to truncated or corrupted output.

Fix this by acquiring target_list_lock with spin_lock_irqsave() before
updating extradata_complete and userdata_length, and releasing it after
both fields are fully updated. This ensures that readers see a
consistent view of the userdata, preventing corruption during concurrent
access.

The fix aligns with the existing locking pattern used throughout the
netconsole code, where target_list_lock protects access to target
fields including buf[] and msgcounter that are accessed during message
transmission.

Also get rid of the unnecessary variable complete_idx, which makes it
easier to bail out of update_userdata().

Fixes: df03f830d099 ("net: netconsole: cache userdata formatted string in netconsole_target")
Signed-off-by: Gustavo Luiz Duarte <gustavold@gmail.com>
Link: https://patch.msgid.link/20251028-netconsole-fix-race-v4-1-63560b0ae1a0@meta.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/netconsole.c

index 194570443493b1417570d3fe3250281cffe01316..5d8d0214786c7bc098ce9be0adbfa3cc17dc9a04 100644 (file)
@@ -886,8 +886,11 @@ static ssize_t userdatum_value_show(struct config_item *item, char *buf)
 
 static void update_userdata(struct netconsole_target *nt)
 {
-       int complete_idx = 0, child_count = 0;
        struct list_head *entry;
+       int child_count = 0;
+       unsigned long flags;
+
+       spin_lock_irqsave(&target_list_lock, flags);
 
        /* Clear the current string in case the last userdatum was deleted */
        nt->userdata_length = 0;
@@ -897,8 +900,11 @@ static void update_userdata(struct netconsole_target *nt)
                struct userdatum *udm_item;
                struct config_item *item;
 
-               if (WARN_ON_ONCE(child_count >= MAX_EXTRADATA_ITEMS))
-                       break;
+               if (child_count >= MAX_EXTRADATA_ITEMS) {
+                       spin_unlock_irqrestore(&target_list_lock, flags);
+                       WARN_ON_ONCE(1);
+                       return;
+               }
                child_count++;
 
                item = container_of(entry, struct config_item, ci_entry);
@@ -912,12 +918,11 @@ static void update_userdata(struct netconsole_target *nt)
                 * one entry length (1/MAX_EXTRADATA_ITEMS long), entry count is
                 * checked to not exceed MAX items with child_count above
                 */
-               complete_idx += scnprintf(&nt->extradata_complete[complete_idx],
-                                         MAX_EXTRADATA_ENTRY_LEN, " %s=%s\n",
-                                         item->ci_name, udm_item->value);
+               nt->userdata_length += scnprintf(&nt->extradata_complete[nt->userdata_length],
+                                                MAX_EXTRADATA_ENTRY_LEN, " %s=%s\n",
+                                                item->ci_name, udm_item->value);
        }
-       nt->userdata_length = strnlen(nt->extradata_complete,
-                                     sizeof(nt->extradata_complete));
+       spin_unlock_irqrestore(&target_list_lock, flags);
 }
 
 static ssize_t userdatum_value_store(struct config_item *item, const char *buf,