From 53e0b2255d92c9c21c19306cf37cc8de0476dc9c Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 12 Dec 2018 16:41:01 +0100 Subject: [PATCH] ipa-getkeytab: resolve symlink Resolve one level of symbolic links to support a dangling symlink as keytab target. To prevent symlink attacks, only resolve symlink when the symlink is owned by the current effective user and group, or by root. Fixes: https://pagure.io/freeipa/issue/4607 Signed-off-by: Christian Heimes Reviewed-By: Rob Crittenden Reviewed-By: Alexander Bokovoy --- client/ipa-getkeytab.c | 61 ++++++++++++++++++++-- ipatests/test_cmdline/test_ipagetkeytab.py | 17 ++++++ 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/client/ipa-getkeytab.c b/client/ipa-getkeytab.c index 8a7ddd5ab..b26a26c54 100644 --- a/client/ipa-getkeytab.c +++ b/client/ipa-getkeytab.c @@ -672,6 +672,56 @@ int read_ipa_config(struct ipa_config **ipacfg) return 0; } +static int resolve_ktname(const char *keytab, char **ktname, char **err_msg) +{ + char keytab_resolved[PATH_MAX + 1]; + struct stat st; + struct stat lst; + int ret; + + *err_msg = NULL; + + /* Resolve keytab symlink to support dangling symlinks, see + * https://pagure.io/freeipa/issue/4607. To prevent symlink attacks, + * the symlink is only resolved owned by the current user or by + * root. For simplicity, only one level if indirection is resolved. + */ + if ((stat(keytab, &st) == -1) && + (errno = ENOENT) && + (lstat(keytab, &lst) == 0) && + (S_ISLNK(lst.st_mode))) { + /* keytab is a dangling symlink. */ + if (((lst.st_uid == 0) && (lst.st_gid == 0)) || + ((lst.st_uid == geteuid()) && (lst.st_gid == getegid()))) { + /* Either root or current user owns symlink, resolve symlink and + * return the resolved symlink. */ + ret = readlink(keytab, keytab_resolved, PATH_MAX + 1); + if ((ret == -1) || (ret > PATH_MAX)) { + *err_msg = _("Failed to resolve symlink to keytab.\n"); + return ENOENT; + } + keytab_resolved[ret] = '\0'; + ret = asprintf(ktname, "WRFILE:%s", keytab_resolved); + if (ret == -1) { + *err_msg = strerror(errno); + return ENOMEM; + } + return 0; + } else { + *err_msg = _("keytab is a dangling symlink and owned by another " + "user.\n"); + return EINVAL; + } + } else { + ret = asprintf(ktname, "WRFILE:%s", keytab); + if (ret == -1) { + *err_msg = strerror(errno); + return ENOMEM; + } + return 0; + } +} + int main(int argc, const char *argv[]) { static const char *server = NULL; @@ -860,11 +910,6 @@ int main(int argc, const char *argv[]) } } - ret = asprintf(&ktname, "WRFILE:%s", keytab); - if (ret == -1) { - exit(3); - } - krberr = krb5_parse_name(krbctx, principal, &sprinc); if (krberr) { fprintf(stderr, _("Invalid Service Principal Name\n")); @@ -889,6 +934,12 @@ int main(int argc, const char *argv[]) } } + ret = resolve_ktname(keytab, &ktname, &err_msg); + if (krberr) { + fprintf(stderr, "%s", err_msg); + exit(ret); + } + krberr = krb5_kt_resolve(krbctx, ktname, &kt); if (krberr) { fprintf(stderr, _("Failed to open Keytab\n")); diff --git a/ipatests/test_cmdline/test_ipagetkeytab.py b/ipatests/test_cmdline/test_ipagetkeytab.py index 3a134062e..6b86c9dd4 100644 --- a/ipatests/test_cmdline/test_ipagetkeytab.py +++ b/ipatests/test_cmdline/test_ipagetkeytab.py @@ -190,6 +190,23 @@ class test_ipagetkeytab(KeytabRetrievalTest): except Exception as errmsg: assert('Unable to bind to LDAP. Error initializing principal' in str(errmsg)) + def test_dangling_symlink(self, test_service): + # see https://pagure.io/freeipa/issue/4607 + test_service.ensure_exists() + + fd, symlink_target = tempfile.mkstemp() + os.close(fd) + os.unlink(symlink_target) + # create dangling symlink + os.symlink(self.keytabname, symlink_target) + + try: + self.assert_success(test_service.name, raiseonerr=True) + assert os.path.isfile(symlink_target) + assert os.path.samefile(self.keytabname, symlink_target) + finally: + os.unlink(symlink_target) + class TestBindMethods(KeytabRetrievalTest): """