From f4082146e91014c56c6215fb4e471f9f1baa8f60 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Tue, 24 Jul 2007 12:01:32 +0000 Subject: 2007-07-24 Richard Hughes * bus/activation-helper-bin.c: (convert_error_to_exit_code), (main): * bus/activation-helper.c: (desktop_file_for_name), (clear_environment), (check_permissions), (check_service_name), (get_parameters_for_service), (switch_user), (exec_for_correct_user), (check_bus_name), (get_correct_parser), (launch_bus_name), (check_dbus_user), (run_launch_helper): * bus/activation-helper.h: Add the initial launch-helper. This is split into a main section and a binary loader that allows us to lauch the main section in another test harness to do stuff like OOM testing. No build glue yet. --- bus/activation-helper-bin.c | 92 ++++++++ bus/activation-helper.c | 556 ++++++++++++++++++++++++++++++++++++++++++++ bus/activation-helper.h | 31 +++ 3 files changed, 679 insertions(+) create mode 100644 bus/activation-helper-bin.c create mode 100644 bus/activation-helper.c create mode 100644 bus/activation-helper.h (limited to 'bus') diff --git a/bus/activation-helper-bin.c b/bus/activation-helper-bin.c new file mode 100644 index 00000000..6b9ec1f5 --- /dev/null +++ b/bus/activation-helper-bin.c @@ -0,0 +1,92 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* activation-helper-bin.c Setuid helper for launching programs as a custom + * user. This file is security sensitive. + * + * Copyright (C) 2007 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#include "utils.h" +#include "activation-helper.h" +#include "activation-exit-codes.h" + +#include +#include +#include + +static int +convert_error_to_exit_code (DBusError *error) +{ + if (dbus_error_has_name (error, DBUS_ERROR_NO_MEMORY)) + return BUS_SPAWN_EXIT_CODE_NO_MEMORY; + + if (dbus_error_has_name (error, DBUS_ERROR_SPAWN_CONFIG_INVALID)) + return BUS_SPAWN_EXIT_CODE_CONFIG_INVALID; + + if (dbus_error_has_name (error, DBUS_ERROR_SPAWN_SETUP_FAILED)) + return BUS_SPAWN_EXIT_CODE_SETUP_FAILED; + + if (dbus_error_has_name (error, DBUS_ERROR_SPAWN_SERVICE_INVALID)) + return BUS_SPAWN_EXIT_CODE_SERVICE_NOT_FOUND; + + if (dbus_error_has_name (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID)) + return BUS_SPAWN_EXIT_CODE_PERMISSIONS_INVALID; + + if (dbus_error_has_name (error, DBUS_ERROR_SPAWN_FILE_INVALID)) + return BUS_SPAWN_EXIT_CODE_FILE_INVALID; + + if (dbus_error_has_name (error, DBUS_ERROR_SPAWN_EXEC_FAILED)) + return BUS_SPAWN_EXIT_CODE_EXEC_FAILED; + + /* should we assert? */ + return BUS_SPAWN_EXIT_CODE_SETUP_FAILED; +} + +int +main (int argc, char **argv) +{ + DBusError error; + int retval; + + /* default is all okay */ + retval = 0; + + /* have we used a help option or not specified the correct arguments? */ + if (argc != 2 || + strcmp (argv[1], "--help") == 0 || + strcmp (argv[1], "-h") == 0 || + strcmp (argv[1], "-?") == 0) + { + fprintf (stderr, "dbus-daemon-activation-helper service.to.activate\n"); + exit (0); + } + + dbus_error_init (&error); + if (!run_launch_helper (argv[1], &error)) + { + /* convert error to an exit code */ + retval = convert_error_to_exit_code (&error); + dbus_error_free (&error); + } + + return retval; +} + diff --git a/bus/activation-helper.c b/bus/activation-helper.c new file mode 100644 index 00000000..1636660a --- /dev/null +++ b/bus/activation-helper.c @@ -0,0 +1,556 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* activation-helper.c Setuid helper for launching programs as a custom + * user. This file is security sensitive. + * + * Copyright (C) 2007 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#include "bus.h" +#include "driver.h" +#include "utils.h" +#include "desktop-file.h" +#include "config-parser-trivial.h" +#include "activation-helper.h" +#include "activation-exit-codes.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static BusDesktopFile * +desktop_file_for_name (BusConfigParser *parser, + const char *name, + DBusError *error) +{ + BusDesktopFile *desktop_file; + DBusList **service_dirs; + DBusList *link; + DBusError tmp_error; + DBusString full_path; + DBusString filename; + const char *dir; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + desktop_file = NULL; + + if (!_dbus_string_init (&filename)) + { + BUS_SET_OOM (error); + goto out_all; + } + + if (!_dbus_string_init (&full_path)) + { + BUS_SET_OOM (error); + goto out_filename; + } + + if (!_dbus_string_append (&filename, name) || + !_dbus_string_append (&filename, ".service")) + { + BUS_SET_OOM (error); + goto out; + } + + service_dirs = bus_config_parser_get_service_dirs (parser); + for (link = _dbus_list_get_first_link (service_dirs); + link != NULL; + link = _dbus_list_get_next_link (service_dirs, link)) + { + dir = link->data; + _dbus_verbose ("Looking at '%s'\n", dir); + + dbus_error_init (&tmp_error); + + /* clear the path from last time */ + _dbus_string_set_length (&full_path, 0); + + /* build the full path */ + if (!_dbus_string_append (&full_path, dir) || + !_dbus_concat_dir_and_file (&full_path, &filename)) + { + BUS_SET_OOM (error); + goto out; + } + + _dbus_verbose ("Trying to load file '%s'\n", _dbus_string_get_data (&full_path)); + desktop_file = bus_desktop_file_load (&full_path, &tmp_error); + if (desktop_file == NULL) + { + _DBUS_ASSERT_ERROR_IS_SET (&tmp_error); + _dbus_verbose ("Could not load %s: %s: %s\n", + _dbus_string_get_const_data (&full_path), + tmp_error.name, tmp_error.message); + + /* we may have failed if the file is not found; this is not fatal */ + if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY)) + { + dbus_move_error (&tmp_error, error); + /* we only bail out on OOM */ + goto out; + } + dbus_error_free (&tmp_error); + } + + /* did we find the desktop file we want? */ + if (desktop_file != NULL) + break; + } + + /* Didn't find desktop file; set error */ + if (desktop_file == NULL) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND, + "The name %s was not provided by any .service files", + name); + } + +out: + _dbus_string_free (&full_path); +out_filename: + _dbus_string_free (&filename); +out_all: + return desktop_file; +} + +/* Cleares the environment, except for DBUS_VERBOSE and DBUS_STARTER_x */ +static dbus_bool_t +clear_environment (DBusError *error) +{ + const char *debug_env = NULL; + const char *starter_env = NULL; + +#ifdef DBUS_ENABLE_VERBOSE_MODE + /* are we debugging */ + debug_env = _dbus_getenv ("DBUS_VERBOSE"); +#endif + + /* we save the starter */ + starter_env = _dbus_getenv ("DBUS_STARTER_ADDRESS"); + +#ifndef ACTIVATION_LAUNCHER_TEST + /* totally clear the environment */ + if (!_dbus_clearenv ()) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, + "could not clear environment\n"); + return FALSE; + } +#endif + +#ifdef DBUS_ENABLE_VERBOSE_MODE + /* restore the debugging environment setting if set */ + if (debug_env) + _dbus_setenv ("DBUS_VERBOSE", debug_env); +#endif + + /* restore the starter */ + if (starter_env) + _dbus_setenv ("DBUS_STARTER_ADDRESS", starter_env); + + /* set the type, which must be system if we got this far */ + _dbus_setenv ("DBUS_STARTER_BUS_TYPE", "system"); + + return TRUE; +} + +static dbus_bool_t +check_permissions (const char *dbus_user, DBusError *error) +{ + uid_t uid, euid; + struct passwd *pw; + + pw = NULL; + uid = 0; + euid = 0; + +#ifndef ACTIVATION_LAUNCHER_TEST + /* bail out unless the dbus user is invoking the helper */ + pw = getpwnam(dbus_user); + if (!pw) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID, + "cannot find user '%s'", dbus_user); + return FALSE; + } + uid = getuid(); + if (pw->pw_uid != uid) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID, + "not invoked from user '%s'", dbus_user); + return FALSE; + } + + /* bail out unless we are setuid to user root */ + euid = geteuid(); + if (euid != 0) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID, + "not setuid root"); + return FALSE; + } +#endif + + return TRUE; +} + +static dbus_bool_t +check_service_name (BusDesktopFile *desktop_file, + const char *service_name, + DBusError *error) +{ + char *name_tmp; + dbus_bool_t retval; + + retval = FALSE; + + /* try to get Name */ + if (!bus_desktop_file_get_string (desktop_file, + DBUS_SERVICE_SECTION, + DBUS_SERVICE_NAME, + &name_tmp, + error)) + goto failed; + + /* verify that the name is the same as the file service name */ + if (strcmp (service_name, name_tmp) != 0) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_FILE_INVALID, + "Service '%s' does not match expected value", name_tmp); + goto failed_free; + } + + retval = TRUE; + +failed_free: + /* we don't return the name, so free it here */ + dbus_free (name_tmp); +failed: + return retval; +} + +static dbus_bool_t +get_parameters_for_service (BusDesktopFile *desktop_file, + const char *service_name, + char **exec, + char **user, + DBusError *error) +{ + char *exec_tmp; + char *user_tmp; + + exec_tmp = NULL; + user_tmp = NULL; + + /* check the name of the service */ + if (!check_service_name (desktop_file, service_name, error)) + goto failed; + + /* get the complete path of the executable */ + if (!bus_desktop_file_get_string (desktop_file, + DBUS_SERVICE_SECTION, + DBUS_SERVICE_EXEC, + &exec_tmp, + error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed; + } + + /* get the user that should run this service - user is compulsary for system activation */ + if (!bus_desktop_file_get_string (desktop_file, + DBUS_SERVICE_SECTION, + DBUS_SERVICE_USER, + &user_tmp, + error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed; + } + + /* only assign if all the checks passed */ + *exec = exec_tmp; + *user = user_tmp; + return TRUE; + +failed: + dbus_free (exec_tmp); + dbus_free (user_tmp); + return FALSE; +} + +static dbus_bool_t +switch_user (char *user, DBusError *error) +{ +#ifndef ACTIVATION_LAUNCHER_TEST + struct passwd *pw; + + /* find user */ + pw = getpwnam (user); + if (!pw) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, + "cannot find user '%s'\n", user); + return FALSE; + } + + /* initialize the group access list */ + if (initgroups (user, pw->pw_gid)) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, + "could not initialize groups"); + return FALSE; + } + + /* change to the primary group for the user */ + if (setgid (pw->pw_gid)) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, + "cannot setgid group %i", pw->pw_gid); + return FALSE; + } + + /* change to the user specified */ + if (setuid (pw->pw_uid) < 0) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, + "cannot setuid user %i", pw->pw_uid); + return FALSE; + } +#endif + return TRUE; +} + +static dbus_bool_t +exec_for_correct_user (char *exec, char *user, DBusError *error) +{ + char **argv; + int argc; + dbus_bool_t retval; + + argc = 0; + retval = TRUE; + argv = NULL; + + if (!switch_user (user, error)) + return FALSE; + + /* convert command into arguments */ + if (!_dbus_shell_parse_argv (exec, &argc, &argv, error)) + return FALSE; + +#ifndef ACTIVATION_LAUNCHER_DO_OOM + /* replace with new binary, with no environment */ + if (execv (argv[0], argv) < 0) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_EXEC_FAILED, + "Failed to exec: %s", argv[0]); + retval = FALSE; + } +#endif + + dbus_free_string_array (argv); + return retval; +} + +static dbus_bool_t +check_bus_name (const char *bus_name, DBusError *error) +{ + if (!_dbus_check_is_valid_bus_name (bus_name)) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND, + "bus name '%s' not found\n", bus_name); + return FALSE; + } + return TRUE; +} + +static dbus_bool_t +get_correct_parser (BusConfigParser **parser, DBusError *error) +{ + DBusString config_file; + dbus_bool_t retval; + const char *test_config_file; + + retval = FALSE; + test_config_file = NULL; + +#ifdef ACTIVATION_LAUNCHER_TEST + /* there is no _way_ we should be setuid if this define is set. + * but we should be doubly paranoid and check... */ + if (getuid() != geteuid()) + _dbus_assert_not_reached ("dbus-daemon-launch-helper-test binary is setuid!"); + + /* this is not a security hole. The environment variable is only passed in the + * dbus-daemon-lauch-helper-test NON-SETUID launcher */ + test_config_file = _dbus_getenv ("TEST_LAUNCH_HELPER_CONFIG"); + if (test_config_file == NULL) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, + "the TEST_LAUNCH_HELPER_CONFIG env variable is not set"); + goto out; + } +#endif + + /* we _only_ use the predefined system config file */ + if (!_dbus_string_init (&config_file)) + { + BUS_SET_OOM (error); + goto out; + } +#ifndef ACTIVATION_LAUNCHER_TEST + if (!_dbus_string_append (&config_file, DBUS_SYSTEM_CONFIG_FILE)) + { + BUS_SET_OOM (error); + goto out_free_config; + } +#else + if (!_dbus_string_append (&config_file, test_config_file)) + { + BUS_SET_OOM (error); + goto out_free_config; + } +#endif + + /* where are we pointing.... */ + _dbus_verbose ("dbus-daemon-activation-helper: using config file: %s\n", + _dbus_string_get_const_data (&config_file)); + + /* get the dbus user */ + *parser = bus_config_load (&config_file, TRUE, NULL, error); + if (*parser == NULL) + { + goto out_free_config; + } + + /* woot */ + retval = TRUE; + +out_free_config: + _dbus_string_free (&config_file); +out: + return retval; +} + +static dbus_bool_t +launch_bus_name (const char *bus_name, BusConfigParser *parser, DBusError *error) +{ + BusDesktopFile *desktop_file; + char *exec, *user; + dbus_bool_t retval; + + exec = NULL; + user = NULL; + retval = FALSE; + + /* get the correct service file for the name we are trying to activate */ + desktop_file = desktop_file_for_name (parser, bus_name, error); + if (desktop_file == NULL) + return FALSE; + + /* get exec and user for service name */ + if (!get_parameters_for_service (desktop_file, bus_name, &exec, &user, error)) + goto finish; + + _dbus_verbose ("dbus-daemon-activation-helper: Name='%s'\n", bus_name); + _dbus_verbose ("dbus-daemon-activation-helper: Exec='%s'\n", exec); + _dbus_verbose ("dbus-daemon-activation-helper: User='%s'\n", user); + + /* actually execute */ + if (!exec_for_correct_user (exec, user, error)) + goto finish; + + retval = TRUE; + +finish: + dbus_free (exec); + dbus_free (user); + bus_desktop_file_free (desktop_file); + return retval; +} + +static dbus_bool_t +check_dbus_user (BusConfigParser *parser, DBusError *error) +{ + const char *dbus_user; + + dbus_user = bus_config_parser_get_user (parser); + if (dbus_user == NULL) + { + dbus_set_error (error, DBUS_ERROR_SPAWN_CONFIG_INVALID, + "could not get user from config file\n"); + return FALSE; + } + + /* check to see if permissions are correct */ + if (!check_permissions (dbus_user, error)) + return FALSE; + + return TRUE; +} + +dbus_bool_t +run_launch_helper (const char *bus_name, DBusError *error) +{ + BusConfigParser *parser; + dbus_bool_t retval; + + parser = NULL; + retval = FALSE; + + /* clear the environment, apart from a few select settings */ + if (!clear_environment (error)) + goto error; + + /* check to see if we have a valid bus name */ + if (!check_bus_name (bus_name, error)) + goto error; + + /* get the correct parser, either the test or default parser */ + if (!get_correct_parser (&parser, error)) + goto error; + + /* check we are being invoked by the correct dbus user */ + if (!check_dbus_user (parser, error)) + goto error_free_parser; + + /* launch the bus with the service defined user */ + if (!launch_bus_name (bus_name, parser, error)) + goto error_free_parser; + + /* woohoo! */ + retval = TRUE; + +error_free_parser: + bus_config_parser_unref (parser); +error: + return retval; +} + diff --git a/bus/activation-helper.h b/bus/activation-helper.h new file mode 100644 index 00000000..aee50108 --- /dev/null +++ b/bus/activation-helper.h @@ -0,0 +1,31 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* activation-helper.h The actual activation helper split from the main + * function for testing. + * + * Copyright (C) 2007 Red Hat, Inc. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef BUS_ACTIVATION_HELPER_H +#define BUS_ACTIVATION_HELPER_H + +dbus_bool_t run_launch_helper (const char *bus_name, DBusError *error); + + +#endif /* BUS_ACTIVATION_HELPER_H */ -- cgit