GCC Code Coverage Report


Directory: ./
File: libgsystemservice/peer-manager-dbus.c
Date: 2024-04-09 14:29:48
Exec Total Coverage
Lines: 42 115 36.5%
Functions: 10 16 62.5%
Branches: 15 61 24.6%

Line Branch Exec Source
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 *
3 * Copyright © 2018 Endless Mobile, Inc.
4 *
5 * SPDX-License-Identifier: LGPL-2.1-or-later
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 *
21 * Authors:
22 * - Philip Withnall <withnall@endlessm.com>
23 */
24
25 #include "config.h"
26
27 #include <glib.h>
28 #include <glib-object.h>
29 #include <glib/gi18n-lib.h>
30 #include <gio/gio.h>
31 #include <libgsystemservice/peer-manager.h>
32 #include <libgsystemservice/peer-manager-dbus.h>
33 #include <stdlib.h>
34
35
36 /**
37 * SECTION:peer-manager-dbus
38 * @short_description: D-Bus peer management and notification (implementation)
39 * @stability: Stable
40 * @include: libgsystemservice/peer-manager-dbus.h
41 *
42 * An implementation of the #GssPeerManager interface which draws its data
43 * from the D-Bus daemon. This is the only expected runtime implementation of
44 * the interface, and has only been split out from the interface to allow for
45 * easier unit testing of anything which uses it.
46 *
47 * The credentials of a peer are retrieved from the D-Bus daemon using
48 * [`GetConnectionCredentials`](https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-get-connection-credentials),
49 * and reading `/proc/$pid/cmdline` to get the absolute path to the executable
50 * for each peer, which we use as an identifier for it. This is not atomic or
51 * particularly trusted, as PIDs can be reused in the time it takes us to query
52 * the information, and processes can modify their own cmdline file, but
53 * without an LSM enabled in the kernel and dbus-daemon, it’s the best we can do
54 * for identifying processes.
55 *
56 * Since: 0.1.0
57 */
58
59 static void gss_peer_manager_dbus_peer_manager_init (GssPeerManagerInterface *iface);
60 static void gss_peer_manager_dbus_dispose (GObject *object);
61
62 static void gss_peer_manager_dbus_get_property (GObject *object,
63 guint property_id,
64 GValue *value,
65 GParamSpec *pspec);
66 static void gss_peer_manager_dbus_set_property (GObject *object,
67 guint property_id,
68 const GValue *value,
69 GParamSpec *pspec);
70
71 static void gss_peer_manager_dbus_ensure_peer_credentials_async (GssPeerManager *manager,
72 const gchar *sender,
73 GCancellable *cancellable,
74 GAsyncReadyCallback callback,
75 gpointer user_data);
76 static gchar *gss_peer_manager_dbus_ensure_peer_credentials_finish (GssPeerManager *manager,
77 GAsyncResult *result,
78 GError **error);
79 static const gchar *gss_peer_manager_dbus_get_peer_credentials (GssPeerManager *manager,
80 const gchar *sender);
81
82 /**
83 * GssPeerManagerDBus:
84 *
85 * An implementation of #GssPeerManager.
86 *
87 * Since: 0.1.0
88 */
89 struct _GssPeerManagerDBus
90 {
91 GObject parent;
92
93 GDBusConnection *connection; /* (owned) */
94
95 /* Hold the watch IDs of all peers who have added entries at some point. */
96 GPtrArray *peer_watch_ids; /* (owned) */
97
98 /* Cache of peer credentials (currently only the executable path of each peer). */
99 GHashTable *peer_credentials; /* (owned) (element-type utf8 filename) */
100 };
101
102 typedef enum
103 {
104 PROP_CONNECTION = 1,
105 } GssPeerManagerDBusProperty;
106
107
6/7
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 3 times.
12 G_DEFINE_TYPE_WITH_CODE (GssPeerManagerDBus, gss_peer_manager_dbus, G_TYPE_OBJECT,
108 G_IMPLEMENT_INTERFACE (GSS_TYPE_PEER_MANAGER,
109 gss_peer_manager_dbus_peer_manager_init))
110 static void
111 1 gss_peer_manager_dbus_class_init (GssPeerManagerDBusClass *klass)
112 {
113 1 GObjectClass *object_class = (GObjectClass *) klass;
114 1 GParamSpec *props[PROP_CONNECTION + 1] = { NULL, };
115
116 1 object_class->dispose = gss_peer_manager_dbus_dispose;
117 1 object_class->get_property = gss_peer_manager_dbus_get_property;
118 1 object_class->set_property = gss_peer_manager_dbus_set_property;
119
120 /**
121 * GssPeerManagerDBus:connection:
122 *
123 * D-Bus connection to use for retrieving peer credentials.
124 *
125 * Since: 0.1.0
126 */
127 1 props[PROP_CONNECTION] =
128 1 g_param_spec_object ("connection", "Connection",
129 "D-Bus connection to use for retrieving peer credentials.",
130 G_TYPE_DBUS_CONNECTION,
131 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
132
133 1 g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props);
134 1 }
135
136 static void
137 1 gss_peer_manager_dbus_peer_manager_init (GssPeerManagerInterface *iface)
138 {
139 1 iface->ensure_peer_credentials_async = gss_peer_manager_dbus_ensure_peer_credentials_async;
140 1 iface->ensure_peer_credentials_finish = gss_peer_manager_dbus_ensure_peer_credentials_finish;
141 1 iface->get_peer_credentials = gss_peer_manager_dbus_get_peer_credentials;
142 1 }
143
144 static void
145 watcher_id_free (gpointer data)
146 {
147 g_bus_unwatch_name (GPOINTER_TO_UINT (data));
148 }
149
150 static void
151 1 gss_peer_manager_dbus_init (GssPeerManagerDBus *self)
152 {
153 1 self->peer_watch_ids = g_ptr_array_new_with_free_func (watcher_id_free);
154 1 self->peer_credentials = g_hash_table_new_full (g_str_hash, g_str_equal,
155 g_free, g_free);
156 1 }
157
158 static void
159 1 gss_peer_manager_dbus_dispose (GObject *object)
160 {
161 1 GssPeerManagerDBus *self = GSS_PEER_MANAGER_DBUS (object);
162
163
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 g_clear_object (&self->connection);
164
165
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 g_clear_pointer (&self->peer_credentials, g_hash_table_unref);
166
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 g_clear_pointer (&self->peer_watch_ids, g_ptr_array_unref);
167
168 /* Chain up to the parent class */
169 1 G_OBJECT_CLASS (gss_peer_manager_dbus_parent_class)->dispose (object);
170 1 }
171
172 static void
173 gss_peer_manager_dbus_get_property (GObject *object,
174 guint property_id,
175 GValue *value,
176 GParamSpec *pspec)
177 {
178 GssPeerManagerDBus *self = GSS_PEER_MANAGER_DBUS (object);
179
180 switch ((GssPeerManagerDBusProperty) property_id)
181 {
182 case PROP_CONNECTION:
183 g_value_set_object (value, self->connection);
184 break;
185 default:
186 g_assert_not_reached ();
187 }
188 }
189
190 static void
191 1 gss_peer_manager_dbus_set_property (GObject *object,
192 guint property_id,
193 const GValue *value,
194 GParamSpec *pspec)
195 {
196 1 GssPeerManagerDBus *self = GSS_PEER_MANAGER_DBUS (object);
197
198
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 switch ((GssPeerManagerDBusProperty) property_id)
199 {
200 1 case PROP_CONNECTION:
201 /* Construct only. */
202
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 g_assert (self->connection == NULL);
203 1 self->connection = g_value_dup_object (value);
204 1 break;
205 default:
206 g_assert_not_reached ();
207 }
208 1 }
209
210 static void
211 peer_vanished_cb (GDBusConnection *connection,
212 const gchar *name,
213 gpointer user_data)
214 {
215 GssPeerManagerDBus *self = GSS_PEER_MANAGER_DBUS (user_data);
216
217 g_debug ("%s: Removing peer credentials for ‘%s’ from cache", G_STRFUNC, name);
218 if (g_hash_table_remove (self->peer_credentials, name))
219 {
220 /* Notify users of this API. */
221 g_signal_emit_by_name (self, "peer-vanished", name);
222 }
223 }
224
225 /* An async function for getting credentials for D-Bus peers, either by querying
226 * the bus, or by getting them from a cache. */
227 static void ensure_peer_credentials_cb (GObject *obj,
228 GAsyncResult *result,
229 gpointer user_data);
230
231 static void
232 gss_peer_manager_dbus_ensure_peer_credentials_async (GssPeerManager *manager,
233 const gchar *sender,
234 GCancellable *cancellable,
235 GAsyncReadyCallback callback,
236 gpointer user_data)
237 {
238 GssPeerManagerDBus *self = GSS_PEER_MANAGER_DBUS (manager);
239
240 g_autoptr(GTask) task = g_task_new (self, cancellable, callback, user_data);
241 g_task_set_source_tag (task, (gpointer) gss_peer_manager_dbus_ensure_peer_credentials_async);
242 g_task_set_task_data (task, g_strdup (sender), g_free);
243
244 /* Look up information about the sender so that we can (for example)
245 * prioritise downloads by sender. */
246 const gchar *peer_path = gss_peer_manager_get_peer_credentials (manager, sender);
247
248 if (peer_path != NULL)
249 {
250 g_debug ("%s: Found credentials in cache; path is ‘%s’",
251 G_STRFUNC, peer_path);
252 g_task_return_pointer (task, g_strdup (peer_path), g_free);
253 }
254 else
255 {
256 /* Watch the peer so we can know if/when it disappears. */
257 guint watch_id = g_bus_watch_name_on_connection (self->connection, sender,
258 G_BUS_NAME_WATCHER_FLAGS_NONE,
259 NULL, peer_vanished_cb,
260 self, NULL);
261 g_ptr_array_add (self->peer_watch_ids, GUINT_TO_POINTER (watch_id));
262
263 /* And query for its credentials. */
264 g_dbus_connection_call (self->connection, "org.freedesktop.DBus", "/",
265 "org.freedesktop.DBus", "GetConnectionCredentials",
266 g_variant_new ("(s)", sender),
267 G_VARIANT_TYPE ("(a{sv})"),
268 G_DBUS_CALL_FLAGS_NONE,
269 -1 /* default timeout */,
270 cancellable,
271 ensure_peer_credentials_cb, g_steal_pointer (&task));
272 }
273 }
274
275 static void
276 ensure_peer_credentials_cb (GObject *obj,
277 GAsyncResult *result,
278 gpointer user_data)
279 {
280 g_autoptr(GTask) task = G_TASK (user_data);
281 GssPeerManagerDBus *self = GSS_PEER_MANAGER_DBUS (g_task_get_source_object (task));
282 GDBusConnection *connection = G_DBUS_CONNECTION (obj);
283 const gchar *sender = g_task_get_task_data (task);
284 g_autoptr(GError) local_error = NULL;
285
286 /* Finish looking up the sender. */
287 g_autoptr(GVariant) retval = NULL;
288 retval = g_dbus_connection_call_finish (connection, result, &local_error);
289
290 if (retval == NULL)
291 {
292 g_task_return_error (task, g_steal_pointer (&local_error));
293 return;
294 }
295
296 /* From the credentials information from D-Bus, we can get the process ID,
297 * and then look up the process name. Note that this is racy (the process
298 * ID may get recycled between GetConnectionCredentials() returning and us
299 * querying the kernel for the process name), but there’s nothing we can
300 * do about that. The correct approach is to use an LSM label as returned
301 * by GetConnectionCredentials(), but EOS doesn’t support any LSM.
302 * We would look at /proc/$pid/exe, but that requires elevated privileges
303 * (CAP_SYS_PTRACE, but I can’t get that working; so it would require root
304 * privileges). Instead, we look at /proc/$pid/cmdline, which is accessible
305 * by all. Unfortunately, it is also forgeable. */
306 guint process_id;
307
308 g_autoptr(GVariant) credentials = g_variant_get_child_value (retval, 0);
309
310 if (!g_variant_lookup (credentials, "ProcessID", "u", &process_id))
311 {
312 g_task_return_new_error (task, GSS_PEER_MANAGER_ERROR,
313 GSS_PEER_MANAGER_ERROR_IDENTIFYING_PEER,
314 _("Process ID for peer ‘%s’ could not be determined"),
315 sender);
316 return;
317 }
318
319 g_autofree gchar *pid_str = g_strdup_printf ("%u", process_id);
320 g_autofree gchar *proc_pid_cmdline = g_build_filename ("/proc", pid_str, "cmdline", NULL);
321 g_autofree gchar *cmdline = NULL;
322
323 g_debug ("%s: Getting contents of ‘%s’", G_STRFUNC, proc_pid_cmdline);
324
325 /* Assume the path is always the first nul-terminated segment. */
326 if (!g_file_get_contents (proc_pid_cmdline, &cmdline, NULL, &local_error))
327 {
328 g_task_return_new_error (task, GSS_PEER_MANAGER_ERROR,
329 GSS_PEER_MANAGER_ERROR_IDENTIFYING_PEER,
330 _("Executable path for peer ‘%s’ (process ID: %s) "
331 "could not be determined: %s"),
332 sender, pid_str, local_error->message);
333 return;
334 }
335
336 /* Resolve to an absolute path, since what we get back might not be absolute. */
337 g_autofree gchar *sender_path = g_find_program_in_path (cmdline);
338
339 if (sender_path == NULL)
340 {
341 g_autofree gchar *message =
342 g_strdup_printf (_("Path ‘%s’ could not be resolved"), cmdline);
343 g_task_return_new_error (task, GSS_PEER_MANAGER_ERROR,
344 GSS_PEER_MANAGER_ERROR_IDENTIFYING_PEER,
345 _("Executable path for peer ‘%s’ (process ID: %s) "
346 "could not be determined: %s"),
347 sender, pid_str, message);
348 return;
349 }
350
351 g_debug ("%s: Got credentials from D-Bus daemon; path is ‘%s’ (resolved from ‘%s’)",
352 G_STRFUNC, sender_path, cmdline);
353
354 g_hash_table_replace (self->peer_credentials,
355 g_strdup (sender), g_strdup (sender_path));
356
357 g_task_return_pointer (task, g_steal_pointer (&sender_path), g_free);
358 }
359
360 static gchar *
361 gss_peer_manager_dbus_ensure_peer_credentials_finish (GssPeerManager *manager,
362 GAsyncResult *result,
363 GError **error)
364 {
365 g_return_val_if_fail (g_task_is_valid (result, manager), NULL);
366 g_return_val_if_fail (g_async_result_is_tagged (result, (gpointer) gss_peer_manager_dbus_ensure_peer_credentials_async), NULL);
367
368 return g_task_propagate_pointer (G_TASK (result), error);
369 }
370
371 static const gchar *
372 1 gss_peer_manager_dbus_get_peer_credentials (GssPeerManager *manager,
373 const gchar *sender)
374 {
375 1 GssPeerManagerDBus *self = GSS_PEER_MANAGER_DBUS (manager);
376
377 1 g_debug ("%s: Querying credentials for peer ‘%s’", G_STRFUNC, sender);
378 1 return g_hash_table_lookup (self->peer_credentials, sender);
379 }
380
381 /**
382 * gss_peer_manager_dbus_new:
383 * @connection: a #GDBusConnection
384 *
385 * Create a #GssPeerManagerDBus object to wrap the given existing @connection.
386 *
387 * Returns: (transfer full): a new #GssPeerManagerDBus wrapping @connection
388 * Since: 0.1.0
389 */
390 GssPeerManagerDBus *
391 1 gss_peer_manager_dbus_new (GDBusConnection *connection)
392 {
393
4/8
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
1 g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
394
395 1 return g_object_new (GSS_TYPE_PEER_MANAGER_DBUS,
396 "connection", connection,
397 NULL);
398 }
399