From e8f7c10185f827e4341f2c30fa1b7999945b973d Mon Sep 17 00:00:00 2001 From: Alexey Tikhonov Date: Fri, 19 Dec 2025 16:08:14 +0100 Subject: [PATCH 1/2] KRB5: let 'krb5_child' tolerate missing cap-set-id If CAP_SETUID and/or CAP_SETGID are missing, 'krb5_child' will skip operation that require those capabilities, namely any manipulations with user ccache. :packaging:This update makes it possible to not grant CAP_SETUID and CAP_SETGID to 'krb5_child' binary in a situation where it is not required to store acquired TGT after user authentication. Taking into account that it is already possible to avoid using CAP_DAC_READ_SEARCH if keytab is readable by SSSD service user, and usage of 'selinux_child' isn't always required, this allows to build a setup with completely privilege-less SSSD to serve certain use cases. In particular, this might be used to build a container running SSSD on OCP with a restricted profile. --- src/providers/krb5/krb5_ccache.c | 6 ++- src/providers/krb5/krb5_child.c | 78 ++++++++++++++++++++++---------- 2 files changed, 59 insertions(+), 25 deletions(-) diff --git a/src/providers/krb5/krb5_ccache.c b/src/providers/krb5/krb5_ccache.c index 67de16bf786..3d37ba138ec 100644 --- a/src/providers/krb5/krb5_ccache.c +++ b/src/providers/krb5/krb5_ccache.c @@ -37,7 +37,11 @@ #include "util/util.h" -/* real id == user id; set id == service id */ +/* `switch_to_()` functions expect that + * real id == user id; set id == service id. + * This is prepared (set) in `privileged_krb5_setup()` + * if process has corresponding capabilities. + */ errno_t switch_to_user(void) { int ret; diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c index 05c73455c14..9a693c634e9 100644 --- a/src/providers/krb5/krb5_child.c +++ b/src/providers/krb5/krb5_child.c @@ -121,6 +121,7 @@ struct krb5_req { struct cli_opts *cli_opts; }; +static bool krb5_child_has_setid_cap = true; static krb5_context krb5_error_ctx; #define KRB5_CHILD_DEBUG_INT(level, errctx, krb5_error) do { \ @@ -1845,6 +1846,10 @@ static errno_t k5c_attach_ccname_msg(struct krb5_req *kr) char *msg = NULL; int ret; + if (!krb5_child_has_setid_cap) { + return EOK; + } + if (kr->ccname == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, "Error obtaining ccname.\n"); return ERR_INTERNAL; @@ -2523,6 +2528,12 @@ static krb5_error_code get_and_save_tgt(struct krb5_req *kr, goto done; } + if (!krb5_child_has_setid_cap) { + /* no set-id capability => can't populate user ccache */ + kerr = 0; + goto done; + } + /* Make sure ccache is created and written as the user */ kerr = switch_to_user(); if (kerr != EOK) { @@ -4085,32 +4096,43 @@ static krb5_error_code privileged_krb5_setup(struct krb5_req *kr, int ret; char *mem_keytab; - /* Make use of cap_set*id first to bootstap process */ - sss_set_cap_effective(CAP_SETGID, true); - if (geteuid() != 0) { - ret = setgroups(0, NULL); + /* Make use of cap_set*id (if available) first to bootstrap process */ + if (sss_set_cap_effective(CAP_SETGID, true) == EOK) { + if (geteuid() != 0) { + ret = setgroups(0, NULL); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to drop supplementary groups: %d\n", ret); + return ret; + } + } /* Otherwise keep supplementary groups to have access to DB_PATH to store FAST ccache */ + ret = setresgid(kr->gid, -1, -1); if (ret != 0) { ret = errno; - DEBUG(SSSDBG_CRIT_FAILURE, "Failed to drop supplementary groups: %d\n", ret); + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set real GID: %d\n", ret); return ret; } - } /* Otherwise keep supplementary groups to have access to DB_PATH to store FAST ccache */ - ret = setresgid(kr->gid, -1, -1); - if (ret != 0) { - ret = errno; - DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set real GID: %d\n", ret); - return ret; + } else { + krb5_child_has_setid_cap = false; } - sss_set_cap_effective(CAP_SETUID, true); - ret = setresuid(kr->uid, -1, -1); - if (ret != 0) { - ret = errno; - DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set real UID: %d\n", ret); - return ret; + if (krb5_child_has_setid_cap && (sss_set_cap_effective(CAP_SETUID, true) == EOK)) { + ret = setresuid(kr->uid, -1, -1); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set real UID: %d\n", ret); + return ret; + } + } else { + krb5_child_has_setid_cap = false; } sss_drop_cap(CAP_SETUID); sss_drop_cap(CAP_SETGID); + if (!krb5_child_has_setid_cap) { + DEBUG(SSSDBG_CONF_SETTINGS, "'krb5_child' doesn't have CAP_SETUID and/or " + "CAP_SETGID. User ccache won't be updated.\n"); + } + kr->realm = kr->cli_opts->realm; if (kr->realm == NULL) { DEBUG(SSSDBG_MINOR_FAILURE, "Realm not available.\n"); @@ -4337,7 +4359,7 @@ int main(int argc, const char *argv[]) * is only allowed for authenticated users. Since PKINIT is part of * the authentication and the user is not authenticated yet, we have * to use different privileges and can only drop it after the TGT is - * received. IDs the backend (and thus 'krb5_child) is running with are + * received. IDs the backend (and thus 'krb5_child') is running with are * either root or the 'sssd' user. Root is allowed by default and * the 'sssd' user is allowed with the help of the sssd-pcsc.rules * policy-kit rule. So those IDs are a suitable choice and needs to @@ -4346,11 +4368,13 @@ int main(int argc, const char *argv[]) * to make sure the empty ccache is created with the expected * ownership. */ if (!IS_SC_AUTHTOK(kr->pd->authtok) || offline) { - ret = switch_to_user(); - if (ret != EOK) { - DEBUG(SSSDBG_CRIT_FAILURE, "Failed to switch to user IDs: %d\n", ret); - ret = EFAULT; - goto done; + if (krb5_child_has_setid_cap) { + ret = switch_to_user(); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to switch to user IDs: %d\n", ret); + ret = EFAULT; + goto done; + } } } @@ -4370,7 +4394,9 @@ int main(int argc, const char *argv[]) case SSS_PAM_AUTHENTICATE: /* If we are offline, we need to create an empty ccache file */ if (offline) { - ret = create_empty_ccache(kr); + if (krb5_child_has_setid_cap) { + ret = create_empty_ccache(kr); + } } else { DEBUG(SSSDBG_TRACE_FUNC, "Will perform online auth\n"); ret = tgt_req_child(kr); @@ -4391,6 +4417,10 @@ int main(int argc, const char *argv[]) ret = KRB5_KDC_UNREACH; goto done; } + if (!krb5_child_has_setid_cap) { + ret = KRB5_CC_NOTFOUND; + goto done; + } ret = renew_tgt_child(kr); break; case SSS_PAM_PREAUTH: From a4a7b3d6186b31c9141860f7ceb834f249b16bcd Mon Sep 17 00:00:00 2001 From: Alexey Tikhonov Date: Sat, 20 Dec 2025 11:40:42 +0100 Subject: [PATCH 2/2] TMP/TEST: don't grant cap-set-id to 'krb5_child' --- Makefile.am | 2 +- contrib/sssd.spec.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.am b/Makefile.am index 4bc4e28a9fc..507bf2c30c8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5524,7 +5524,7 @@ if SSSD_USER -$(SETCAP) cap_dac_read_search=p $(DESTDIR)$(sssdlibexecdir)/ldap_child -chgrp $(SSSD_USER) $(DESTDIR)$(sssdlibexecdir)/krb5_child chmod 750 $(DESTDIR)$(sssdlibexecdir)/krb5_child - -$(SETCAP) cap_dac_read_search,cap_setuid,cap_setgid=p $(DESTDIR)$(sssdlibexecdir)/krb5_child + -$(SETCAP) cap_dac_read_search=p $(DESTDIR)$(sssdlibexecdir)/krb5_child -chgrp $(SSSD_USER) $(DESTDIR)$(sssdlibexecdir)/proxy_child chmod 750 $(DESTDIR)$(sssdlibexecdir)/proxy_child -chgrp $(SSSD_USER) $(DESTDIR)$(sssdlibexecdir)/sssd_pam diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in index 20bbbdcc931..a959761c0c1 100644 --- a/contrib/sssd.spec.in +++ b/contrib/sssd.spec.in @@ -779,7 +779,7 @@ install -D -p -m 0644 %{SOURCE1} %{buildroot}%{_sysusersdir}/sssd.conf %license COPYING %attr(775,sssd,sssd) %dir %{pubconfpath}/krb5.include.d %attr(0750,root,sssd) %caps(cap_dac_read_search=p) %{_libexecdir}/%{servicename}/ldap_child -%attr(0750,root,sssd) %caps(cap_dac_read_search,cap_setuid,cap_setgid=p) %{_libexecdir}/%{servicename}/krb5_child +%attr(0750,root,sssd) %caps(cap_dac_read_search=p) %{_libexecdir}/%{servicename}/krb5_child %config(noreplace) %{_sysconfdir}/krb5.conf.d/enable_sssd_conf_dir %dir %{_datadir}/sssd/krb5-snippets %{_datadir}/sssd/krb5-snippets/enable_sssd_conf_dir