From eff8330b579c07f1f5338f50a459709727d690cf Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 23 Feb 2026 18:09:12 +0100 Subject: [PATCH 1/3] libnm-core: add missing flags check in .to_dbus_function() Properties that define a .to_dbus_function() as a D-Bus override, need to return early if the flags only ask to serialize secrets. Fixes: 7fb23b0a62a0 ('libnm: add NMIPRoutingRule API') --- src/libnm-core-impl/nm-setting-ip-config.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libnm-core-impl/nm-setting-ip-config.c b/src/libnm-core-impl/nm-setting-ip-config.c index 1aecc20c83..f4ee094ca4 100644 --- a/src/libnm-core-impl/nm-setting-ip-config.c +++ b/src/libnm-core-impl/nm-setting-ip-config.c @@ -5079,6 +5079,9 @@ routing_rules_to_dbus(_NM_SETT_INFO_PROP_TO_DBUS_FCN_ARGS _nm_nil) gboolean any = FALSE; guint i; + if (!_nm_connection_serialize_non_secret(flags)) + return NULL; + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(self); if (!priv->routing_rules || priv->routing_rules->len == 0) From db0825a110b24b755d3dab5df4381959b693ba9e Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 23 Feb 2026 17:21:55 +0100 Subject: [PATCH 2/3] settings: accept not-saved secrets from agents without modify-system The "modify.system" polkit permission allows a user to modify settings for connection profiles that belong to all users. For this reason, when an agent returns system secrets (i.e. secrets that are going to be stored to disk), NetworkManager checks that the agent has the modify.system permission. If a secret has the AGENT_OWNED flag, it's stored in the agent itself. If the secret has the NOT_SAVED flag, it will be asked to users at the beginning of every connection attempt. In both those cases the profile is not modified and there is no need for the modify.system permission. Fix the check to also consider the NOT_SAVED flag. --- src/core/settings/nm-settings-connection.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/settings/nm-settings-connection.c b/src/core/settings/nm-settings-connection.c index d5611e7640..7ed3712b47 100644 --- a/src/core/settings/nm-settings-connection.c +++ b/src/core/settings/nm-settings-connection.c @@ -781,7 +781,8 @@ validate_secret_flags(NMConnection *connection, GVariant *secrets, ForEachSecret static gboolean secret_is_system_owned(NMSettingSecretFlags flags, gpointer user_data) { - return !NM_FLAGS_HAS(flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED); + return !NM_FLAGS_ANY(flags, + NM_SETTING_SECRET_FLAG_AGENT_OWNED | NM_SETTING_SECRET_FLAG_NOT_SAVED); } static void From 024360bffa1d0848f2acb0d4eabefedf1b5f8787 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 23 Feb 2026 17:23:23 +0100 Subject: [PATCH 3/3] settings: fix check on existing system secrets The previous check was based only on the presence of a non-NULL "existing_secrets" GVariant. That GVariant is created via: nm_connection_to_dbus(nm_settings_connection_get_connection(self), NM_CONNECTION_SERIALIZE_WITH_SECRETS_SYSTEM_OWNED) The function returns a GVariant containing a first-level dictionary for each setting, even for those that doesn't contain any secrets. As a result, the check was requiring the system.modify permission even if there weren't any cached secrets to send to the agent. Fix the check to actually check for the presence of any secrets in the cached dictionary. Some connection types have a third-level dictionary that can be empty, for example VPNs have vpn.secrets. --- src/core/settings/nm-agent-manager.c | 35 +++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/core/settings/nm-agent-manager.c b/src/core/settings/nm-agent-manager.c index 19b3cfcbaf..ce7dbab2f0 100644 --- a/src/core/settings/nm-agent-manager.c +++ b/src/core/settings/nm-agent-manager.c @@ -1083,6 +1083,39 @@ _con_get_request_start_validated(NMAuthChain *chain, _con_get_request_start_proceed(req, req->con.current_has_modify); } +static gboolean +_req_has_existing_secrets(Request *req) +{ + GVariantIter iter; + const char *setting_name; + GVariant *setting_dict; + gboolean has; + + if (!req->con.get.existing_secrets) + return FALSE; + + nm_assert(g_variant_is_of_type(req->con.get.existing_secrets, NM_VARIANT_TYPE_CONNECTION)); + + g_variant_iter_init(&iter, req->con.get.existing_secrets); + while (g_variant_iter_next(&iter, "{&s@a{sv}}", &setting_name, &setting_dict)) { + GVariantIter setting_iter; + GVariant *val; + + g_variant_iter_init(&setting_iter, setting_dict); + while (g_variant_iter_next(&setting_iter, "{&sv}", NULL, &val)) { + has = !g_variant_is_container(val) || g_variant_n_children(val) > 0; + g_variant_unref(val); + if (has) { + g_variant_unref(setting_dict); + return TRUE; + } + } + g_variant_unref(setting_dict); + } + + return FALSE; +} + static void _con_get_request_start(Request *req) { @@ -1103,7 +1136,7 @@ _con_get_request_start(Request *req) * unprivileged users. */ if ((req->con.get.flags != NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE) - && (req->con.get.existing_secrets + && (_req_has_existing_secrets(req) || _nm_connection_aggregate(req->con.connection, NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS, NULL))) {