core: pass certificates as blobs to supplicant for private connections

In case of private connections, the device has already read the
certificates and keys content from disk, validating that the owner of
the connection has access to them. Pass those files as blobs to the
supplicant so that it doesn't have to read them again from the
filesystem, creating the opportunity for TOCTOU bugs.
This commit is contained in:
Beniamino Galvani 2025-09-23 17:06:39 +02:00 committed by Íñigo Huguet
parent a1928b4459
commit e85cc46d0b
7 changed files with 137 additions and 60 deletions

3
NEWS
View file

@ -18,6 +18,9 @@ USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE!
polkit permissions to allow non-admin users to create system-wide polkit permissions to allow non-admin users to create system-wide
connection. That configuration is discouraged because it can be used connection. That configuration is discouraged because it can be used
to bypass filesystem permissions. to bypass filesystem permissions.
* For private connections (the ones that specify a user in the
"connection.permissions" property), verify that the user can access
the 802.1X certificates and keys set in the connection.
============================================= =============================================
NetworkManager-1.56 NetworkManager-1.56

View file

@ -629,10 +629,17 @@ build_supplicant_config(NMDeviceEthernet *self, GError **error)
mtu = nm_platform_link_get_mtu(nm_device_get_platform(NM_DEVICE(self)), mtu = nm_platform_link_get_mtu(nm_device_get_platform(NM_DEVICE(self)),
nm_device_get_ifindex(NM_DEVICE(self))); nm_device_get_ifindex(NM_DEVICE(self)));
config = nm_supplicant_config_new(NM_SUPPL_CAP_MASK_NONE); config = nm_supplicant_config_new(NM_SUPPL_CAP_MASK_NONE,
nm_utils_get_connection_first_permissions_user(connection));
security = nm_connection_get_setting_802_1x(connection); security = nm_connection_get_setting_802_1x(connection);
if (!nm_supplicant_config_add_setting_8021x(config, security, con_uuid, mtu, TRUE, error)) { if (!nm_supplicant_config_add_setting_8021x(config,
security,
con_uuid,
mtu,
TRUE,
nm_device_get_private_files(NM_DEVICE(self)),
error)) {
g_prefix_error(error, "802-1x-setting: "); g_prefix_error(error, "802-1x-setting: ");
g_clear_object(&config); g_clear_object(&config);
} }

View file

