netconsole: Fix race condition in between reader and writer of userdata
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: df03f830d0 ("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>
This commit is contained in:
committed by
Jakub Kicinski
parent
a433038098
commit
00764aa5c9
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user