1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 *
3 * Copyright (C) 2007-2010 David Zeuthen <zeuthen@gmail.com>
4 * Copyright (C) 2020 Tomas Bzatek <tbzatek@redhat.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 *
20 */
21
22 #include "config.h"
23 #include <glib/gi18n-lib.h>
24
25 #include <sys/types.h>
26 #include <pwd.h>
27 #include <grp.h>
28 #include <string.h>
29 #include <stdlib.h>
30
31 #include <glib/gstdio.h>
32
33 #include <libmount/libmount.h>
34
35 #include "udiskslogging.h"
36 #include "udiskslinuxmountoptions.h"
37 #include "udiskslinuxblockobject.h"
38 #include "udisksdaemon.h"
39 #include "udisksstate.h"
40 #include "udisksdaemonutil.h"
41 #include "udiskslinuxdevice.h"
42 #include "udisksconfigmanager.h"
43 #include "udisks-daemon-resources.h"
44
45
46 /* ---------------------------------------------------------------------------------------------------- */
47
48 static GHashTable * mount_options_parse_config_file (const gchar *filename, GError **error);
49 static GHashTable * mount_options_get_from_udev (UDisksLinuxDevice *device, GError **error);
50
51 /* ---------------------------------------------------------------------------------------------------- */
52
53 typedef struct
54 {
55 gchar **defaults;
56 gchar **allow;
57 } FSMountOptions;
58
59 static void
free_fs_mount_options(FSMountOptions * options)60 free_fs_mount_options (FSMountOptions *options)
61 {
62 if (options)
63 {
64 g_strfreev (options->defaults);
65 g_strfreev (options->allow);
66 g_free (options);
67 }
68 }
69
70 static void
strv_append_unique(gchar ** src,gchar *** dest)71 strv_append_unique (gchar **src, gchar ***dest)
72 {
73 guint src_len;
74 guint dest_len;
75 gchar **s;
76 gchar **l;
77 guint l_len = 0;
78
79 g_warn_if_fail (dest != NULL);
80
81 if (!src || g_strv_length (src) == 0)
82 return;
83
84 if (!*dest)
85 {
86 *dest = g_strdupv (src);
87 return;
88 }
89
90 src_len = g_strv_length (src);
91 dest_len = g_strv_length (*dest);
92
93 l = g_malloc (src_len * sizeof (gpointer));
94 for (s = src; *s; s++)
95 if (!g_strv_contains ((const gchar * const *) *dest, *s))
96 l[l_len++] = g_strdup (*s);
97
98 if (l_len > 0)
99 {
100 *dest = g_realloc (*dest, (dest_len + l_len + 1) * sizeof (gpointer));
101 memcpy (*dest + dest_len, l, l_len * sizeof (gpointer));
102 (*dest)[dest_len + l_len] = NULL;
103 }
104
105 g_free (l);
106 }
107
108 static void
append_fs_mount_options(const FSMountOptions * src,FSMountOptions * dest)109 append_fs_mount_options (const FSMountOptions *src, FSMountOptions *dest)
110 {
111 if (!src)
112 return;
113
114 strv_append_unique (src->defaults, &dest->defaults);
115 strv_append_unique (src->allow, &dest->allow);
116 }
117
118 /* Similar to append_fs_mount_options() but replaces the member data instead of appending */
119 static void
override_fs_mount_options(const FSMountOptions * src,FSMountOptions * dest)120 override_fs_mount_options (const FSMountOptions *src, FSMountOptions *dest)
121 {
122 if (!src)
123 return;
124
125 if (src->defaults)
126 {
127 g_strfreev (dest->defaults);
128 dest->defaults = g_strdupv (src->defaults);
129 }
130 if (src->allow)
131 {
132 g_strfreev (dest->allow);
133 dest->allow = g_strdupv (src->allow);
134 }
135 }
136
137 /* ---------------------------------------------------------------------------------------------------- */
138
139 #define MOUNT_OPTIONS_GLOBAL_CONFIG_FILE_NAME "mount_options.conf"
140
141 #define MOUNT_OPTIONS_CONFIG_GROUP_DEFAULTS "defaults"
142 #define MOUNT_OPTIONS_KEY_DEFAULTS "defaults"
143 #define MOUNT_OPTIONS_KEY_ALLOW "allow"
144 #define MOUNT_OPTIONS_ARG_UID_SELF "$UID"
145 #define MOUNT_OPTIONS_ARG_GID_SELF "$GID"
146 #define UDEV_MOUNT_OPTIONS_PREFIX "UDISKS_MOUNT_OPTIONS_"
147
148 /*
149 * compute_block_level_mount_options: <internal>
150 * @daemon: A #UDisksDaemon.
151 * @block: A #UDisksBlock.
152 * @fstype: The filesystem type to match or %NULL.
153 * @fsmo: Filesystem type specific #FSMountOptions.
154 * @fsmo_any: General ("any") #FSMountOptions.
155 *
156 * Calculate mount options for the given level of overrides. Matches the block
157 * device-specific options on top of the defaults.
158 *
159 * Returns: %TRUE when mount options were overridden, %FALSE otherwise.
160 */
161 static gboolean
compute_block_level_mount_options(GHashTable * opts,UDisksBlock * block,const gchar * fstype,FSMountOptions * fsmo,FSMountOptions * fsmo_any)162 compute_block_level_mount_options (GHashTable *opts,
163 UDisksBlock *block,
164 const gchar *fstype,
165 FSMountOptions *fsmo,
166 FSMountOptions *fsmo_any)
167 {
168 GHashTable *general_options;
169 GHashTable *block_options;
170 gboolean changed = FALSE;
171
172 /* Compute general defaults first */
173 general_options = g_hash_table_lookup (opts, MOUNT_OPTIONS_CONFIG_GROUP_DEFAULTS);
174 if (general_options)
175 {
176 FSMountOptions *o;
177
178 o = g_hash_table_lookup (general_options, MOUNT_OPTIONS_CONFIG_GROUP_DEFAULTS);
179 override_fs_mount_options (o, fsmo_any);
180 changed = changed || o != NULL;
181
182 o = fstype ? g_hash_table_lookup (general_options, fstype) : NULL;
183 override_fs_mount_options (o, fsmo);
184 changed = changed || o != NULL;
185 }
186
187 /* Match specific block device */
188 block_options = NULL;
189 if (block)
190 {
191 const gchar *block_device;
192 const gchar * const *block_symlinks;
193 GList *keys;
194 GList *l;
195
196 block_device = udisks_block_get_device (block);
197 block_symlinks = udisks_block_get_symlinks (block);
198
199 keys = g_hash_table_get_keys (opts);
200 g_warn_if_fail (keys != NULL);
201 for (l = keys; l != NULL; l = l->next)
202 {
203 if (!l->data || g_str_equal (l->data, MOUNT_OPTIONS_CONFIG_GROUP_DEFAULTS))
204 continue;
205 if (g_str_equal (l->data, block_device) ||
206 (block_symlinks && g_strv_contains (block_symlinks, l->data)))
207 {
208 block_options = g_hash_table_lookup (opts, l->data);
209 break;
210 }
211 }
212 g_list_free (keys);
213 }
214
215 /* Block device specific options should fully override "general" options per-member basis */
216 if (block_options)
217 {
218 FSMountOptions *o;
219
220 o = g_hash_table_lookup (block_options, MOUNT_OPTIONS_CONFIG_GROUP_DEFAULTS);
221 override_fs_mount_options (o, fsmo_any);
222 changed = changed || o != NULL;
223
224 o = fstype ? g_hash_table_lookup (block_options, fstype) : NULL;
225 override_fs_mount_options (o, fsmo);
226 changed = changed || o != NULL;
227 }
228
229 return changed;
230 }
231
232 /*
233 * compute_mount_options_for_fs_type: <internal>
234 * @daemon: A #UDisksDaemon.
235 * @block: A #UDisksBlock.
236 * @object: A #UDisksLinuxBlockObject.
237 * @fstype: The filesystem type to use or %NULL.
238 *
239 * Calculate mount options across different levels of overrides
240 * (builtin, global config, local user config).
241 *
242 * Returns: (transfer full): Newly allocated #FSMountOptions options. Free with free_fs_mount_options().
243 */
244 static FSMountOptions *
compute_mount_options_for_fs_type(UDisksDaemon * daemon,UDisksBlock * block,UDisksLinuxBlockObject * object,const gchar * fstype)245 compute_mount_options_for_fs_type (UDisksDaemon *daemon,
246 UDisksBlock *block,
247 UDisksLinuxBlockObject *object,
248 const gchar *fstype)
249 {
250 UDisksConfigManager *config_manager;
251 UDisksLinuxDevice *device;
252 GHashTable *builtin_opts;
253 GHashTable *overrides;
254 FSMountOptions *fsmo;
255 FSMountOptions *fsmo_any;
256 gchar *config_file_path;
257 GError *error = NULL;
258 gboolean changed = FALSE;
259
260 config_manager = udisks_daemon_get_config_manager (daemon);
261
262 /* Builtin options, two-level hashtable */
263 builtin_opts = g_object_get_data (G_OBJECT (daemon), "mount-options");
264 g_return_val_if_fail (builtin_opts != NULL, NULL);
265
266 fsmo = g_malloc0 (sizeof (FSMountOptions));
267 fsmo_any = g_malloc0 (sizeof (FSMountOptions));
268 compute_block_level_mount_options (builtin_opts, block, fstype, fsmo, fsmo_any);
269
270 /* Global config file overrides, two-level hashtable */
271 config_file_path = g_build_filename (udisks_config_manager_get_config_dir (config_manager),
272 MOUNT_OPTIONS_GLOBAL_CONFIG_FILE_NAME, NULL);
273 overrides = mount_options_parse_config_file (config_file_path, &error);
274 if (overrides)
275 {
276 changed = compute_block_level_mount_options (overrides, block, fstype, fsmo, fsmo_any);
277 g_hash_table_unref (overrides);
278 }
279 else
280 {
281 if (! g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT) /* not found */ &&
282 ! g_error_matches (error, UDISKS_ERROR, UDISKS_ERROR_NOT_SUPPORTED) /* empty file */ )
283 {
284 udisks_warning ("Error reading global mount options config file %s: %s",
285 config_file_path, error->message);
286 }
287 g_clear_error (&error);
288 }
289 g_free (config_file_path);
290
291 /* udev properties, single-level hashtable */
292 device = udisks_linux_block_object_get_device (object);
293 overrides = mount_options_get_from_udev (device, &error);
294 if (overrides)
295 {
296 FSMountOptions *o;
297
298 o = g_hash_table_lookup (overrides, MOUNT_OPTIONS_CONFIG_GROUP_DEFAULTS);
299 override_fs_mount_options (o, fsmo_any);
300 changed = changed || o != NULL;
301
302 o = fstype ? g_hash_table_lookup (overrides, fstype) : NULL;
303 override_fs_mount_options (o, fsmo);
304 changed = changed || o != NULL;
305
306 g_hash_table_unref (overrides);
307 }
308 else
309 {
310 udisks_warning ("Error getting udev mount options: %s",
311 error->message);
312 g_clear_error (&error);
313 }
314 g_object_unref (device);
315
316 /* Merge "any" and fstype-specific options */
317 append_fs_mount_options (fsmo_any, fsmo);
318 free_fs_mount_options (fsmo_any);
319 fsmo_any = NULL;
320
321 if (changed && fsmo->defaults)
322 {
323 gchar *opts = g_strjoinv (",", fsmo->defaults);
324 udisks_notice ("Using overridden mount options: %s", opts);
325 g_free (opts);
326 }
327
328 return fsmo;
329 }
330
331 /* ---------------------------------------------------------------------------------------------------- */
332
333 /* transfer-full */
334 static gchar **
parse_mount_options_string(const gchar * str,gboolean strip_empty_values)335 parse_mount_options_string (const gchar *str, gboolean strip_empty_values)
336 {
337 GPtrArray *opts;
338 char *optstr;
339 char *name;
340 size_t namesz;
341 char *value;
342 size_t valuesz;
343 int ret;
344
345 if (!str)
346 return NULL;
347
348 opts = g_ptr_array_new_with_free_func (g_free);
349 optstr = (char *)str;
350
351 while ((ret = mnt_optstr_next_option (&optstr, &name, &namesz, &value, &valuesz)) == 0)
352 {
353 gchar *opt;
354
355 if (value == NULL || (strip_empty_values && valuesz == 0))
356 {
357 opt = g_strndup (name, namesz);
358 }
359 else
360 {
361 opt = g_strdup_printf ("%.*s=%.*s", (int) namesz, name, (int) valuesz, value);
362 }
363 g_ptr_array_add (opts, opt);
364 }
365 if (ret < 0)
366 {
367 udisks_warning ("Malformed mount options string '%s' at position %zd, ignoring",
368 str, optstr - str + 1);
369 g_ptr_array_free (opts, TRUE);
370 return NULL;
371 }
372
373 g_ptr_array_add (opts, NULL);
374 return (gchar **) g_ptr_array_free (opts, FALSE);
375 }
376
377 /* transfer-full */
378 static gchar *
extract_fs_type(const gchar * key,const gchar ** group)379 extract_fs_type (const gchar *key, const gchar **group)
380 {
381 if (g_str_equal (key, MOUNT_OPTIONS_KEY_DEFAULTS) ||
382 g_str_equal (key, MOUNT_OPTIONS_KEY_ALLOW))
383 {
384 *group = key;
385 return g_strdup (MOUNT_OPTIONS_CONFIG_GROUP_DEFAULTS);
386 }
387
388 if (g_str_has_suffix (key, "_" MOUNT_OPTIONS_KEY_DEFAULTS))
389 {
390 *group = MOUNT_OPTIONS_KEY_DEFAULTS;
391 return g_strndup (key, strlen (key) - strlen (MOUNT_OPTIONS_KEY_DEFAULTS) - 1);
392 }
393 if (g_str_has_suffix (key, "_" MOUNT_OPTIONS_KEY_ALLOW))
394 {
395 *group = MOUNT_OPTIONS_KEY_ALLOW;
396 return g_strndup (key, strlen (key) - strlen (MOUNT_OPTIONS_KEY_ALLOW) - 1);
397 }
398
399 /* invalid key name */
400 *group = NULL;
401 return NULL;
402 }
403
404 static void
parse_key_value_pair(GHashTable * mount_options,const gchar * key,const gchar * value)405 parse_key_value_pair (GHashTable *mount_options, const gchar *key, const gchar *value)
406 {
407 FSMountOptions *ent;
408 gchar *fs_type;
409 const gchar *group = NULL;
410 gchar **opts;
411
412 fs_type = extract_fs_type (key, &group);
413 if (!fs_type)
414 {
415 /* invalid or malformed key detected, do not parse and ignore */
416 udisks_debug ("parse_key_value_pair: garbage key found: %s", key);
417 return;
418 }
419 g_warn_if_fail (group != NULL);
420
421 ent = g_hash_table_lookup (mount_options, fs_type);
422 if (!ent)
423 {
424 ent = g_malloc0 (sizeof (FSMountOptions));
425 g_hash_table_replace (mount_options, g_strdup (fs_type), ent);
426 }
427
428 opts = parse_mount_options_string (value,
429 /* strip empty values for _allow groups for easier matching */
430 !g_str_equal (group, MOUNT_OPTIONS_CONFIG_GROUP_DEFAULTS));
431
432 #define ASSIGN_OPTS(g,p) \
433 if (g_str_equal (group, g)) \
434 { \
435 if (ent->p) \
436 { \
437 g_warning ("mount_options_parse_group: Duplicate key '%s' detected", key); \
438 g_strfreev (ent->p); \
439 } \
440 ent->p = opts; \
441 } \
442 else
443
444 ASSIGN_OPTS (MOUNT_OPTIONS_KEY_ALLOW, allow)
445 ASSIGN_OPTS (MOUNT_OPTIONS_CONFIG_GROUP_DEFAULTS, defaults)
446 {
447 /* should be caught by extract_fs_type() already */
448 g_warning ("parse_key_value_pair: Unmatched key '%s' found, ignoring", key);
449 }
450
451 g_free (fs_type);
452 }
453
454 static GHashTable *
mount_options_parse_group(GKeyFile * key_file,const gchar * group_name,GError ** error)455 mount_options_parse_group (GKeyFile *key_file, const gchar *group_name, GError **error)
456 {
457 GHashTable *mount_options;
458 gchar **keys;
459 gsize keys_len = 0;
460
461 keys = g_key_file_get_keys (key_file, group_name, &keys_len, error);
462 g_warn_if_fail (keys != NULL);
463
464 mount_options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) free_fs_mount_options);
465 for (; keys_len > 0; keys_len--)
466 {
467 gchar *key;
468 gchar *value;
469 GError *e = NULL;
470
471 key = g_ascii_strdown (keys[keys_len - 1], -1);
472 value = g_key_file_get_string (key_file, group_name, keys[keys_len - 1], &e);
473 if (!value)
474 {
475 udisks_warning ("mount_options_parse_group: cannot retrieve value for key '%s': %s",
476 key, e->message);
477 g_error_free (e);
478 }
479 else
480 {
481 parse_key_value_pair (mount_options, key, value);
482 }
483 g_free (value);
484 g_free (key);
485 }
486
487 g_strfreev (keys);
488
489 return mount_options;
490 }
491
492 static GHashTable *
mount_options_parse_key_file(GKeyFile * key_file,GError ** error)493 mount_options_parse_key_file (GKeyFile *key_file, GError **error)
494 {
495 GHashTable *mount_options = NULL;
496 gchar **groups;
497 gsize groups_len = 0;
498
499 groups = g_key_file_get_groups (key_file, &groups_len);
500 if (groups == NULL || groups_len == 0)
501 {
502 g_set_error_literal (error, UDISKS_ERROR, UDISKS_ERROR_NOT_SUPPORTED,
503 "Failed to parse mount options: No sections found.");
504 g_strfreev (groups);
505 return NULL;
506 }
507
508 mount_options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_hash_table_destroy);
509 for (; groups_len > 0; groups_len--)
510 {
511 GHashTable *opts;
512 GError *local_error = NULL;
513 gchar *group = groups[groups_len - 1];
514
515 opts = mount_options_parse_group (key_file, group, &local_error);
516 if (! opts)
517 {
518 udisks_warning ("Failed to parse mount options section %s: %s",
519 group, local_error->message);
520 g_error_free (local_error);
521 /* ignore the whole section, continue with the rest */
522 }
523 else
524 {
525 g_hash_table_replace (mount_options, g_strdup (group), opts);
526 }
527 }
528 g_strfreev (groups);
529
530 return mount_options;
531 }
532
533 /* returns two-level hashtable with block specifics at the first level */
534 static GHashTable *
mount_options_parse_config_file(const gchar * filename,GError ** error)535 mount_options_parse_config_file (const gchar *filename, GError **error)
536 {
537 GKeyFile *key_file;
538 GHashTable *mount_options;
539
540 key_file = g_key_file_new ();
541 if (! g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, error))
542 {
543 g_key_file_free (key_file);
544 return NULL;
545 }
546
547 mount_options = mount_options_parse_key_file (key_file, error);
548 g_key_file_free (key_file);
549
550 return mount_options;
551 }
552
553 /* returns second level of mount options (not block-specific) */
554 static GHashTable *
mount_options_get_from_udev(UDisksLinuxDevice * device,GError ** error)555 mount_options_get_from_udev (UDisksLinuxDevice *device, GError **error)
556 {
557 GHashTable *mount_options;
558 const gchar * const *keys;
559
560 g_warn_if_fail (device != NULL);
561 if (!device->udev_device)
562 {
563 g_set_error_literal (error, UDISKS_ERROR, UDISKS_ERROR_FAILED,
564 "'device' is not a valid UDisksLinuxDevice");
565 return NULL;
566 }
567
568 mount_options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) free_fs_mount_options);
569
570 keys = g_udev_device_get_property_keys (device->udev_device);
571 for (; *keys; keys++)
572 if (g_str_has_prefix (*keys, UDEV_MOUNT_OPTIONS_PREFIX))
573 {
574 gchar *key;
575 const gchar *value;
576
577 key = g_ascii_strdown (*keys + strlen (UDEV_MOUNT_OPTIONS_PREFIX), -1);
578 value = g_udev_device_get_property (device->udev_device, *keys);
579 if (!value)
580 {
581 udisks_warning ("mount_options_get_from_udev: cannot retrieve value for udev property %s",
582 *keys);
583 }
584 else
585 {
586 parse_key_value_pair (mount_options, key, value);
587 }
588 g_free (key);
589 }
590
591 return mount_options;
592 }
593
594 /*
595 * udisks_linux_mount_options_get_builtin: <internal>
596 *
597 * Get built-in set of default mount options. This function will never
598 * fail, the process is aborted in case of a parse error.
599 *
600 * Returns: (transfer full) A #GHashTable with mount options.
601 */
602 /* returns two-level hashtable */
603 GHashTable *
udisks_linux_mount_options_get_builtin(void)604 udisks_linux_mount_options_get_builtin (void)
605 {
606 GResource *daemon_resource;
607 GBytes *builtin_opts_bytes;
608 GKeyFile *key_file;
609 GHashTable *mount_options;
610 GError *error = NULL;
611
612 daemon_resource = udisks_daemon_resources_get_resource ();
613 builtin_opts_bytes = g_resource_lookup_data (daemon_resource,
614 "/org/freedesktop/UDisks2/data/builtin_mount_options.conf",
615 G_RESOURCE_LOOKUP_FLAGS_NONE,
616 &error);
617 g_resource_unref (daemon_resource);
618
619 if (builtin_opts_bytes == NULL)
620 {
621 udisks_error ("Failed to read built-in mount options resource: %s", error->message);
622 g_error_free (error);
623 return NULL;
624 }
625
626 key_file = g_key_file_new ();
627 if (! g_key_file_load_from_bytes (key_file, builtin_opts_bytes, G_KEY_FILE_NONE, &error))
628 {
629 /* should never happen */
630 udisks_error ("Failed to read built-in mount options: %s", error->message);
631 g_error_free (error);
632 g_key_file_free (key_file);
633 g_bytes_unref (builtin_opts_bytes);
634 return NULL;
635 }
636
637 mount_options = mount_options_parse_key_file (key_file, &error);
638 g_key_file_free (key_file);
639 g_bytes_unref (builtin_opts_bytes);
640
641 if (mount_options == NULL)
642 {
643 /* should never happen either */
644 udisks_error ("Failed to parse built-in mount options: %s", error->message);
645 g_error_free (error);
646 }
647 else if (!g_hash_table_contains (mount_options, MOUNT_OPTIONS_CONFIG_GROUP_DEFAULTS))
648 {
649 g_hash_table_destroy (mount_options);
650 mount_options = NULL;
651 udisks_error ("Failed to parse built-in mount options: No global `defaults` section found.");
652 }
653
654 return mount_options;
655 }
656
657 /* ---------------------------------------------------------------------------------------------------- */
658
659 static gboolean
is_uid_in_gid(uid_t uid,gid_t gid)660 is_uid_in_gid (uid_t uid,
661 gid_t gid)
662 {
663 GError *error = NULL;
664 gid_t primary_gid = -1;
665 gchar *user_name = NULL;
666 static gid_t supplementary_groups[128];
667 int num_supplementary_groups = 128;
668 int n;
669
670 /* TODO: use some #define instead of hardcoding some random number like 128 */
671
672 if (! udisks_daemon_util_get_user_info (uid, &primary_gid, &user_name, &error))
673 {
674 udisks_warning ("%s", error->message);
675 g_error_free (error);
676 return FALSE;
677 }
678 if (primary_gid == gid)
679 {
680 g_free (user_name);
681 return TRUE;
682 }
683
684 if (getgrouplist (user_name, primary_gid, supplementary_groups, &num_supplementary_groups) < 0)
685 {
686 udisks_warning ("Error getting supplementary groups for uid %u: %m", uid);
687 g_free (user_name);
688 return FALSE;
689 }
690 g_free (user_name);
691
692 for (n = 0; n < num_supplementary_groups; n++)
693 {
694 if (supplementary_groups[n] == gid)
695 return TRUE;
696 }
697
698 return FALSE;
699 }
700
701 /* extracts option names from @allow that carry the @arg string as an argument */
702 static gchar **
extract_opts_with_arg(gchar ** allow,const gchar * arg)703 extract_opts_with_arg (gchar **allow,
704 const gchar *arg)
705 {
706 GPtrArray *opts;
707
708 if (allow == NULL)
709 return NULL;
710
711 opts = g_ptr_array_new ();
712 for (; *allow; allow++)
713 {
714 gchar *eq;
715
716 eq = g_strrstr (*allow, arg);
717 if (eq && eq != *allow && *(eq - 1) == '=')
718 g_ptr_array_add (opts, g_strndup (*allow, eq - *allow - 1));
719 }
720 g_ptr_array_add (opts, NULL);
721
722 return (gchar **) g_ptr_array_free (opts, FALSE);
723 }
724
725 #define VARIANT_NULL_STRING "\1"
726
727 static gboolean
is_mount_option_allowed(const FSMountOptions * fsmo,const gchar * const * allow_uid_self,const gchar * const * allow_gid_self,const gchar * option,const gchar * value,uid_t caller_uid)728 is_mount_option_allowed (const FSMountOptions *fsmo,
729 const gchar * const *allow_uid_self,
730 const gchar * const *allow_gid_self,
731 const gchar *option,
732 const gchar *value,
733 uid_t caller_uid)
734 {
735 gchar *endp;
736 uid_t uid;
737 gid_t gid;
738 gchar *s;
739
740 /* match the exact option=value string within allowed options */
741 if (fsmo && fsmo->allow && value && strlen (value) > 0)
742 {
743 s = g_strdup_printf ("%s=%s", option, value);
744 if (g_strv_contains ((const gchar * const *) fsmo->allow, s))
745 {
746 g_free (s);
747 /* not checking whether the option is in UID/GID_self as
748 * this is what was explicitly allowed by sysadmin (overrides) */
749 return TRUE;
750 }
751 g_free (s);
752 }
753
754 /* .. then check for mount options where the caller is allowed to pass
755 * in his own uid
756 */
757 if (fsmo && allow_uid_self && g_strv_contains (allow_uid_self, option))
758 {
759 if (value == NULL || strlen (value) == 0)
760 {
761 udisks_warning ("is_mount_option_allowed: option '%s' is listed within allow_uid_self but has no value",
762 option);
763 return FALSE;
764 }
765 uid = strtol (value, &endp, 10);
766 if (*endp != '\0')
767 /* malformed value string */
768 return FALSE;
769 return (uid == caller_uid);
770 }
771
772 /* .. ditto for gid
773 */
774 if (fsmo && allow_gid_self && g_strv_contains (allow_gid_self, option))
775 {
776 if (value == NULL || strlen (value) == 0)
777 {
778 udisks_warning ("is_mount_option_allowed: option '%s' is listed within allow_gid_self but has no value",
779 option);
780 return FALSE;
781 }
782 gid = strtol (value, &endp, 10);
783 if (*endp != '\0')
784 /* malformed value string */
785 return FALSE;
786 return is_uid_in_gid (caller_uid, gid);
787 }
788
789 /* the above UID/GID checks also assure that none of the options
790 * would be checked again against the _allow array */
791
792 /* match within allowed mount options */
793 if (fsmo && fsmo->allow)
794 {
795 /* simple 'option' match */
796 if (g_strv_contains ((const gchar * const *) fsmo->allow, option))
797 return TRUE;
798 }
799
800 if (g_str_has_prefix (option, "x-"))
801 {
802 return TRUE;
803 }
804
805 return FALSE;
806 }
807
808 static GVariant *
prepend_default_mount_options(const FSMountOptions * fsmo,const gchar * const * allow_uid_self,const gchar * const * allow_gid_self,uid_t caller_uid,GVariant * given_options,gboolean shared_fs)809 prepend_default_mount_options (const FSMountOptions *fsmo,
810 const gchar * const *allow_uid_self,
811 const gchar * const *allow_gid_self,
812 uid_t caller_uid,
813 GVariant *given_options,
814 gboolean shared_fs)
815 {
816 GVariantBuilder builder;
817 gint n;
818 gchar *s;
819 gid_t gid;
820 const gchar *option_string;
821
822 g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
823 if (fsmo != NULL)
824 {
825 gchar **defaults = fsmo->defaults;
826
827 for (n = 0; defaults != NULL && defaults[n] != NULL; n++)
828 {
829 const gchar *option = defaults[n];
830 const gchar *eq = strchr (option, '=');
831
832 if (eq != NULL)
833 {
834 const gchar *value = eq + 1;
835 gchar *option_name = g_strndup (option, eq - option);
836
837 /* check that 'option=value' is explicitly allowed */
838 if (value && strlen (value) > 0 && fsmo->allow &&
839 g_strv_contains ((const gchar * const *) fsmo->allow, option) &&
840 !g_str_equal (value, MOUNT_OPTIONS_ARG_UID_SELF) &&
841 !g_str_equal (value, MOUNT_OPTIONS_ARG_GID_SELF))
842 {
843 g_variant_builder_add (&builder, "{ss}", option_name, value);
844 }
845 else if (allow_uid_self && g_strv_contains (allow_uid_self, option_name))
846 {
847 /* append caller UID */
848 s = g_strdup_printf ("%u", caller_uid);
849 g_variant_builder_add (&builder, "{ss}", option_name, s);
850 g_free (s);
851 }
852 else if (allow_gid_self && g_strv_contains (allow_gid_self, option_name))
853 {
854 if (udisks_daemon_util_get_user_info (caller_uid, &gid, NULL, NULL))
855 {
856 s = g_strdup_printf ("%u", gid);
857 g_variant_builder_add (&builder, "{ss}", option_name, s);
858 g_free (s);
859 }
860 }
861 else if (shared_fs && g_str_equal (option_name, "mode"))
862 {
863 /* set different 'mode' and 'dmode' options for file systems mounted at shared
864 location (otherwise they cannot be used by anybody else so mounting them at
865 a shared location doesn't make much sense */
866 gchar *shared_mode = g_strdup (value);
867
868 /* give group and others the same permissions as to the owner
869 without the 'write' permission, but at least 'read'
870 (HINT: keep in mind that chars are ints in C and that
871 digits are ordered naturally in the ASCII table) */
872 shared_mode[2] = MAX(shared_mode[1] - 2, '4');
873 shared_mode[3] = MAX(shared_mode[1] - 2, '4');
874 g_variant_builder_add (&builder, "{ss}", option_name, shared_mode);
875 g_free (shared_mode);
876 }
877 else if (shared_fs && g_str_equal (option_name, "dmode"))
878 {
879 /* see right above */
880 /* Does any other dmode than 0555 make sense for a FS mounted
881 at a shared location? */
882 g_variant_builder_add (&builder, "{ss}", option_name, "0555");
883 }
884 else
885 {
886 g_variant_builder_add (&builder, "{ss}", option_name, value);
887 }
888 g_free (option_name);
889 }
890 else
891 g_variant_builder_add (&builder, "{ss}", option, VARIANT_NULL_STRING);
892 }
893 }
894
895 if (g_variant_lookup (given_options,
896 "options",
897 "&s", &option_string))
898 {
899 gchar **split_option_string;
900 split_option_string = g_strsplit (option_string, ",", -1);
901 for (n = 0; split_option_string[n] != NULL; n++)
902 {
903 gchar *option = split_option_string[n];
904 const gchar *eq = strchr (option, '=');
905
906 if (eq != NULL)
907 {
908 const gchar *value = eq + 1;
909 gsize opt_len = eq - option;
910
911 s = g_strndup (option, opt_len);
912 g_variant_builder_add (&builder, "{ss}", s, value);
913 g_free (s);
914 }
915 else
916 g_variant_builder_add (&builder, "{ss}", option, VARIANT_NULL_STRING);
917
918 g_free (option);
919 }
920 g_free (split_option_string);
921 }
922
923 return g_variant_builder_end (&builder);
924 }
925
926 /* ---------------------------------------------------------------------------------------------------- */
927
928 /*
929 * udisks_linux_calculate_mount_options: <internal>
930 * @daemon: A #UDisksDaemon.
931 * @block: A #UDisksBlock.
932 * @caller_uid: The uid of the caller making the request.
933 * @fs_type: The filesystem type to use or %NULL.
934 * @options: Options requested by the caller.
935 * @error: Return location for error or %NULL.
936 *
937 * Calculates the mount option string to use. Ensures (by returning an
938 * error) that only safe options are used.
939 *
940 * Returns: A string with mount options or %NULL if @error is set. Free with g_free().
941 */
942 gchar *
udisks_linux_calculate_mount_options(UDisksDaemon * daemon,UDisksBlock * block,uid_t caller_uid,const gchar * fs_type,GVariant * options,GError ** error)943 udisks_linux_calculate_mount_options (UDisksDaemon *daemon,
944 UDisksBlock *block,
945 uid_t caller_uid,
946 const gchar *fs_type,
947 GVariant *options,
948 GError **error)
949 {
950 FSMountOptions *fsmo;
951 gchar **allow_uid_self = NULL;
952 gchar **allow_gid_self = NULL;
953 GVariant *options_to_use;
954 GVariantIter iter;
955 UDisksLinuxBlockObject *object = NULL;
956 UDisksLinuxDevice *device = NULL;
957 gboolean shared_fs = FALSE;
958 gchar *options_to_use_str = NULL;
959 gchar *key, *value;
960 gchar *fs_type_l;
961 GString *str;
962
963 object = udisks_daemon_util_dup_object (block, NULL);
964 device = udisks_linux_block_object_get_device (object);
965 if (device != NULL && device->udev_device != NULL &&
966 g_udev_device_get_property_as_boolean (device->udev_device, "UDISKS_FILESYSTEM_SHARED"))
967 shared_fs = TRUE;
968
969 fs_type_l = g_ascii_strdown (fs_type, -1);
970 fsmo = compute_mount_options_for_fs_type (daemon, block, object, fs_type_l);
971 g_free (fs_type_l);
972
973 allow_uid_self = extract_opts_with_arg (fsmo->allow, MOUNT_OPTIONS_ARG_UID_SELF);
974 allow_gid_self = extract_opts_with_arg (fsmo->allow, MOUNT_OPTIONS_ARG_GID_SELF);
975
976 g_clear_object (&device);
977 g_clear_object (&object);
978
979 /* always prepend some reasonable default mount options; these are
980 * chosen here; the user can override them if he wants to
981 */
982 options_to_use = prepend_default_mount_options (fsmo,
983 (const gchar * const *) allow_uid_self,
984 (const gchar * const *) allow_gid_self,
985 caller_uid,
986 options,
987 shared_fs);
988
989 /* validate mount options */
990 str = g_string_new ("uhelper=udisks2,nodev,nosuid");
991 g_variant_iter_init (&iter, options_to_use);
992 while (g_variant_iter_next (&iter, "{&s&s}", &key, &value))
993 {
994 /* GVariant doesn't handle NULL strings gracefully */
995 if (g_str_equal (value, VARIANT_NULL_STRING))
996 value = NULL;
997 /* avoid attacks like passing "shortname=lower,uid=0" as a single mount option */
998 if (strstr (key, ",") != NULL || (value && strstr (value, ",") != NULL))
999 {
1000 g_set_error (error,
1001 UDISKS_ERROR,
1002 UDISKS_ERROR_OPTION_NOT_PERMITTED,
1003 "Malformed mount option `%s'",
1004 key);
1005 g_string_free (str, TRUE);
1006 goto out;
1007 }
1008
1009 /* first check if the mount option is allowed */
1010 if (!is_mount_option_allowed (fsmo,
1011 (const gchar * const *) allow_uid_self,
1012 (const gchar * const *) allow_gid_self,
1013 key, value, caller_uid))
1014 {
1015 if (value == NULL)
1016 {
1017 g_set_error (error,
1018 UDISKS_ERROR,
1019 UDISKS_ERROR_OPTION_NOT_PERMITTED,
1020 "Mount option `%s' is not allowed",
1021 key);
1022 }
1023 else
1024 {
1025 g_set_error (error,
1026 UDISKS_ERROR,
1027 UDISKS_ERROR_OPTION_NOT_PERMITTED,
1028 "Mount option `%s=%s' is not allowed",
1029 key, value);
1030 }
1031 g_string_free (str, TRUE);
1032 goto out;
1033 }
1034
1035 g_string_append_c (str, ',');
1036 if (value == NULL)
1037 g_string_append (str, key);
1038 else
1039 g_string_append_printf (str, "%s=%s", key, value);
1040 }
1041 options_to_use_str = g_string_free (str, FALSE);
1042
1043 out:
1044 g_variant_unref (options_to_use);
1045 free_fs_mount_options (fsmo);
1046 g_strfreev (allow_uid_self);
1047 g_strfreev (allow_gid_self);
1048
1049 g_assert (options_to_use_str == NULL || g_utf8_validate (options_to_use_str, -1, NULL));
1050
1051 return options_to_use_str;
1052 }
1053
1054 /* ---------------------------------------------------------------------------------------------------- */
1055