From c823470f7270fb1864ee9fe6200796cb7f5a692c Mon Sep 17 00:00:00 2001
From: Alexey Tikhonov <atikhono@redhat.com>
Date: Fri, 19 Dec 2025 16:08:14 +0100
Subject: [PATCH] KRB5: let 'krb5_child' tolerate missing cap-set-id
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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.

Reviewed-by: Pavel Březina <pbrezina@redhat.com>
Reviewed-by: Sumit Bose <sbose@redhat.com>
---
 src/providers/krb5/krb5_ccache.c |  6 ++-
 src/providers/krb5/krb5_child.c  | 78 ++++++++++++++++++++++----------
 2 files changed, 58 insertions(+), 26 deletions(-)

diff --git a/src/providers/krb5/krb5_ccache.c b/src/providers/krb5/krb5_ccache.c
index 67de16bf7..3d37ba138 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 22fcbf95e..fa73c868b 100644
--- a/src/providers/krb5/krb5_child.c
+++ b/src/providers/krb5/krb5_child.c
@@ -82,6 +82,8 @@ struct cli_opts {
 };
 
 struct krb5_req {
+    bool krb5_child_has_setid_caps;
+
     krb5_context ctx;
     krb5_principal princ;
     krb5_principal princ_orig;
@@ -1759,6 +1761,10 @@ static errno_t k5c_attach_ccname_msg(struct krb5_req *kr)
     char *msg = NULL;
     int ret;
 
+    if (!kr->krb5_child_has_setid_caps) {
+        return EOK;
+    }
+
     if (kr->ccname == NULL) {
         DEBUG(SSSDBG_CRIT_FAILURE, "Error obtaining ccname.\n");
         return ERR_INTERNAL;
@@ -2428,6 +2434,12 @@ static krb5_error_code get_and_save_tgt(struct krb5_req *kr,
         goto done;
     }
 
+    if (!kr->krb5_child_has_setid_caps) {
+        /* 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) {
@@ -3985,29 +3997,37 @@ 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 */
+    kr->krb5_child_has_setid_caps =
+        ((sss_set_cap_effective(CAP_SETGID, true) == EOK) &&
+         (sss_set_cap_effective(CAP_SETUID, true) == EOK));
+
+    if (kr->krb5_child_has_setid_caps) {
+        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;
-    }
-    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;
+        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 {
+        DEBUG(SSSDBG_CONF_SETTINGS, "'krb5_child' doesn't have CAP_SETUID and/or "
+                                    "CAP_SETGID. User ccache won't be updated.\n");
     }
+
     sss_drop_cap(CAP_SETUID);
     sss_drop_cap(CAP_SETGID);
 
@@ -4270,7 +4290,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
@@ -4279,11 +4299,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 (kr->krb5_child_has_setid_caps) {
+            ret = switch_to_user();
+            if (ret != EOK) {
+                DEBUG(SSSDBG_CRIT_FAILURE, "Failed to switch to user IDs: %d\n", ret);
+                ret = EFAULT;
+                goto done;
+            }
         }
     }
 
@@ -4303,7 +4325,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 (kr->krb5_child_has_setid_caps) {
+                ret = create_empty_ccache(kr);
+            }
         } else {
             DEBUG(SSSDBG_TRACE_FUNC, "Will perform online auth\n");
             ret = tgt_req_child(kr);
@@ -4324,6 +4348,10 @@ int main(int argc, const char *argv[])
             ret = KRB5_KDC_UNREACH;
             goto done;
         }
+        if (!kr->krb5_child_has_setid_caps) {
+            ret = KRB5_CC_NOTFOUND;
+            goto done;
+        }
         ret = renew_tgt_child(kr);
         break;
     case SSS_PAM_PREAUTH:
-- 
2.53.0