@ -201,7 +201,8 @@ build_supplicant_config(NMDeviceMacsec *self, GError **error)
mtu = nm_platform_link_get_mtu(nm_device_get_platform(NM_DEVICE(self)), mtu = nm_platform_link_get_mtu(nm_device_get_platform(NM_DEVICE(self)),
nm_device_get_ifindex(NM_DEVICE(self))); nm_device_get_ifindex(NM_DEVICE(self)));
config = nm_supplicant_config_new(NM_SUPPL_CAP_MASK_NONE); config = nm_supplicant_config_new(NM_SUPPL_CAP_MASK_NONE,
nm_utils_get_connection_first_permissions_user(connection));
s_macsec = nm_device_get_applied_setting(NM_DEVICE(self), NM_TYPE_SETTING_MACSEC); s_macsec = nm_device_get_applied_setting(NM_DEVICE(self), NM_TYPE_SETTING_MACSEC);
@ -227,7 +228,13 @@ build_supplicant_config(NMDeviceMacsec *self, GError **error)
if (nm_setting_macsec_get_mode(s_macsec) == NM_SETTING_MACSEC_MODE_EAP) { if (nm_setting_macsec_get_mode(s_macsec) == NM_SETTING_MACSEC_MODE_EAP) {
s_8021x = nm_connection_get_setting_802_1x(connection); s_8021x = nm_connection_get_setting_802_1x(connection);
if (!nm_supplicant_config_add_setting_8021x(config, s_8021x, con_uuid, mtu, TRUE, error)) { if (!nm_supplicant_config_add_setting_8021x(config,
s_8021x,
con_uuid,
mtu,
TRUE,
nm_device_get_private_files(NM_DEVICE(self)),
error)) {
g_prefix_error(error, "802-1x-setting: "); g_prefix_error(error, "802-1x-setting: ");
return NULL; return NULL;
} }

View file

@ -3019,7 +3019,8 @@ build_supplicant_config(NMDeviceWifi *self,
s_wireless = nm_connection_get_setting_wireless(connection); s_wireless = nm_connection_get_setting_wireless(connection);
g_return_val_if_fail(s_wireless != NULL, NULL); g_return_val_if_fail(s_wireless != NULL, NULL);
config = nm_supplicant_config_new(nm_supplicant_interface_get_capabilities(priv->sup_iface)); config = nm_supplicant_config_new(nm_supplicant_interface_get_capabilities(priv->sup_iface),
nm_utils_get_connection_first_permissions_user(connection));
/* Warn if AP mode may not be supported */ /* Warn if AP mode may not be supported */
if (nm_streq0(nm_setting_wireless_get_mode(s_wireless), NM_SETTING_WIRELESS_MODE_AP) if (nm_streq0(nm_setting_wireless_get_mode(s_wireless), NM_SETTING_WIRELESS_MODE_AP)
@ -3095,6 +3096,7 @@ build_supplicant_config(NMDeviceWifi *self,
mtu, mtu,
pmf, pmf,
fils, fils,
nm_device_get_private_files(NM_DEVICE(self)),
error)) { error)) {
g_prefix_error(error, "802-11-wireless-security: "); g_prefix_error(error, "802-11-wireless-security: ");
goto error; goto error;

View file

@ -30,6 +30,7 @@ typedef struct {
typedef struct { typedef struct {
GHashTable *config; GHashTable *config;
GHashTable *blobs; GHashTable *blobs;
char *private_user;
NMSupplCapMask capabilities; NMSupplCapMask capabilities;
guint32 ap_scan; guint32 ap_scan;
bool fast_required : 1; bool fast_required : 1;
@ -60,7 +61,7 @@ _get_capability(NMSupplicantConfigPrivate *priv, NMSupplCapType type)
} }
NMSupplicantConfig * NMSupplicantConfig *
nm_supplicant_config_new(NMSupplCapMask capabilities) nm_supplicant_config_new(NMSupplCapMask capabilities, const char *private_user)
{ {
NMSupplicantConfigPrivate *priv; NMSupplicantConfigPrivate *priv;
NMSupplicantConfig *self; NMSupplicantConfig *self;
@ -69,6 +70,7 @@ nm_supplicant_config_new(NMSupplCapMask capabilities)
priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self); priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
priv->capabilities = capabilities; priv->capabilities = capabilities;
priv->private_user = g_strdup(private_user);
return self; return self;
} }
@ -283,6 +285,7 @@ nm_supplicant_config_finalize(GObject *object)
g_hash_table_destroy(priv->config); g_hash_table_destroy(priv->config);
nm_clear_pointer(&priv->blobs, g_hash_table_destroy); nm_clear_pointer(&priv->blobs, g_hash_table_destroy);
nm_clear_pointer(&priv->private_user, g_free);
G_OBJECT_CLASS(nm_supplicant_config_parent_class)->finalize(object); G_OBJECT_CLASS(nm_supplicant_config_parent_class)->finalize(object);
} }
@ -930,6 +933,7 @@ nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig
guint32 mtu, guint32 mtu,
NMSettingWirelessSecurityPmf pmf, NMSettingWirelessSecurityPmf pmf,
NMSettingWirelessSecurityFils fils, NMSettingWirelessSecurityFils fils,
GHashTable *files,
GError **error) GError **error)
{ {
NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self); NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE(self);
@ -1284,6 +1288,7 @@ nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig
con_uuid, con_uuid,
mtu, mtu,
FALSE, FALSE,
files,
error)) error))
return FALSE; return FALSE;
} }
@ -1365,6 +1370,7 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
const char *con_uuid, const char *con_uuid,
guint32 mtu, guint32 mtu,
gboolean wired, gboolean wired,
GHashTable *files,
GError **error) GError **error)
{ {
NMSupplicantConfigPrivate *priv; NMSupplicantConfigPrivate *priv;
@ -1594,24 +1600,21 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
} }
/* CA certificate */ /* CA certificate */
path = NULL;
bytes = NULL;
if (ca_cert_override) { if (ca_cert_override) {
if (!add_string_val(self, ca_cert_override, "ca_cert", FALSE, NULL, error)) /* This is a build-time-configured system-wide file path, no need to pass
return FALSE; * it as a blob */
path = ca_cert_override;
} else { } else {
switch (nm_setting_802_1x_get_ca_cert_scheme(setting)) { switch (nm_setting_802_1x_get_ca_cert_scheme(setting)) {
case NM_SETTING_802_1X_CK_SCHEME_BLOB: case NM_SETTING_802_1X_CK_SCHEME_BLOB:
bytes = nm_setting_802_1x_get_ca_cert_blob(setting); bytes = nm_setting_802_1x_get_ca_cert_blob(setting);
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"ca_cert",
con_uuid,
error))
return FALSE;
break; break;
case NM_SETTING_802_1X_CK_SCHEME_PATH: case NM_SETTING_802_1X_CK_SCHEME_PATH:
path = nm_setting_802_1x_get_ca_cert_path(setting); path = nm_setting_802_1x_get_ca_cert_path(setting);
if (!add_string_val(self, path, "ca_cert", FALSE, NULL, error)) if (priv->private_user)
return FALSE; bytes = nm_g_hash_table_lookup(files, path);
break; break;
case NM_SETTING_802_1X_CK_SCHEME_PKCS11: case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
if (!add_pkcs11_uri_with_pin(self, if (!add_pkcs11_uri_with_pin(self,
@ -1627,26 +1630,32 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
break; break;
} }
} }
if (bytes) {
if (!nm_supplicant_config_add_blob_for_connection(self, bytes, "ca_cert", con_uuid, error))
return FALSE;
} else if (path) {
/* Private connections cannot use paths other than the system CA store */
g_return_val_if_fail(ca_cert_override || !priv->private_user, FALSE);
if (!add_string_val(self, path, "ca_cert", FALSE, NULL, error))
return FALSE;
}
/* Phase 2 CA certificate */ /* Phase 2 CA certificate */
path = NULL;
bytes = NULL;
if (ca_cert_override) { if (ca_cert_override) {
if (!add_string_val(self, ca_cert_override, "ca_cert2", FALSE, NULL, error)) /* This is a build-time-configured system-wide file path, no need to pass
return FALSE; * it as a blob */
path = ca_cert_override;
} else { } else {
switch (nm_setting_802_1x_get_phase2_ca_cert_scheme(setting)) { switch (nm_setting_802_1x_get_phase2_ca_cert_scheme(setting)) {
case NM_SETTING_802_1X_CK_SCHEME_BLOB: case NM_SETTING_802_1X_CK_SCHEME_BLOB:
bytes = nm_setting_802_1x_get_phase2_ca_cert_blob(setting); bytes = nm_setting_802_1x_get_phase2_ca_cert_blob(setting);
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"ca_cert2",
con_uuid,
error))
return FALSE;
break; break;
case NM_SETTING_802_1X_CK_SCHEME_PATH: case NM_SETTING_802_1X_CK_SCHEME_PATH:
path = nm_setting_802_1x_get_phase2_ca_cert_path(setting); path = nm_setting_802_1x_get_phase2_ca_cert_path(setting);
if (!add_string_val(self, path, "ca_cert2", FALSE, NULL, error)) if (priv->private_user)
return FALSE; bytes = nm_g_hash_table_lookup(files, path);
break; break;
case NM_SETTING_802_1X_CK_SCHEME_PKCS11: case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
if (!add_pkcs11_uri_with_pin( if (!add_pkcs11_uri_with_pin(
@ -1663,6 +1672,15 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
break; break;
} }
} }
if (bytes) {
if (!nm_supplicant_config_add_blob_for_connection(self, bytes, "ca_cert2", con_uuid, error))
return FALSE;
} else if (path) {
/* Private connections cannot use paths other than the system CA store */
g_return_val_if_fail(ca_cert_override || !priv->private_user, FALSE);
if (!add_string_val(self, path, "ca_cert2", FALSE, NULL, error))
return FALSE;
}
/* Subject match */ /* Subject match */
value = nm_setting_802_1x_get_subject_match(setting); value = nm_setting_802_1x_get_subject_match(setting);
@ -1714,21 +1732,17 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
/* Private key */ /* Private key */
added = FALSE; added = FALSE;
path = NULL;
bytes = NULL;
switch (nm_setting_802_1x_get_private_key_scheme(setting)) { switch (nm_setting_802_1x_get_private_key_scheme(setting)) {
case NM_SETTING_802_1X_CK_SCHEME_BLOB: case NM_SETTING_802_1X_CK_SCHEME_BLOB:
bytes = nm_setting_802_1x_get_private_key_blob(setting); bytes = nm_setting_802_1x_get_private_key_blob(setting);
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"private_key",
con_uuid,
error))
return FALSE;
added = TRUE; added = TRUE;
break; break;
case NM_SETTING_802_1X_CK_SCHEME_PATH: case NM_SETTING_802_1X_CK_SCHEME_PATH:
path = nm_setting_802_1x_get_private_key_path(setting); path = nm_setting_802_1x_get_private_key_path(setting);
if (!add_string_val(self, path, "private_key", FALSE, NULL, error)) if (priv->private_user)
return FALSE; bytes = nm_g_hash_table_lookup(files, path);
added = TRUE; added = TRUE;
break; break;
case NM_SETTING_802_1X_CK_SCHEME_PKCS11: case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
@ -1745,6 +1759,19 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
default: default:
break; break;
} }
if (bytes) {
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"private_key",
con_uuid,
error))
return FALSE;
} else if (path) {
/* Private connections cannot use paths */
g_return_val_if_fail(!priv->private_user, FALSE);
if (!add_string_val(self, path, "private_key", FALSE, NULL, error))
return FALSE;
}
if (added) { if (added) {
NMSetting8021xCKFormat format; NMSetting8021xCKFormat format;
@ -1768,20 +1795,16 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
/* Only add the client cert if the private key is not PKCS#12, as /* Only add the client cert if the private key is not PKCS#12, as
* wpa_supplicant configuration directs us to do. * wpa_supplicant configuration directs us to do.
*/ */
path = NULL;
bytes = NULL;
switch (nm_setting_802_1x_get_client_cert_scheme(setting)) { switch (nm_setting_802_1x_get_client_cert_scheme(setting)) {
case NM_SETTING_802_1X_CK_SCHEME_BLOB: case NM_SETTING_802_1X_CK_SCHEME_BLOB:
bytes = nm_setting_802_1x_get_client_cert_blob(setting); bytes = nm_setting_802_1x_get_client_cert_blob(setting);
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"client_cert",
con_uuid,
error))
return FALSE;
break; break;
case NM_SETTING_802_1X_CK_SCHEME_PATH: case NM_SETTING_802_1X_CK_SCHEME_PATH:
path = nm_setting_802_1x_get_client_cert_path(setting); path = nm_setting_802_1x_get_client_cert_path(setting);
if (!add_string_val(self, path, "client_cert", FALSE, NULL, error)) if (priv->private_user)
return FALSE; bytes = nm_g_hash_table_lookup(files, path);
break; break;
case NM_SETTING_802_1X_CK_SCHEME_PKCS11: case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
if (!add_pkcs11_uri_with_pin( if (!add_pkcs11_uri_with_pin(
@ -1797,26 +1820,35 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
default: default:
break; break;
} }
if (bytes) {
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"client_cert",
con_uuid,
error))
return FALSE;
} else if (path) {
/* Private connections cannot use paths */
g_return_val_if_fail(!priv->private_user, FALSE);
if (!add_string_val(self, path, "client_cert", FALSE, NULL, error))
return FALSE;
}
} }
} }
/* Phase 2 private key */ /* Phase 2 private key */
added = FALSE; added = FALSE;
path = NULL;
bytes = NULL;
switch (nm_setting_802_1x_get_phase2_private_key_scheme(setting)) { switch (nm_setting_802_1x_get_phase2_private_key_scheme(setting)) {
case NM_SETTING_802_1X_CK_SCHEME_BLOB: case NM_SETTING_802_1X_CK_SCHEME_BLOB:
bytes = nm_setting_802_1x_get_phase2_private_key_blob(setting); bytes = nm_setting_802_1x_get_phase2_private_key_blob(setting);
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"private_key2",
con_uuid,
error))
return FALSE;
added = TRUE; added = TRUE;
break; break;
case NM_SETTING_802_1X_CK_SCHEME_PATH: case NM_SETTING_802_1X_CK_SCHEME_PATH:
path = nm_setting_802_1x_get_phase2_private_key_path(setting); path = nm_setting_802_1x_get_phase2_private_key_path(setting);
if (!add_string_val(self, path, "private_key2", FALSE, NULL, error)) if (priv->private_user)
return FALSE; bytes = nm_g_hash_table_lookup(files, path);
added = TRUE; added = TRUE;
break; break;
case NM_SETTING_802_1X_CK_SCHEME_PKCS11: case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
@ -1834,6 +1866,19 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
default: default:
break; break;
} }
if (bytes) {
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"private_key2",
con_uuid,
error))
return FALSE;
} else if (path) {
/* Private connections cannot use paths */
g_return_val_if_fail(!priv->private_user, FALSE);
if (!add_string_val(self, path, "private_key2", FALSE, NULL, error))
return FALSE;
}
if (added) { if (added) {
NMSetting8021xCKFormat format; NMSetting8021xCKFormat format;
@ -1857,20 +1902,16 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
/* Only add the client cert if the private key is not PKCS#12, as /* Only add the client cert if the private key is not PKCS#12, as
* wpa_supplicant configuration directs us to do. * wpa_supplicant configuration directs us to do.
*/ */
path = NULL;
bytes = NULL;
switch (nm_setting_802_1x_get_phase2_client_cert_scheme(setting)) { switch (nm_setting_802_1x_get_phase2_client_cert_scheme(setting)) {
case NM_SETTING_802_1X_CK_SCHEME_BLOB: case NM_SETTING_802_1X_CK_SCHEME_BLOB:
bytes = nm_setting_802_1x_get_phase2_client_cert_blob(setting); bytes = nm_setting_802_1x_get_phase2_client_cert_blob(setting);
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"client_cert2",
con_uuid,
error))
return FALSE;
break; break;
case NM_SETTING_802_1X_CK_SCHEME_PATH: case NM_SETTING_802_1X_CK_SCHEME_PATH:
path = nm_setting_802_1x_get_phase2_client_cert_path(setting); path = nm_setting_802_1x_get_phase2_client_cert_path(setting);
if (!add_string_val(self, path, "client_cert2", FALSE, NULL, error)) if (priv->private_user)
return FALSE; bytes = nm_g_hash_table_lookup(files, path);
break; break;
case NM_SETTING_802_1X_CK_SCHEME_PKCS11: case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
if (!add_pkcs11_uri_with_pin( if (!add_pkcs11_uri_with_pin(
@ -1886,6 +1927,19 @@ nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
default: default:
break; break;
} }
if (bytes) {
if (!nm_supplicant_config_add_blob_for_connection(self,
bytes,
"client_cert2",
con_uuid,
error))
return FALSE;
} else if (path) {
/* Private connections cannot use paths */
g_return_val_if_fail(!priv->private_user, FALSE);
if (!add_string_val(self, path, "client_cert2", FALSE, NULL, error))
return FALSE;
}
} }
} }

