diff --git a/utils/gssd/gssd_proc.c b/utils/gssd/gssd_proc.c
index e58c341..b48d163 100644
--- a/utils/gssd/gssd_proc.c
+++ b/utils/gssd/gssd_proc.c
@@ -67,6 +67,8 @@
#include <errno.h>
#include <gssapi/gssapi.h>
#include <netdb.h>
+#include <sys/types.h>
+#include <sys/wait.h>
#include "gssd.h"
#include "err_util.h"
@@ -832,7 +834,6 @@ create_auth_rpc_client(struct clnt_info *clp,
CLIENT *rpc_clnt = NULL;
struct rpc_gss_sec sec;
AUTH *auth = NULL;
- uid_t save_uid = -1;
int retval = -1;
OM_uint32 min_stat;
char rpc_errmsg[1024];
@@ -841,16 +842,6 @@ create_auth_rpc_client(struct clnt_info *clp,
struct sockaddr *addr = (struct sockaddr *) &clp->addr;
socklen_t salen;
- /* Create the context as the user (not as root) */
- save_uid = geteuid();
- if (setfsuid(uid) != 0) {
- printerr(0, "WARNING: Failed to setfsuid for "
- "user with uid %d\n", uid);
- goto out_fail;
- }
- printerr(2, "creating context using fsuid %d (save_uid %d)\n",
- uid, save_uid);
-
sec.qop = GSS_C_QOP_DEFAULT;
sec.svc = RPCSEC_GSS_SVC_NONE;
sec.cred = cred;
@@ -949,11 +940,6 @@ create_auth_rpc_client(struct clnt_info *clp,
out:
if (sec.cred != GSS_C_NO_CREDENTIAL)
gss_release_cred(&min_stat, &sec.cred);
- /* Restore euid to original value */
- if (((int)save_uid != -1) && (setfsuid(save_uid) != (int)uid)) {
- printerr(0, "WARNING: Failed to restore fsuid"
- " to uid %d from %d\n", save_uid, uid);
- }
return retval;
out_fail:
@@ -964,6 +950,64 @@ create_auth_rpc_client(struct clnt_info *clp,
}
/*
+ * Create the context as the user (not as root).
+ *
+ * Note that we change the *real* uid here, as changing the effective uid is
+ * not sufficient. This is due to an unfortunate historical error in the MIT
+ * krb5 libs, where they used %{uid} in the default_ccache_name. Changing that
+ * now might break some applications so we're sort of stuck with it.
+ *
+ * Unfortunately, doing this leaves the forked child vulnerable to signals and
+ * renicing, but this is the best we can do. In the event that a child is
+ * signalled before downcalling, the kernel will just eventually time out the
+ * upcall attempt.
+ */
+static int
+change_identity(uid_t uid)
+{
+ struct passwd *pw;
+
+ /* drop list of supplimentary groups first */
+ if (setgroups(0, NULL) != 0) {
+ printerr(0, "WARNING: unable to drop supplimentary groups!");
+ return errno;
+ }
+
+ /* try to get pwent for user */
+ pw = getpwuid(uid);
+ if (!pw) {
+ /* if that doesn't work, try to get one for "nobody" */
+ errno = 0;
+ pw = getpwnam("nobody");
+ if (!pw) {
+ printerr(0, "WARNING: unable to determine gid for uid %u\n", uid);
+ return errno ? errno : ENOENT;
+ }
+ }
+
+ /*
+ * Switch the GIDs. Note that we leave the saved-set-gid alone in an
+ * attempt to prevent attacks via ptrace()
+ */
+ if (setresgid(pw->pw_gid, pw->pw_gid, -1) != 0) {
+ printerr(0, "WARNING: failed to set gid to %u!\n", pw->pw_gid);
+ return errno;
+ }
+
+ /*
+ * Switch UIDs, but leave saved-set-uid alone to prevent ptrace() by
+ * other processes running with this uid.
+ */
+ if (setresuid(uid, uid, -1) != 0) {
+ printerr(0, "WARNING: Failed to setuid for user with uid %u\n",
+ uid);
+ return errno;
+ }
+
+ return 0;
+}
+
+/*
* this code uses the userland rpcsec gss library to create a krb5
* context on behalf of the kernel
*/
@@ -982,6 +1026,26 @@ process_krb5_upcall(struct clnt_info *clp, uid_t uid, int fd, char *tgtname,
int err, downcall_err = -EACCES;
gss_cred_id_t gss_cred;
OM_uint32 maj_stat, min_stat, lifetime_rec;
+ pid_t pid;
+
+ pid = fork();
+ switch(pid) {
+ case 0:
+ /* Child: fall through to rest of function */
+ break;
+ case -1:
+ /* fork() failed! */
+ printerr(0, "WARNING: unable to fork() to handle upcall: %s\n",
+ strerror(errno));
+ return;
+ default:
+ /* Parent: just wait on child to exit and return */
+ wait(&err);
+ if (WIFSIGNALED(err))
+ printerr(0, "WARNING: forked child was killed with signal %d\n",
+ WTERMSIG(err));
+ return;
+ }
printerr(1, "handling krb5 upcall (%s)\n", clp->dirname);
@@ -1014,6 +1078,14 @@ process_krb5_upcall(struct clnt_info *clp, uid_t uid, int fd, char *tgtname,
service ? service : "<null>");
if (uid != 0 || (uid == 0 && root_uses_machine_creds == 0 &&
service == NULL)) {
+
+ err = change_identity(uid);
+ if (err) {
+ printerr(0, "WARNING: failed to change identity: %s",
+ strerror(err));
+ goto out_return_error;
+ }
+
/* Tell krb5 gss which credentials cache to use */
/* Try first to acquire credentials directly via GSSAPI */
err = gssd_acquire_user_cred(uid, &gss_cred);
@@ -1121,7 +1193,7 @@ out:
AUTH_DESTROY(auth);
if (rpc_clnt)
clnt_destroy(rpc_clnt);
- return;
+ exit(0);
out_return_error:
do_error_downcall(fd, uid, downcall_err);