diff options
| author | Michael Biebl <biebl@debian.org> | 2025-12-13 13:49:03 +0100 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2025-12-13 13:49:03 +0100 |
| commit | b5001976dbff19f014171eed4a482e28ae7ec0f5 (patch) | |
| tree | 64fbd4ff0dd3c92b6e78fa4172c9f57a2b20e1fa /src/libnm-core-impl | |
| parent | def61b130ab011561ed66a1638a41d59c23b4710 (diff) | |
| parent | 6de29285e533f4fec22a219013f3687edb6b7399 (diff) | |
Update upstream source from tag 'upstream/1.54.3'
Update to upstream version '1.54.3' with Debian dir 29de9f9896550eb82236dfd0adfd428a49c51043
Diffstat (limited to 'src/libnm-core-impl')
| -rw-r--r-- | src/libnm-core-impl/nm-setting-8021x.c | 97 | ||||
| -rw-r--r-- | src/libnm-core-impl/nm-setting-connection.c | 41 | ||||
| -rw-r--r-- | src/libnm-core-impl/nm-setting-private.h | 17 | ||||
| -rw-r--r-- | src/libnm-core-impl/nm-setting.c | 29 | ||||
| -rw-r--r-- | src/libnm-core-impl/nm-utils.c | 255 | ||||
| -rw-r--r-- | src/libnm-core-impl/nm-vpn-plugin-info.c | 23 |
6 files changed, 451 insertions, 11 deletions
diff --git a/src/libnm-core-impl/nm-setting-8021x.c b/src/libnm-core-impl/nm-setting-8021x.c index 4ea60729..f9333803 100644 --- a/src/libnm-core-impl/nm-setting-8021x.c +++ b/src/libnm-core-impl/nm-setting-8021x.c @@ -3134,6 +3134,86 @@ need_secrets(NMSetting *setting, gboolean check_rerequest) /*****************************************************************************/ static void +get_private_files(NMSetting *setting, GPtrArray *files) +{ + const struct { + const char *property; + NMSetting8021xCKScheme (*get_scheme_func)(NMSetting8021x *); + const char *(*get_path_func)(NMSetting8021x *); + } cert_props[] = { + {NM_SETTING_802_1X_CA_CERT, + nm_setting_802_1x_get_ca_cert_scheme, + nm_setting_802_1x_get_ca_cert_path}, + {NM_SETTING_802_1X_CLIENT_CERT, + nm_setting_802_1x_get_client_cert_scheme, + nm_setting_802_1x_get_client_cert_path}, + {NM_SETTING_802_1X_PRIVATE_KEY, + nm_setting_802_1x_get_private_key_scheme, + nm_setting_802_1x_get_private_key_path}, + {NM_SETTING_802_1X_PHASE2_CA_CERT, + nm_setting_802_1x_get_phase2_ca_cert_scheme, + nm_setting_802_1x_get_phase2_ca_cert_path}, + {NM_SETTING_802_1X_PHASE2_CLIENT_CERT, + nm_setting_802_1x_get_phase2_client_cert_scheme, + nm_setting_802_1x_get_phase2_client_cert_path}, + {NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, + nm_setting_802_1x_get_phase2_private_key_scheme, + nm_setting_802_1x_get_phase2_private_key_path}, + }; + NMSetting8021x *s_8021x = NM_SETTING_802_1X(setting); + const char *path; + guint i; + + if (NM_MORE_ASSERT_ONCE(5)) { + GObjectClass *klass; + gs_free GParamSpec **properties = NULL; + guint n_properties; + gboolean found; + guint j; + + /* Check that all the properties in the setting with flag CERT_KEY_FILE + * are listed in the table, and vice versa. */ + + klass = G_OBJECT_GET_CLASS(setting); + + properties = g_object_class_list_properties(klass, &n_properties); + for (i = 0; i < n_properties; i++) { + if (!(properties[i]->flags & NM_SETTING_PARAM_CERT_KEY_FILE)) + continue; + + found = FALSE; + for (j = 0; j < G_N_ELEMENTS(cert_props); j++) { + if (nm_streq0(properties[i]->name, cert_props[j].property)) { + found = TRUE; + break; + } + } + + nm_assert(found); + } + + for (i = 0; i < G_N_ELEMENTS(cert_props); i++) { + GParamSpec *prop; + + prop = g_object_class_find_property(klass, cert_props[i].property); + nm_assert(prop); + nm_assert(prop->flags & NM_SETTING_PARAM_CERT_KEY_FILE); + } + } + + for (i = 0; i < G_N_ELEMENTS(cert_props); i++) { + if (cert_props[i].get_scheme_func(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH) { + path = cert_props[i].get_path_func(s_8021x); + if (path) { + g_ptr_array_add(files, (gpointer) path); + } + } + } +} + +/*****************************************************************************/ + +static void get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NMSetting8021x *setting = NM_SETTING_802_1X(object); @@ -3223,8 +3303,9 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass) object_class->set_property = set_property; object_class->finalize = finalize; - setting_class->verify = verify; - setting_class->need_secrets = need_secrets; + setting_class->verify = verify; + setting_class->need_secrets = need_secrets; + setting_class->get_private_files = get_private_files; /** * NMSetting8021x:eap: @@ -3359,7 +3440,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass) obj_properties, NM_SETTING_802_1X_CA_CERT, PROP_CA_CERT, - NM_SETTING_PARAM_NONE, + NM_SETTING_PARAM_CERT_KEY_FILE, NMSetting8021xPrivate, ca_cert); @@ -3556,7 +3637,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass) obj_properties, NM_SETTING_802_1X_CLIENT_CERT, PROP_CLIENT_CERT, - NM_SETTING_PARAM_NONE, + NM_SETTING_PARAM_CERT_KEY_FILE, NMSetting8021xPrivate, client_cert); @@ -3803,7 +3884,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass) obj_properties, NM_SETTING_802_1X_PHASE2_CA_CERT, PROP_PHASE2_CA_CERT, - NM_SETTING_PARAM_NONE, + NM_SETTING_PARAM_CERT_KEY_FILE, NMSetting8021xPrivate, phase2_ca_cert); @@ -4006,7 +4087,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass) obj_properties, NM_SETTING_802_1X_PHASE2_CLIENT_CERT, PROP_PHASE2_CLIENT_CERT, - NM_SETTING_PARAM_NONE, + NM_SETTING_PARAM_CERT_KEY_FILE, NMSetting8021xPrivate, phase2_client_cert); @@ -4175,7 +4256,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass) obj_properties, NM_SETTING_802_1X_PRIVATE_KEY, PROP_PRIVATE_KEY, - NM_SETTING_PARAM_NONE, + NM_SETTING_PARAM_CERT_KEY_FILE, NMSetting8021xPrivate, private_key); @@ -4276,7 +4357,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass) obj_properties, NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, PROP_PHASE2_PRIVATE_KEY, - NM_SETTING_PARAM_NONE, + NM_SETTING_PARAM_CERT_KEY_FILE, NMSetting8021xPrivate, phase2_private_key); diff --git a/src/libnm-core-impl/nm-setting-connection.c b/src/libnm-core-impl/nm-setting-connection.c index 4829dcdc..ec3a1fed 100644 --- a/src/libnm-core-impl/nm-setting-connection.c +++ b/src/libnm-core-impl/nm-setting-connection.c @@ -431,6 +431,47 @@ nm_setting_connection_permissions_user_allowed_by_uid(NMSettingConnection *setti return _permissions_user_allowed(setting, NULL, uid); } +guint +_nm_setting_connection_get_num_permissions_users(NMSettingConnection *setting) +{ + NMSettingConnectionPrivate *priv; + guint i; + guint count = 0; + + nm_assert(NM_IS_SETTING_CONNECTION(setting)); + priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting); + + for (i = 0; priv->permissions && i < priv->permissions->len; i++) { + const Permission *permission = &nm_g_array_index(priv->permissions, Permission, i); + + if (permission->ptype == PERM_TYPE_USER) { + count++; + } + } + + return count; +} + +const char * +_nm_setting_connection_get_first_permissions_user(NMSettingConnection *setting) +{ + NMSettingConnectionPrivate *priv; + guint i; + + nm_assert(NM_IS_SETTING_CONNECTION(setting)); + priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting); + + for (i = 0; priv->permissions && i < priv->permissions->len; i++) { + const Permission *permission = &nm_g_array_index(priv->permissions, Permission, i); + + if (permission->ptype == PERM_TYPE_USER) { + return permission->item; + } + } + + return NULL; +} + /** * nm_setting_connection_add_permission: * @setting: the #NMSettingConnection diff --git a/src/libnm-core-impl/nm-setting-private.h b/src/libnm-core-impl/nm-setting-private.h index 8ee770f4..61f96789 100644 --- a/src/libnm-core-impl/nm-setting-private.h +++ b/src/libnm-core-impl/nm-setting-private.h @@ -154,6 +154,11 @@ struct _NMSettingClass { guint /* NMSettingParseFlags */ parse_flags, GError **error); + /* returns a list of certificate/key files referenced in the connection. + * When the connection is private, we need to verify that the owner of + * the connection has access to them. */ + void (*get_private_files)(NMSetting *setting, GPtrArray *files); + const struct _NMMetaSettingInfo *setting_info; }; @@ -334,6 +339,11 @@ struct _NMRange { */ #define NM_SETTING_PARAM_TO_DBUS_IGNORE_FLAGS (1 << (7 + G_PARAM_USER_SHIFT)) +/* The property can refer to a certificate or key stored on disk. As such, + * special care is needed when accessing the file for private connections. + */ +#define NM_SETTING_PARAM_CERT_KEY_FILE (1 << (8 + G_PARAM_USER_SHIFT)) + extern const NMSettInfoPropertType nm_sett_info_propert_type_setting_name; extern const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_interface_name; extern const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_ignore_i; @@ -859,9 +869,10 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p { \ GParamSpec *_param_spec; \ \ - G_STATIC_ASSERT(!NM_FLAGS_ANY((param_flags), \ - ~(NM_SETTING_PARAM_SECRET | NM_SETTING_PARAM_INFERRABLE \ - | NM_SETTING_PARAM_FUZZY_IGNORE))); \ + G_STATIC_ASSERT( \ + !NM_FLAGS_ANY((param_flags), \ + ~(NM_SETTING_PARAM_SECRET | NM_SETTING_PARAM_INFERRABLE \ + | NM_SETTING_PARAM_FUZZY_IGNORE | NM_SETTING_PARAM_CERT_KEY_FILE))); \ \ _param_spec = g_param_spec_boxed("" prop_name "", \ "", \ diff --git a/src/libnm-core-impl/nm-setting.c b/src/libnm-core-impl/nm-setting.c index 98424c76..295eabe7 100644 --- a/src/libnm-core-impl/nm-setting.c +++ b/src/libnm-core-impl/nm-setting.c @@ -2262,6 +2262,34 @@ init_from_dbus(NMSetting *setting, return TRUE; } +static void +get_private_files(NMSetting *setting, GPtrArray *files) +{ + if (NM_MORE_ASSERTS) { + GParamSpec **properties; + guint n_properties; + int i; + + properties = g_object_class_list_properties(G_OBJECT_GET_CLASS(setting), &n_properties); + for (i = 0; i < n_properties; i++) { + if (properties[i]->flags & NM_SETTING_PARAM_CERT_KEY_FILE) { + /* Certificates and keys needs special handling, see setting 802.1X */ + nm_assert_not_reached(); + } + } + g_free(properties); + } +} + +void +_nm_setting_get_private_files(NMSetting *setting, GPtrArray *files) +{ + g_return_if_fail(NM_IS_SETTING(setting)); + g_return_if_fail(files); + + NM_SETTING_GET_CLASS(setting)->get_private_files(setting, files); +} + /** * nm_setting_get_dbus_property_type: * @setting: an #NMSetting @@ -4672,6 +4700,7 @@ nm_setting_class_init(NMSettingClass *setting_class) setting_class->enumerate_values = enumerate_values; setting_class->aggregate = aggregate; setting_class->init_from_dbus = init_from_dbus; + setting_class->get_private_files = get_private_files; /** * NMSetting:name: diff --git a/src/libnm-core-impl/nm-utils.c b/src/libnm-core-impl/nm-utils.c index 6d5df98c..9a78e947 100644 --- a/src/libnm-core-impl/nm-utils.c +++ b/src/libnm-core-impl/nm-utils.c @@ -17,6 +17,7 @@ #include <linux/pkt_sched.h> #include <linux/if_infiniband.h> +#include "libnm-glib-aux/nm-io-utils.h" #include "libnm-glib-aux/nm-uuid.h" #include "libnm-glib-aux/nm-json-aux.h" #include "libnm-glib-aux/nm-str-buf.h" @@ -6195,3 +6196,257 @@ nm_utils_ensure_gtypes(void) for (meta_type = 0; meta_type < _NM_META_SETTING_TYPE_NUM; meta_type++) nm_meta_setting_infos[meta_type].get_setting_gtype(); } + +/*****************************************************************************/ + +typedef struct { + GPid pid; + GSource *child_watch_source; + GMainLoop *loop; + GError *error; + + int child_stdout; + int child_stderr; + + GSource *output_source; + GSource *error_source; + + NMStrBuf output_buffer; + NMStrBuf error_buffer; +} HelperInfo; + +static void +helper_complete(HelperInfo *info, GError *error_take) +{ + if (error_take) { + if (!info->error) + info->error = error_take; + else + g_error_free(error_take); + } + + if (info->output_source || info->error_source || info->pid != -1) { + /* Wait that the pipe is closed and process has terminated */ + return; + } + + if (info->error && info->error_buffer.len > 0) { + /* Prefer the message from stderr as it's more informative */ + g_error_free(info->error); + info->error = g_error_new(NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "%s", + nm_str_buf_get_str(&info->error_buffer)); + } + + g_main_loop_quit(info->loop); +} + +static gboolean +helper_have_err_data(int fd, GIOCondition condition, gpointer user_data) +{ + HelperInfo *info = user_data; + gssize n_read; + GError *error = NULL; + + n_read = nm_utils_fd_read(fd, &info->error_buffer); + + if (n_read > 0) + return G_SOURCE_CONTINUE; + + nm_clear_g_source_inst(&info->error_source); + nm_clear_fd(&info->child_stderr); + + if (n_read < 0) { + error = g_error_new(NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "read from process returned %d (%s)", + (int) -n_read, + nm_strerror_native((int) -n_read)); + } + + helper_complete(info, error); + return G_SOURCE_CONTINUE; +} + +static gboolean +helper_have_data(int fd, GIOCondition condition, gpointer user_data) +{ + HelperInfo *info = user_data; + gssize n_read; + GError *error = NULL; + + n_read = nm_utils_fd_read(fd, &info->output_buffer); + + if (n_read > 0) + return G_SOURCE_CONTINUE; + + nm_clear_g_source_inst(&info->output_source); + nm_clear_fd(&info->child_stdout); + + if (n_read < 0) { + error = g_error_new(NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "read from process returned %d (%s)", + (int) -n_read, + nm_strerror_native((int) -n_read)); + } + + helper_complete(info, error); + return G_SOURCE_CONTINUE; +} + +static void +helper_child_terminated(GPid pid, int status, gpointer user_data) +{ + HelperInfo *info = user_data; + gs_free char *status_desc = NULL; + GError *error = NULL; + + info->pid = -1; + nm_clear_g_source_inst(&info->child_watch_source); + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + if (!status_desc) + status_desc = nm_utils_get_process_exit_status_desc(status); + error = + g_error_new(NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "helper process %s", status_desc); + } + + helper_complete(info, error); +} + +#define RUN_CERT_DIR NMRUNDIR "/cert" + +/** + * nm_utils_copy_cert_as_user: + * @filename: the file name of the certificate or key to copy + * @user: the user to impersonate when reading the file + * @error: (nullable): return location for a #GError, or %NULL + * + * Reads @filename on behalf of user @user and writes the + * content to a new file in /run/NetworkManager/cert/. + * The new file has permission 600 and is owned by root. + * + * This function is useful for VPN plugins that run as root and need + * to verify that the user owning the connection (the one listed in the + * connection.permissions property) can access the file. + * + * Returns: (transfer full): the name of the new temporary file. Or %NULL + * if an error occurred, including when the given user can't access the + * file. + * + * Since: 1.56, 1.54.3 + */ +char * +nm_utils_copy_cert_as_user(const char *filename, const char *user, GError **error) +{ + gs_unref_bytes GBytes *bytes = NULL; + char dst_path[] = RUN_CERT_DIR "/XXXXXX"; + HelperInfo info = { + .child_stdout = -1, + .child_stderr = -1, + }; + GMainContext *context; + int fd = -1; + + g_return_val_if_fail(filename, NULL); + g_return_val_if_fail(user, NULL); + g_return_val_if_fail(!error || !*error, NULL); + + if (geteuid() != 0) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("This function needs to be called by root")); + return NULL; + } + + if (!g_spawn_async_with_pipes( + "/", + (char **) + NM_MAKE_STRV(LIBEXECDIR "/nm-libnm-helper", "read-file-as-user", filename, user), + (char **) NM_MAKE_STRV(), + G_SPAWN_CLOEXEC_PIPES | G_SPAWN_DO_NOT_REAP_CHILD, + NULL, + NULL, + &info.pid, + NULL, + &info.child_stdout, + &info.child_stderr, + error)) { + return NULL; + } + + context = g_main_context_new(); + info.loop = g_main_loop_new(context, FALSE); + + /* Watch process */ + info.child_watch_source = nm_g_child_watch_source_new(info.pid, + G_PRIORITY_DEFAULT, + helper_child_terminated, + &info, + NULL); + g_source_attach(info.child_watch_source, context); + + /* Watch stdout */ + info.output_buffer = NM_STR_BUF_INIT(0, FALSE); + info.output_source = nm_g_unix_fd_source_new(info.child_stdout, + G_IO_IN | G_IO_ERR | G_IO_HUP, + G_PRIORITY_DEFAULT, + helper_have_data, + &info, + NULL); + g_source_attach(info.output_source, context); + + /* Watch stderr */ + info.error_buffer = NM_STR_BUF_INIT(0, FALSE); + info.error_source = nm_g_unix_fd_source_new(info.child_stderr, + G_IO_IN | G_IO_ERR | G_IO_HUP, + G_PRIORITY_DEFAULT, + helper_have_err_data, + &info, + NULL); + g_source_attach(info.error_source, context); + + /* Wait termination */ + g_main_loop_run(info.loop); + g_clear_pointer(&info.loop, g_main_loop_unref); + g_clear_pointer(&context, g_main_context_unref); + + if (info.error) { + nm_str_buf_destroy(&info.output_buffer); + nm_str_buf_destroy(&info.error_buffer); + g_propagate_error(error, g_steal_pointer(&info.error)); + return NULL; + } + + /* Write the data to a new file */ + + bytes = g_bytes_new(nm_str_buf_get_str_unsafe(&info.output_buffer), info.output_buffer.len); + nm_str_buf_destroy(&info.output_buffer); + nm_str_buf_destroy(&info.error_buffer); + + mkdir(RUN_CERT_DIR, 0600); + fd = mkstemp(dst_path); + if (fd < 0) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("Failure creating the temporary file")); + return NULL; + } + nm_close(fd); + + if (!nm_utils_file_set_contents(dst_path, + g_bytes_get_data(bytes, NULL), + g_bytes_get_size(bytes), + 0600, + NULL, + NULL, + error)) { + return NULL; + } + + return g_strdup(dst_path); +} diff --git a/src/libnm-core-impl/nm-vpn-plugin-info.c b/src/libnm-core-impl/nm-vpn-plugin-info.c index 223d8ab3..04b0fb16 100644 --- a/src/libnm-core-impl/nm-vpn-plugin-info.c +++ b/src/libnm-core-impl/nm-vpn-plugin-info.c @@ -914,6 +914,29 @@ nm_vpn_plugin_info_supports_multiple(NMVpnPluginInfo *self) } /** + * nm_vpn_plugin_info_supports_safe_private_file_access: + * @self: plugin info instance + * + * Returns: %TRUE if the service supports reading files (certificates, keys) of + * private connections in a safe way (i.e. checking user permissions), or + if the service doesn't need to read any file from disk. + * + * Since: 1.56, 1.54.3 + */ +gboolean +nm_vpn_plugin_info_supports_safe_private_file_access(NMVpnPluginInfo *self) +{ + const char *s; + + g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), FALSE); + + s = nm_vpn_plugin_info_lookup_property(self, + NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, + "supports-safe-private-file-access"); + return _nm_utils_ascii_str_to_bool(s, FALSE); +} + +/** * nm_vpn_plugin_info_get_aliases: * @self: plugin info instance * |