• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /* activation-helper.c  Setuid helper for launching programs as a custom
3  *                      user. This file is security sensitive.
4  *
5  * Copyright (C) 2007 Red Hat, Inc.
6  *
7  * Licensed under the Academic Free License version 2.1
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22  *
23  */
24 
25 #include <config.h>
26 
27 #include "bus.h"
28 #include "driver.h"
29 #include "utils.h"
30 #include "desktop-file.h"
31 #include "config-parser-trivial.h"
32 #include "activation-helper.h"
33 #include "activation-exit-codes.h"
34 
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include <sys/types.h>
40 #include <pwd.h>
41 #include <grp.h>
42 
43 #include <dbus/dbus-shell.h>
44 #include <dbus/dbus-marshal-validate.h>
45 
46 static BusDesktopFile *
desktop_file_for_name(BusConfigParser * parser,const char * name,DBusError * error)47 desktop_file_for_name (BusConfigParser *parser,
48                        const char *name,
49                        DBusError  *error)
50 {
51   BusDesktopFile *desktop_file;
52   DBusList **service_dirs;
53   DBusList *link;
54   DBusError tmp_error;
55   DBusString full_path;
56   DBusString filename;
57   const char *dir;
58 
59   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
60 
61   desktop_file = NULL;
62 
63   if (!_dbus_string_init (&filename))
64     {
65       BUS_SET_OOM (error);
66       goto out_all;
67     }
68 
69   if (!_dbus_string_init (&full_path))
70     {
71       BUS_SET_OOM (error);
72       goto out_filename;
73     }
74 
75   if (!_dbus_string_append (&filename, name) ||
76       !_dbus_string_append (&filename, ".service"))
77     {
78       BUS_SET_OOM (error);
79       goto out;
80     }
81 
82   service_dirs = bus_config_parser_get_service_dirs (parser);
83   for (link = _dbus_list_get_first_link (service_dirs);
84        link != NULL;
85        link = _dbus_list_get_next_link (service_dirs, link))
86     {
87       dir = link->data;
88       _dbus_verbose ("Looking at '%s'\n", dir);
89 
90       dbus_error_init (&tmp_error);
91 
92       /* clear the path from last time */
93       _dbus_string_set_length (&full_path, 0);
94 
95       /* build the full path */
96       if (!_dbus_string_append (&full_path, dir) ||
97           !_dbus_concat_dir_and_file (&full_path, &filename))
98         {
99           BUS_SET_OOM (error);
100           goto out;
101         }
102 
103       _dbus_verbose ("Trying to load file '%s'\n", _dbus_string_get_data (&full_path));
104       desktop_file = bus_desktop_file_load (&full_path, &tmp_error);
105       if (desktop_file == NULL)
106         {
107           _DBUS_ASSERT_ERROR_IS_SET (&tmp_error);
108           _dbus_verbose ("Could not load %s: %s: %s\n",
109                          _dbus_string_get_const_data (&full_path),
110                          tmp_error.name, tmp_error.message);
111 
112           /* we may have failed if the file is not found; this is not fatal */
113           if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY))
114             {
115               dbus_move_error (&tmp_error, error);
116               /* we only bail out on OOM */
117               goto out;
118             }
119           dbus_error_free (&tmp_error);
120         }
121 
122       /* did we find the desktop file we want? */
123       if (desktop_file != NULL)
124         break;
125     }
126 
127   /* Didn't find desktop file; set error */
128   if (desktop_file == NULL)
129     {
130       dbus_set_error (error, DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND,
131                       "The name %s was not provided by any .service files",
132                       name);
133     }
134 
135 out:
136   _dbus_string_free (&full_path);
137 out_filename:
138   _dbus_string_free (&filename);
139 out_all:
140   return desktop_file;
141 }
142 
143 /* Clears the environment, except for DBUS_STARTER_x,
144  * which we hardcode to the system bus.
145  */
146 static dbus_bool_t
clear_environment(DBusError * error)147 clear_environment (DBusError *error)
148 {
149 #ifndef ACTIVATION_LAUNCHER_TEST
150   /* totally clear the environment */
151   if (!_dbus_clearenv ())
152     {
153       dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
154                       "could not clear environment\n");
155       return FALSE;
156     }
157 #endif
158 
159   /* Ensure the bus is set to system */
160   _dbus_setenv ("DBUS_STARTER_ADDRESS", DBUS_SYSTEM_BUS_DEFAULT_ADDRESS);
161   _dbus_setenv ("DBUS_STARTER_BUS_TYPE", "system");
162 
163   return TRUE;
164 }
165 
166 static dbus_bool_t
check_permissions(const char * dbus_user,DBusError * error)167 check_permissions (const char *dbus_user, DBusError *error)
168 {
169 #ifndef ACTIVATION_LAUNCHER_TEST
170   uid_t uid, euid;
171   struct passwd *pw;
172 
173   pw = NULL;
174   uid = 0;
175   euid = 0;
176 
177   /* bail out unless the dbus user is invoking the helper */
178   pw = getpwnam(dbus_user);
179   if (!pw)
180     {
181       dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,
182                       "cannot find user '%s'", dbus_user);
183       return FALSE;
184     }
185   uid = getuid();
186   if (pw->pw_uid != uid)
187     {
188       dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,
189                       "not invoked from user '%s'", dbus_user);
190       return FALSE;
191     }
192 
193   /* bail out unless we are setuid to user root */
194   euid = geteuid();
195   if (euid != 0)
196     {
197       dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,
198                       "not setuid root");
199       return FALSE;
200     }
201 #endif
202 
203   return TRUE;
204 }
205 
206 static dbus_bool_t
check_service_name(BusDesktopFile * desktop_file,const char * service_name,DBusError * error)207 check_service_name (BusDesktopFile *desktop_file,
208                     const char     *service_name,
209                     DBusError      *error)
210 {
211   char *name_tmp;
212   dbus_bool_t retval;
213 
214   retval = FALSE;
215 
216   /* try to get Name */
217   if (!bus_desktop_file_get_string (desktop_file,
218                                     DBUS_SERVICE_SECTION,
219                                     DBUS_SERVICE_NAME,
220                                     &name_tmp,
221                                     error))
222     goto failed;
223 
224   /* verify that the name is the same as the file service name */
225   if (strcmp (service_name, name_tmp) != 0)
226     {
227       dbus_set_error (error, DBUS_ERROR_SPAWN_FILE_INVALID,
228                       "Service '%s' does not match expected value", name_tmp);
229       goto failed_free;
230     }
231 
232   retval = TRUE;
233 
234 failed_free:
235   /* we don't return the name, so free it here */
236   dbus_free (name_tmp);
237 failed:
238   return retval;
239 }
240 
241 static dbus_bool_t
get_parameters_for_service(BusDesktopFile * desktop_file,const char * service_name,char ** exec,char ** user,DBusError * error)242 get_parameters_for_service (BusDesktopFile *desktop_file,
243                             const char     *service_name,
244                             char          **exec,
245                             char          **user,
246                             DBusError      *error)
247 {
248   char *exec_tmp;
249   char *user_tmp;
250 
251   exec_tmp = NULL;
252   user_tmp = NULL;
253 
254   /* check the name of the service */
255   if (!check_service_name (desktop_file, service_name, error))
256     goto failed;
257 
258   /* get the complete path of the executable */
259   if (!bus_desktop_file_get_string (desktop_file,
260                                     DBUS_SERVICE_SECTION,
261                                     DBUS_SERVICE_EXEC,
262                                     &exec_tmp,
263                                     error))
264     {
265       _DBUS_ASSERT_ERROR_IS_SET (error);
266       goto failed;
267     }
268 
269   /* get the user that should run this service - user is compulsary for system activation */
270   if (!bus_desktop_file_get_string (desktop_file,
271                                     DBUS_SERVICE_SECTION,
272                                     DBUS_SERVICE_USER,
273                                     &user_tmp,
274                                     error))
275     {
276       _DBUS_ASSERT_ERROR_IS_SET (error);
277       goto failed;
278     }
279 
280   /* only assign if all the checks passed */
281   *exec = exec_tmp;
282   *user = user_tmp;
283   return TRUE;
284 
285 failed:
286   dbus_free (exec_tmp);
287   dbus_free (user_tmp);
288   return FALSE;
289 }
290 
291 static dbus_bool_t
switch_user(char * user,DBusError * error)292 switch_user (char *user, DBusError *error)
293 {
294 #ifndef ACTIVATION_LAUNCHER_TEST
295   struct passwd *pw;
296 
297   /* find user */
298   pw = getpwnam (user);
299   if (!pw)
300     {
301       dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
302                       "cannot find user '%s'\n", user);
303       return FALSE;
304     }
305 
306   /* initialize the group access list */
307   if (initgroups (user, pw->pw_gid))
308     {
309       dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
310                       "could not initialize groups");
311       return FALSE;
312     }
313 
314   /* change to the primary group for the user */
315   if (setgid (pw->pw_gid))
316     {
317       dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
318                       "cannot setgid group %i", pw->pw_gid);
319       return FALSE;
320     }
321 
322   /* change to the user specified */
323   if (setuid (pw->pw_uid) < 0)
324     {
325       dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
326                       "cannot setuid user %i", pw->pw_uid);
327       return FALSE;
328     }
329 #endif
330   return TRUE;
331 }
332 
333 static dbus_bool_t
exec_for_correct_user(char * exec,char * user,DBusError * error)334 exec_for_correct_user (char *exec, char *user, DBusError *error)
335 {
336   char **argv;
337   int argc;
338   dbus_bool_t retval;
339 
340   argc = 0;
341   retval = TRUE;
342   argv = NULL;
343 
344   if (!switch_user (user, error))
345     return FALSE;
346 
347   /* convert command into arguments */
348   if (!_dbus_shell_parse_argv (exec, &argc, &argv, error))
349     return FALSE;
350 
351 #ifndef ACTIVATION_LAUNCHER_DO_OOM
352   /* replace with new binary, with no environment */
353   if (execv (argv[0], argv) < 0)
354     {
355       dbus_set_error (error, DBUS_ERROR_SPAWN_EXEC_FAILED,
356                       "Failed to exec: %s", argv[0]);
357       retval = FALSE;
358     }
359 #endif
360 
361   dbus_free_string_array (argv);
362   return retval;
363 }
364 
365 static dbus_bool_t
check_bus_name(const char * bus_name,DBusError * error)366 check_bus_name (const char *bus_name,
367                 DBusError  *error)
368 {
369   DBusString str;
370 
371   _dbus_string_init_const (&str, bus_name);
372   if (!_dbus_validate_bus_name (&str, 0, _dbus_string_get_length (&str)))
373     {
374       dbus_set_error (error, DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND,
375                       "bus name '%s' is not a valid bus name\n",
376                       bus_name);
377       return FALSE;
378     }
379 
380   return TRUE;
381 }
382 
383 static dbus_bool_t
get_correct_parser(BusConfigParser ** parser,DBusError * error)384 get_correct_parser (BusConfigParser **parser, DBusError *error)
385 {
386   DBusString config_file;
387   dbus_bool_t retval;
388 #ifdef ACTIVATION_LAUNCHER_TEST
389   const char *test_config_file;
390 #endif
391 
392   retval = FALSE;
393 
394 #ifdef ACTIVATION_LAUNCHER_TEST
395   test_config_file = NULL;
396 
397   /* there is no _way_ we should be setuid if this define is set.
398    * but we should be doubly paranoid and check... */
399   if (getuid() != geteuid())
400     _dbus_assert_not_reached ("dbus-daemon-launch-helper-test binary is setuid!");
401 
402   /* this is not a security hole. The environment variable is only passed in the
403    * dbus-daemon-lauch-helper-test NON-SETUID launcher */
404   test_config_file = _dbus_getenv ("TEST_LAUNCH_HELPER_CONFIG");
405   if (test_config_file == NULL)
406     {
407       dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
408                       "the TEST_LAUNCH_HELPER_CONFIG env variable is not set");
409       goto out;
410     }
411 #endif
412 
413   /* we _only_ use the predefined system config file */
414   if (!_dbus_string_init (&config_file))
415     {
416       BUS_SET_OOM (error);
417       goto out;
418     }
419 #ifndef ACTIVATION_LAUNCHER_TEST
420   if (!_dbus_string_append (&config_file, DBUS_SYSTEM_CONFIG_FILE))
421     {
422       BUS_SET_OOM (error);
423       goto out_free_config;
424     }
425 #else
426   if (!_dbus_string_append (&config_file, test_config_file))
427     {
428       BUS_SET_OOM (error);
429       goto out_free_config;
430     }
431 #endif
432 
433   /* where are we pointing.... */
434   _dbus_verbose ("dbus-daemon-activation-helper: using config file: %s\n",
435                  _dbus_string_get_const_data (&config_file));
436 
437   /* get the dbus user */
438   *parser = bus_config_load (&config_file, TRUE, NULL, error);
439   if (*parser == NULL)
440     {
441       goto out_free_config;
442     }
443 
444   /* woot */
445   retval = TRUE;
446 
447 out_free_config:
448   _dbus_string_free (&config_file);
449 out:
450   return retval;
451 }
452 
453 static dbus_bool_t
launch_bus_name(const char * bus_name,BusConfigParser * parser,DBusError * error)454 launch_bus_name (const char *bus_name, BusConfigParser *parser, DBusError *error)
455 {
456   BusDesktopFile *desktop_file;
457   char *exec, *user;
458   dbus_bool_t retval;
459 
460   exec = NULL;
461   user = NULL;
462   retval = FALSE;
463 
464   /* get the correct service file for the name we are trying to activate */
465   desktop_file = desktop_file_for_name (parser, bus_name, error);
466   if (desktop_file == NULL)
467     return FALSE;
468 
469   /* get exec and user for service name */
470   if (!get_parameters_for_service (desktop_file, bus_name, &exec, &user, error))
471     goto finish;
472 
473   _dbus_verbose ("dbus-daemon-activation-helper: Name='%s'\n", bus_name);
474   _dbus_verbose ("dbus-daemon-activation-helper: Exec='%s'\n", exec);
475   _dbus_verbose ("dbus-daemon-activation-helper: User='%s'\n", user);
476 
477   /* actually execute */
478   if (!exec_for_correct_user (exec, user, error))
479     goto finish;
480 
481   retval = TRUE;
482 
483 finish:
484   dbus_free (exec);
485   dbus_free (user);
486   bus_desktop_file_free (desktop_file);
487   return retval;
488 }
489 
490 static dbus_bool_t
check_dbus_user(BusConfigParser * parser,DBusError * error)491 check_dbus_user (BusConfigParser *parser, DBusError *error)
492 {
493   const char *dbus_user;
494 
495   dbus_user = bus_config_parser_get_user (parser);
496   if (dbus_user == NULL)
497     {
498       dbus_set_error (error, DBUS_ERROR_SPAWN_CONFIG_INVALID,
499                       "could not get user from config file\n");
500       return FALSE;
501     }
502 
503   /* check to see if permissions are correct */
504   if (!check_permissions (dbus_user, error))
505     return FALSE;
506 
507   return TRUE;
508 }
509 
510 dbus_bool_t
run_launch_helper(const char * bus_name,DBusError * error)511 run_launch_helper (const char *bus_name,
512                    DBusError  *error)
513 {
514   BusConfigParser *parser;
515   dbus_bool_t retval;
516 
517   parser = NULL;
518   retval = FALSE;
519 
520   /* clear the environment, apart from a few select settings */
521   if (!clear_environment (error))
522     goto error;
523 
524   /* check to see if we have a valid bus name */
525   if (!check_bus_name (bus_name, error))
526     goto error;
527 
528   /* get the correct parser, either the test or default parser */
529   if (!get_correct_parser (&parser, error))
530     goto error;
531 
532   /* check we are being invoked by the correct dbus user */
533   if (!check_dbus_user (parser, error))
534     goto error_free_parser;
535 
536   /* launch the bus with the service defined user */
537   if (!launch_bus_name (bus_name, parser, error))
538     goto error_free_parser;
539 
540   /* woohoo! */
541   retval = TRUE;
542 
543 error_free_parser:
544   bus_config_parser_unref (parser);
545 error:
546   return retval;
547 }
548 
549