Commit 0967459ebc9e57bb2a7373ce0d00361a16eebc55

Patrick Steinhardt 2018-01-25T13:11:34

sysdir: do not use environment in setuid case In order to derive the location of some Git directories, we currently use the environment variables $HOME and $XDG_CONFIG_HOME. This might prove to be problematic whenever the binary is run with setuid, that is when the effective user does not equal the real user. In case the environment variables do not get sanitized by the caller, we thus might end up using the real user's configuration when doing stuff as the effective user. The fix is to use the passwd entry's directory instead of $HOME in this situation. As this might break scenarios where the user explicitly sets $HOME to another path, this fix is only applied in case the effective user does not equal the real user.

diff --git a/src/sysdir.c b/src/sysdir.c
index 7480e82..509b23b 100644
--- a/src/sysdir.c
+++ b/src/sysdir.c
@@ -13,6 +13,9 @@
 #include <ctype.h>
 #if GIT_WIN32
 #include "win32/findfile.h"
+#else
+#include <unistd.h>
+#include <pwd.h>
 #endif
 
 static int git_sysdir_guess_programdata_dirs(git_buf *out)
@@ -34,12 +37,63 @@ static int git_sysdir_guess_system_dirs(git_buf *out)
 #endif
 }
 
+#ifndef GIT_WIN32
+static int get_passwd_home(git_buf *out, uid_t uid)
+{
+	struct passwd pwd, *pwdptr;
+	char *buf = NULL;
+	long buflen;
+	int error;
+
+	assert(out);
+
+	if ((buflen = sysconf(_SC_GETPW_R_SIZE_MAX)) == -1)
+		buflen = 1024;
+
+	do {
+		buf = git__realloc(buf, buflen);
+		error = getpwuid_r(uid, &pwd, buf, buflen, &pwdptr);
+		buflen *= 2;
+	} while (error == ERANGE && buflen <= 8192);
+
+	if (error) {
+		giterr_set(GITERR_OS, "failed to get passwd entry");
+		goto out;
+	}
+
+	if (!pwdptr) {
+		giterr_set(GITERR_OS, "no passwd entry found for user");
+		goto out;
+	}
+
+	if ((error = git_buf_puts(out, pwdptr->pw_dir)) < 0)
+		goto out;
+
+out:
+	git__free(buf);
+	return error;
+}
+#endif
+
 static int git_sysdir_guess_global_dirs(git_buf *out)
 {
 #ifdef GIT_WIN32
 	return git_win32__find_global_dirs(out);
 #else
-	int error = git__getenv(out, "HOME");
+	int error;
+	uid_t uid, euid;
+
+	uid = getuid();
+	euid = geteuid();
+
+	/*
+	 * In case we are running setuid, use the configuration
+	 * of the effective user.
+	 */
+	if (uid == euid)
+	    error = git__getenv(out, "HOME");
+	else
+	    error = get_passwd_home(out, euid);
 
 	if (error == GIT_ENOTFOUND) {
 		giterr_clear();
@@ -57,12 +111,25 @@ static int git_sysdir_guess_xdg_dirs(git_buf *out)
 #else
 	git_buf env = GIT_BUF_INIT;
 	int error;
-
-	if ((error = git__getenv(&env, "XDG_CONFIG_HOME")) == 0)
-		error = git_buf_joinpath(out, env.ptr, "git");
-
-	if (error == GIT_ENOTFOUND && (error = git__getenv(&env, "HOME")) == 0)
-		error = git_buf_joinpath(out, env.ptr, ".config/git");
+	uid_t uid, euid;
+
+	uid = getuid();
+	euid = geteuid();
+
+	/*
+	 * In case we are running setuid, only look up passwd
+	 * directory of the effective user.
+	 */
+	if (uid == euid) {
+		if ((error = git__getenv(&env, "XDG_CONFIG_HOME")) == 0)
+			error = git_buf_joinpath(out, env.ptr, "git");
+
+		if (error == GIT_ENOTFOUND && (error = git__getenv(&env, "HOME")) == 0)
+			error = git_buf_joinpath(out, env.ptr, ".config/git");
+	} else {
+		if ((error = get_passwd_home(&env, euid)) == 0)
+			error = git_buf_joinpath(out, env.ptr, ".config/git");
+	}
 
 	if (error == GIT_ENOTFOUND) {
 		giterr_clear();