Commit 00764aa5 authored by Gustavo Luiz Duarte's avatar Gustavo Luiz Duarte Committed by Jakub Kicinski
Browse files

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: df03f830 ("net: netconsole: cache userdata formatted string in netconsole_target")
Signed-off-by: default avatarGustavo Luiz Duarte <gustavold@gmail.com>
Link: https://patch.msgid.link/20251028-netconsole-fix-race-v4-1-63560b0ae1a0@meta.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent a4330380
Loading
Loading
Loading
Loading
+13 −8
Original line number Diff line number Diff line
@@ -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],
		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,