View file

@ -29,7 +29,7 @@ typedef struct _NMSupplicantConfigClass NMSupplicantConfigClass;
GType nm_supplicant_config_get_type(void); GType nm_supplicant_config_get_type(void);
NMSupplicantConfig *nm_supplicant_config_new(NMSupplCapMask capabilities); NMSupplicantConfig *nm_supplicant_config_new(NMSupplCapMask capabilities, const char *private_user);
guint32 nm_supplicant_config_get_ap_scan(NMSupplicantConfig *self); guint32 nm_supplicant_config_get_ap_scan(NMSupplicantConfig *self);
@ -57,6 +57,7 @@ gboolean nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig
guint32 mtu, guint32 mtu,
NMSettingWirelessSecurityPmf pmf, NMSettingWirelessSecurityPmf pmf,
NMSettingWirelessSecurityFils fils, NMSettingWirelessSecurityFils fils,
GHashTable *files,
GError **error); GError **error);
gboolean nm_supplicant_config_add_no_security(NMSupplicantConfig *self, GError **error); gboolean nm_supplicant_config_add_no_security(NMSupplicantConfig *self, GError **error);
@ -66,6 +67,7 @@ gboolean nm_supplicant_config_add_setting_8021x(NMSupplicantConfig *self,
const char *con_uuid, const char *con_uuid,
guint32 mtu, guint32 mtu,
gboolean wired, gboolean wired,
GHashTable *files,
GError **error); GError **error);
gboolean nm_supplicant_config_add_setting_macsec(NMSupplicantConfig *self, gboolean nm_supplicant_config_add_setting_macsec(NMSupplicantConfig *self,

View file

@ -98,7 +98,8 @@ build_supplicant_config(NMConnection *connection,
NMSetting8021x *s_8021x; NMSetting8021x *s_8021x;
gboolean success; gboolean success;
config = nm_supplicant_config_new(capabilities); config = nm_supplicant_config_new(capabilities,
nm_utils_get_connection_first_permissions_user(connection));
s_wifi = nm_connection_get_setting_wireless(connection); s_wifi = nm_connection_get_setting_wireless(connection);
g_assert(s_wifi); g_assert(s_wifi);
@ -120,6 +121,7 @@ build_supplicant_config(NMConnection *connection,
mtu, mtu,
pmf, pmf,
fils, fils,
NULL,
&error); &error);
} else { } else {
success = nm_supplicant_config_add_no_security(config, &error); success = nm_supplicant_config_add_no_security(config, &error);