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