initrd: make parsing of VLANs more robust

We are missing some validations when parsing VLANs: a unexpected
argument can cause a crash, an assertion, or the connection being
dropped without any warning. Make it more robust.
This commit is contained in:
Beniamino Galvani 2025-07-04 14:28:26 +02:00
parent eff8471de4
commit 326fb8f9cf
2 changed files with 85 additions and 7 deletions

View file

@ -1063,27 +1063,44 @@ reader_parse_vlan(Reader *reader, char *argument)
const char *vlan;
const char *phy;
const char *vlanid;
guint64 id;
vlan = get_word(&argument, ':');
phy = get_word(&argument, ':');
if (!vlan) {
_LOGW(LOGD_CORE, "missing VLAN interface name");
return;
}
if (!phy) {
_LOGW(LOGD_CORE, "missing VLAN parent");
return;
}
for (vlanid = vlan + strlen(vlan); vlanid > vlan; vlanid--) {
if (!g_ascii_isdigit(*(vlanid - 1)))
break;
}
if (vlanid[0] == '\0') {
_LOGW(LOGD_CORE, "missing VLAN id in '%s'", vlan);
return;
}
id = _nm_utils_ascii_str_to_int64(vlanid, 10, 0, 4094, G_MAXUINT);
if (id == G_MAXUINT) {
_LOGW(LOGD_CORE, "invalid VLAN id '%s'", vlanid);
return;
}
connection = reader_get_connection(reader, vlan, NM_SETTING_VLAN_SETTING_NAME, TRUE);
s_vlan = nm_connection_get_setting_vlan(connection);
g_object_set(s_vlan,
NM_SETTING_VLAN_PARENT,
phy,
NM_SETTING_VLAN_ID,
(guint) _nm_utils_ascii_str_to_int64(vlanid, 10, 0, G_MAXUINT, G_MAXUINT),
NULL);
g_object_set(s_vlan, NM_SETTING_VLAN_PARENT, phy, NM_SETTING_VLAN_ID, (guint32) id, NULL);
if (argument && *argument)
_LOGW(LOGD_CORE, "Ignoring extra: '%s'.", argument);
_LOGW(LOGD_CORE, "ignoring extra VLAN argument '%s'", argument);
if (!nm_strv_ptrarray_contains(reader->vlan_parents, phy))
g_ptr_array_add(reader->vlan_parents, g_strdup(phy));

View file

@ -1846,6 +1846,66 @@ test_vlan_over_bond(void)
}
}
static void
test_vlan_invalid(void)
{
{
/* Case 1: Missing name */
const char *const *ARGV0 = NM_MAKE_STRV("vlan=");
gs_unref_hashtable GHashTable *connections = NULL;
NMTST_EXPECT_NM_WARN("cmdline-reader: missing VLAN interface name");
connections = _parse_cons(ARGV0);
g_assert_cmpint(g_hash_table_size(connections), ==, 0);
g_test_assert_expected_messages();
}
{
/* Case 2: Missing parent */
const char *const *ARGV0 = NM_MAKE_STRV("vlan=vlan12");
gs_unref_hashtable GHashTable *connections = NULL;
NMTST_EXPECT_NM_WARN("cmdline-reader: missing VLAN parent");
connections = _parse_cons(ARGV0);
g_assert_cmpint(g_hash_table_size(connections), ==, 0);
g_test_assert_expected_messages();
}
{
/* Case 3: Interface name without trailing digits should fail,
* not trigger a GLib assertion. */
const char *const *ARGV0 = NM_MAKE_STRV("vlan=myvlan:eth0");
gs_unref_hashtable GHashTable *connections = NULL;
NMTST_EXPECT_NM_WARN("cmdline-reader: missing VLAN id in 'myvlan'");
connections = _parse_cons(ARGV0);
g_assert_cmpint(g_hash_table_size(connections), ==, 0);
g_test_assert_expected_messages();
}
{
/* Case 4: An invalid VLAN id should be rejected */
const char *const *ARGV0 = NM_MAKE_STRV("vlan=myvlan4095:eth0");
gs_unref_hashtable GHashTable *connections = NULL;
NMTST_EXPECT_NM_WARN("cmdline-reader: invalid VLAN id '4095'");
connections = _parse_cons(ARGV0);
g_assert_cmpint(g_hash_table_size(connections), ==, 0);
g_test_assert_expected_messages();
}
{
/* Case 5: Extra arguments */
const char *const *ARGV0 = NM_MAKE_STRV("vlan=eth0.80:eth0:reorder_hdr=on");
gs_unref_hashtable GHashTable *connections = NULL;
NMTST_EXPECT_NM_WARN("cmdline-reader: ignoring extra VLAN argument 'reorder_hdr=on'");
connections = _parse_cons(ARGV0);
g_assert_cmpint(g_hash_table_size(connections), ==, 2);
g_test_assert_expected_messages();
}
}
static void
test_ibft_ip_dev(void)
{
@ -2820,6 +2880,7 @@ main(int argc, char **argv)
g_test_add_func("/initrd/cmdline/vlan", test_vlan);
g_test_add_func("/initrd/cmdline/vlan/dhcp-on-parent", test_vlan_with_dhcp_on_parent);
g_test_add_func("/initrd/cmdline/vlan/over-bond", test_vlan_over_bond);
g_test_add_func("/initrd/cmdline/vlan/invalid", test_vlan_invalid);
g_test_add_func("/initrd/cmdline/bridge", test_bridge);
g_test_add_func("/initrd/cmdline/bridge/default", test_bridge_default);
g_test_add_func("/initrd/cmdline/bridge/ip", test_bridge_ip);