From 2d257ccf620ce1b611f89cec8f0a94c88c2f2881 Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Jul 10 2012 13:07:26 +0000 Subject: pac responder: limit access by checking UIDs A check for allowed UIDs is added in the common responder code directly after accept(). If the platform does not support reading the UID of the peer but allowed UIDs are configured, access is denied. Currently only the PAC responder sets the allowed UIDs for a socket. The default is that only root is allowed to access the socket of the PAC responder. Fixes: https://fedorahosted.org/sssd/ticket/1382 --- diff --git a/Makefile.am b/Makefile.am index 4e78ae1..3c66b6c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -123,7 +123,8 @@ if HAVE_CHECK util-tests \ debug-tests \ ipa_hbac-tests \ - sss_idmap-tests + sss_idmap-tests \ + responder_socket_access-tests if BUILD_PAC_RESPONDER non_interactive_check_based_tests += pac_responder-tests @@ -1028,6 +1029,20 @@ pac_responder_tests_LDADD = \ libsss_debug.la \ libsss_util.la \ libsss_test_common.la + +responder_socket_access_tests_SOURCES = \ + src/tests/responder_socket_access-tests.c \ + src/responder/common/responder_common.c \ + src/responder/common/responder_packet.c \ + src/responder/common/responder_cmd.c +responder_socket_access_tests_CFLAGS = \ + $(AM_CFLAGS) \ + $(CHECK_CFLAGS) +responder_socket_access_tests_LDADD = \ + $(CHECK_LIBS) \ + $(TALLOC_LIBS) \ + libsss_test_common.la \ + libsss_util.la endif stress_tests_SOURCES = \ diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h index 8ed2356..6f6b730 100644 --- a/src/confdb/confdb.h +++ b/src/confdb/confdb.h @@ -57,6 +57,7 @@ #define CONFDB_SERVICE_FORCE_TIMEOUT "force_timeout" #define CONFDB_SERVICE_RECON_RETRIES "reconnection_retries" #define CONFDB_SERVICE_FD_LIMIT "fd_limit" +#define CONFDB_SERVICE_ALLOWED_UIDS "allowed_uids" /* Monitor */ #define CONFDB_MONITOR_CONF_ENTRY "config/sssd" diff --git a/src/config/SSSDConfig/__init__.py.in b/src/config/SSSDConfig/__init__.py.in index f603067..18586ad 100644 --- a/src/config/SSSDConfig/__init__.py.in +++ b/src/config/SSSDConfig/__init__.py.in @@ -88,6 +88,9 @@ option_strings = { # [ssh] 'ssh_hash_known_hosts': _('Whether to hash host names and addresses in the known_hosts file'), + # [pac] + 'allowed_uids': _('List of UIDs or user names allowed to access the PAC responder'), + # [provider] 'id_provider' : _('Identity provider'), 'auth_provider' : _('Authentication provider'), diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py index c1fbe48..dc4bcc9 100755 --- a/src/config/SSSDConfigTest.py +++ b/src/config/SSSDConfigTest.py @@ -1178,7 +1178,8 @@ class SSSDConfigTestSSSDConfig(unittest.TestCase): 'pam', 'sudo', 'autofs', - 'ssh'] + 'ssh', + 'pac'] for section in control_list: self.assertTrue(sssdconfig.has_section(section), "Section [%s] missing" % @@ -1270,7 +1271,8 @@ class SSSDConfigTestSSSDConfig(unittest.TestCase): 'nss', 'sudo', 'autofs', - 'ssh'] + 'ssh', + 'pac'] service_list = sssdconfig.list_services() for service in control_list: self.assertTrue(service in service_list, diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf index f1cd067..35ebb2e 100644 --- a/src/config/etc/sssd.api.conf +++ b/src/config/etc/sssd.api.conf @@ -63,6 +63,10 @@ autofs_negative_timeout = int, None, false # ssh service ssh_hash_known_hosts = bool, None, false +[pac] +# PAC responder +allowed_uids = str, None, false + [provider] #Available provider types id_provider = str, None, true diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml index 2674885..ed2d1e0 100644 --- a/src/man/sssd.conf.5.xml +++ b/src/man/sssd.conf.5.xml @@ -792,10 +792,6 @@ PAC responder configuration options - Currently there are no PAC responder specific configuration - options. - - @@ -822,6 +818,33 @@ groups. + + These options can be used to configure the PAC responder. + + + + allowed_uids (string) + + + Specifies the comma-separated list of UID values or + user names that are allowed to access the PAC + responder. User names are resolved to UIDs at + startup. + + + Default: 0 (only the root user is allowed to access + the PAC responder) + + + Please note that although the UID 0 is used as the + default it will be overwritten with this option. If + you still want to allow the root user to access the + PAC responder, which would be the typical case, you + have to add 0 to the list of allowed UIDs as well. + + + + diff --git a/src/responder/common/responder.h b/src/responder/common/responder.h index 43a4fa0..c09262d 100644 --- a/src/responder/common/responder.h +++ b/src/responder/common/responder.h @@ -98,6 +98,9 @@ struct resp_ctx { struct timeval get_domains_last_call; + size_t allowed_uids_count; + uid_t *allowed_uids; + void *pvt_ctx; }; @@ -289,4 +292,11 @@ struct tevent_req *sss_dp_get_domains_send(TALLOC_CTX *mem_ctx, const char *hint); errno_t sss_dp_get_domains_recv(struct tevent_req *req); + +errno_t csv_string_to_uid_array(TALLOC_CTX *mem_ctx, const char *cvs_string, + bool allow_sss_loop, + size_t *_uid_count, uid_t **_uids); + +errno_t check_allowed_uids(uid_t uid, size_t allowed_uids_count, + uid_t *allowed_uids); #endif /* __SSS_RESPONDER_H__ */ diff --git a/src/responder/common/responder_common.c b/src/responder/common/responder_common.c index e557740..e44c351 100644 --- a/src/responder/common/responder_common.c +++ b/src/responder/common/responder_common.c @@ -33,6 +33,7 @@ #include #include #include "util/util.h" +#include "util/strtonum.h" #include "db/sysdb.h" #include "confdb/confdb.h" #include "dbus/dbus.h" @@ -104,15 +105,15 @@ static int client_destructor(struct cli_ctx *ctx) static errno_t get_client_cred(struct cli_ctx *cctx) { + cctx->client_euid = -1; + cctx->client_egid = -1; + cctx->client_pid = -1; + #ifdef HAVE_UCRED int ret; struct ucred client_cred; socklen_t client_cred_len = sizeof(client_cred); - cctx->client_euid = -1; - cctx->client_egid = -1; - cctx->client_pid = -1; - ret = getsockopt(cctx->cfd, SOL_SOCKET, SO_PEERCRED, &client_cred, &client_cred_len); if (ret != EOK) { @@ -136,6 +137,107 @@ static errno_t get_client_cred(struct cli_ctx *cctx) return EOK; } +errno_t check_allowed_uids(uid_t uid, size_t allowed_uids_count, + uid_t *allowed_uids) +{ + size_t c; + + if (allowed_uids == NULL) { + return EINVAL; + } + + for (c = 0; c < allowed_uids_count; c++) { + if (uid == allowed_uids[c]) { + return EOK; + } + } + + return EACCES; +} + +errno_t csv_string_to_uid_array(TALLOC_CTX *mem_ctx, const char *cvs_string, + bool allow_sss_loop, + size_t *_uid_count, uid_t **_uids) +{ + int ret; + size_t c; + char **list = NULL; + int list_size; + uid_t *uids = NULL; + char *endptr; + struct passwd *pwd; + + ret = split_on_separator(mem_ctx, cvs_string, ',', true, &list, &list_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, ("split_on_separator failed [%d][%s].\n", + ret, strerror(ret))); + goto done; + } + + uids = talloc_array(mem_ctx, uint32_t, list_size); + if (uids == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("talloc_array failed.\n")); + ret = ENOMEM; + goto done; + } + + if (allow_sss_loop) { + ret = unsetenv("_SSS_LOOPS"); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, ("Failed to unset _SSS_LOOPS, getpwnam " + "might not find sssd users.\n")); + } + } + + for (c = 0; c < list_size; c++) { + errno = 0; + if (*list[c] == '\0') { + DEBUG(SSSDBG_OP_FAILURE, ("Empty list item.\n")); + ret = EINVAL; + goto done; + } + + uids[c] = strtouint32(list[c], &endptr, 10); + if (errno != 0 || *endptr != '\0') { + ret = errno; + if (ret == ERANGE) { + DEBUG(SSSDBG_OP_FAILURE, ("List item [%s] is out of range.\n", + list[c])); + goto done; + } + + errno = 0; + pwd = getpwnam(list[c]); + if (pwd == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("List item [%s] is neither a valid " + "UID nor a user name which cloud be " + "resolved by getpwnam().\n", list[c])); + ret = EINVAL; + goto done; + } + + uids[c] = pwd->pw_uid; + } + } + + *_uid_count = list_size; + *_uids = uids; + + ret = EOK; + +done: + if(setenv("_SSS_LOOPS", "NO", 0) != 0) { + DEBUG(SSSDBG_OP_FAILURE, ("Failed to set _SSS_LOOPS.\n")); + } + talloc_free(list); + if (ret != EOK) { + talloc_free(uids); + } + + return ret; +} + + static void client_send(struct cli_ctx *cctx) { int ret; @@ -320,6 +422,32 @@ static void accept_fd_handler(struct tevent_context *ev, "client cred may not be available.\n")); } + if (rctx->allowed_uids_count != 0) { + if (cctx->client_euid == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, ("allowed_uids configured, " \ + "but platform does not support " \ + "reading peer credential from the " \ + "socket. Access denied.\n")); + close(cctx->cfd); + talloc_free(cctx); + return; + } + + ret = check_allowed_uids(cctx->client_euid, rctx->allowed_uids_count, + rctx->allowed_uids); + if (ret != EOK) { + if (ret == EACCES) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Access denied for uid [%d].\n", + cctx->client_euid)); + } else { + DEBUG(SSSDBG_OP_FAILURE, ("check_allowed_uids failed.\n")); + } + close(cctx->cfd); + talloc_free(cctx); + return; + } + } + cctx->cfde = tevent_add_fd(ev, cctx, cctx->cfd, TEVENT_FD_READ, client_fd_handler, cctx); if (!cctx->cfde) { diff --git a/src/responder/pac/pacsrv.c b/src/responder/pac/pacsrv.c index db6e6b4..348fc6f 100644 --- a/src/responder/pac/pacsrv.c +++ b/src/responder/pac/pacsrv.c @@ -45,6 +45,7 @@ #define SSS_PAC_PIPE_NAME "pac" #define DEFAULT_PAC_FD_LIMIT 8192 +#define DEFAULT_ALLOWED_UIDS "0" struct sbus_method monitor_pac_methods[] = { { MON_CLI_METHOD_PING, monitor_common_pong }, @@ -124,6 +125,7 @@ int pac_process_init(TALLOC_CTX *mem_ctx, int ret, max_retries; enum idmap_error_code err; int fd_limit; + char *uid_str; pac_ctx = talloc_zero(mem_ctx, struct pac_ctx); if (!pac_ctx) { @@ -147,6 +149,23 @@ int pac_process_init(TALLOC_CTX *mem_ctx, } pac_ctx->rctx->pvt_ctx = pac_ctx; + + ret = confdb_get_string(pac_ctx->rctx->cdb, pac_ctx->rctx, + CONFDB_PAC_CONF_ENTRY, CONFDB_SERVICE_ALLOWED_UIDS, + DEFAULT_ALLOWED_UIDS, &uid_str); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, ("Failed to get allowed UIDs.\n")); + return ret; + } + + ret = csv_string_to_uid_array(pac_ctx->rctx, uid_str, true, + &pac_ctx->rctx->allowed_uids_count, + &pac_ctx->rctx->allowed_uids); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, ("Failed to set allowed UIDs.\n")); + return ret; + } + /* Enable automatic reconnection to the Data Provider */ ret = confdb_get_int(pac_ctx->rctx->cdb, CONFDB_PAC_CONF_ENTRY, diff --git a/src/tests/responder_socket_access-tests.c b/src/tests/responder_socket_access-tests.c new file mode 100644 index 0000000..734bf1c --- /dev/null +++ b/src/tests/responder_socket_access-tests.c @@ -0,0 +1,178 @@ +/* + SSSD - Test for routine to check to access to responder sockets + + Authors: + Sumit Bose + + Copyright (C) 2012 Red Hat + + 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 3 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, see . +*/ + +#include +#include +#include + +#include "tests/common.h" +#include "responder/common/responder.h" + +struct cli_protocol_version *register_cli_protocol_version(void) +{ + static struct cli_protocol_version responder_test_cli_protocol_version[] = { + {0, NULL, NULL} + }; + + return responder_test_cli_protocol_version; +} + +struct s2a_data { + const char *inp; + int exp_ret; + size_t exp_count; + uid_t *exp_uids; +}; + +struct s2a_data s2a_data[] = { + {"1,2,3", 0, 3, (uid_t []){1, 2, 3}}, + {"1,2,3, 4,5 , 6 , 7 ", 0, 7, (uid_t []){1, 2, 3, 4, 5, 6, 7}}, + {"1", 0, 1, (uid_t []){1}}, + {"1, +2,3", 0, 3, (uid_t []){1, 2, 3}}, + {"1, -2,3", ERANGE, 0, NULL}, + {"1, 2ab, 3, 4", EINVAL, 0, NULL}, + {"1,", EINVAL, 0, NULL}, + {"", EINVAL, 0, NULL}, + {"1, 2, 4294967295", 0, 3, (uid_t []){1, 2, 4294967295U}}, + {"1, 2, 4294967296", ERANGE, 0, NULL}, + {"1, 2, root, 4, 5", 0, 5, (uid_t []){1, 2, 0, 4, 5}}, + {NULL, EINVAL, 0, NULL}, + {NULL, -1, 0, NULL} +}; + +START_TEST(resp_str_to_array_test) +{ + int ret; + size_t uid_count; + uid_t *uids = NULL; + size_t c; + size_t d; + + for (c = 0; s2a_data[c].exp_ret != -1; c++) { + ret = csv_string_to_uid_array(global_talloc_context, s2a_data[c].inp, + true, &uid_count, &uids); + fail_unless(ret == s2a_data[c].exp_ret, + "csv_string_to_uid_array failed [%d][%s].", ret, + strerror(ret)); + if (ret == 0) { + fail_unless(uid_count == s2a_data[c].exp_count, + "Wrong number of values, expected [%d], got [%d].", + s2a_data[c].exp_count, uid_count); + for (d = 0; d < s2a_data[c].exp_count; d++) { + fail_unless(uids[d] == s2a_data[c].exp_uids[d], + "Wrong value, expected [%d], got [%d].\n", + s2a_data[c].exp_uids[d], uids[d]); + } + } + + talloc_free(uids); + uids = NULL; + } + +} +END_TEST + +struct uid_check_data { + uid_t uid; + size_t allowed_uids_count; + uid_t *allowed_uids; + int exp_ret; +}; + +struct uid_check_data uid_check_data[] = { + {1, 3, (uid_t []){1, 2, 3}, 0}, + {2, 3, (uid_t []){1, 2, 3}, 0}, + {3, 3, (uid_t []){1, 2, 3}, 0}, + {4, 3, (uid_t []){1, 2, 3}, EACCES}, + {4, 0, NULL, EINVAL}, + {0, 0, NULL, -1} +}; + +START_TEST(check_allowed_uids_test) +{ + int ret; + size_t c; + + for (c = 0; uid_check_data[c].exp_ret == -1; c++) { + ret = check_allowed_uids(uid_check_data[c].uid, + uid_check_data[c].allowed_uids_count, + uid_check_data[c].allowed_uids); + fail_unless(ret == uid_check_data[c].exp_ret, + "check_allowed_uids failed [%d][%s].", ret, strerror(ret)); + } +} +END_TEST + +Suite *responder_test_suite(void) +{ + Suite *s = suite_create ("Responder socket access"); + + TCase *tc_utils = tcase_create("Utility test"); + + tcase_add_test(tc_utils, resp_str_to_array_test); + tcase_add_test(tc_utils, check_allowed_uids_test); + + suite_add_tcase(s, tc_utils); + + return s; +} + +int main(int argc, const char *argv[]) +{ + int opt; + int number_failed; + poptContext pc; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can deside if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + poptFreeContext(pc); + + CONVERT_AND_SET_DEBUG_LEVEL(debug_level); + tests_set_cwd(); + + Suite *s = responder_test_suite(); + SRunner *sr = srunner_create(s); + + /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */ + srunner_run_all(sr, CK_ENV); + number_failed = srunner_ntests_failed (sr); + srunner_free (sr); + + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +}