diff mbox series

[v2,1/3] git-compat-util: avoid failing dir ownership checks if running privileged

Message ID 20220428105852.94449-2-carenas@gmail.com (mailing list archive)
State Superseded
Headers show
Series fix `sudo make install` regression in maint | expand

Commit Message

Carlo Marcelo Arenas Belón April 28, 2022, 10:58 a.m. UTC
bdc77d1d685 (Add a function to determine whether a path is owned by the
current user, 2022-03-02) checks for the effective uid of the running
process using geteuid() but didn't account for cases where that user was
root (because git was invoked through sudo or a compatible tool) and the
original uid that repository trusted for its config was no longer known,
therefore failing the following otherwise safe call:

  guy@renard ~/Software/uncrustify $ sudo git describe --always --dirty
  [sudo] password for guy:
  fatal: unsafe repository ('/home/guy/Software/uncrustify' is owned by someone else)

Attempt to detect those cases by using the environment variables that
those tools create to keep track of the original user id, and do the
ownership check using that instead.

This assumes the environment the user is running with after going
privileged can't be tampered with, and also does the check only for
root to keep the most common case less complicated, but as a side effect
will miss cases where sudo (or an equivalent) was used to change to
another unprivileged user or where the equivalent tool used to raise
privileges didn't track the original id in a sudo compatible way.

Reported-by: Guy Maurel <guy.j@maurel.de>
Helped-by: SZEDER Gábor <szeder.dev@gmail.com>
Helped-by: Randall Becker <rsbecker@nexbridge.com>
Helped-by: Phillip Wood <phillip.wood123@gmail.com>
Suggested-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 git-compat-util.h | 40 +++++++++++++++++++++++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

Comments

Phillip Wood April 28, 2022, 6:02 p.m. UTC | #1
Hi Carlo

On 28/04/2022 11:58, Carlo Marcelo Arenas Belón wrote:
> bdc77d1d685 (Add a function to determine whether a path is owned by the
> current user, 2022-03-02) checks for the effective uid of the running
> process using geteuid() but didn't account for cases where that user was
> root (because git was invoked through sudo or a compatible tool) and the
> original uid that repository trusted for its config was no longer known,
> therefore failing the following otherwise safe call:
> 
>    guy@renard ~/Software/uncrustify $ sudo git describe --always --dirty
>    [sudo] password for guy:
>    fatal: unsafe repository ('/home/guy/Software/uncrustify' is owned by someone else)
> 
> Attempt to detect those cases by using the environment variables that
> those tools create to keep track of the original user id, and do the
> ownership check using that instead.
> 
> This assumes the environment the user is running with after going
> privileged can't be tampered with, and also does the check only for
> root to keep the most common case less complicated, but as a side effect
> will miss cases where sudo (or an equivalent) was used to change to
> another unprivileged user or where the equivalent tool used to raise
> privileges didn't track the original id in a sudo compatible way.
> 
> Reported-by: Guy Maurel <guy.j@maurel.de>
> Helped-by: SZEDER Gábor <szeder.dev@gmail.com>
> Helped-by: Randall Becker <rsbecker@nexbridge.com>
> Helped-by: Phillip Wood <phillip.wood123@gmail.com>
> Suggested-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
> Signed-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>   git-compat-util.h | 40 +++++++++++++++++++++++++++++++++++++++-
>   1 file changed, 39 insertions(+), 1 deletion(-)
> 
> diff --git a/git-compat-util.h b/git-compat-util.h
> index 63ba89dd31d..dfdd3e4f81a 100644
> --- a/git-compat-util.h
> +++ b/git-compat-util.h
> @@ -393,12 +393,50 @@ static inline int git_offset_1st_component(const char *path)
>   #endif
>   
>   #ifndef is_path_owned_by_current_user
> +
> +#ifdef __TANDEM
> +#define ROOT_UID 65535
> +#else
> +#define ROOT_UID 0
> +#endif
> +
> +/*
> + * this helper function overrides a ROOT_UID with the one provided by
> + * an environment variable, do not use unless the original user is
> + * root
> + */
> +static inline void extract_id_from_env(const char *env, uid_t *id)
> +{
> +	const char *real_uid = getenv(env);
> +
> +	/* discard any empty values */
> +	if (real_uid && *real_uid) {
> +		char *endptr;
> +		unsigned long env_id;
> +		int saved_errno = errno;

I still don't understand why you're worried about preserving errno - am 
I missing something? It's not wrong to save it but I'm not sure why we 
need to. is_path_owned_by_current_uid() does not save it around the stat 
call so why do we need to do this here?

> +
> +		errno = 0;
> +		env_id = strtoul(real_uid, &endptr, 10);
> +		if (!errno && !*endptr && env_id <= (uid_t)-1)

Thinking out loud:
"!errno && !*endptr" ensures we have read a valid integer from SUDO_UID. 
If uid_t is unsigned then "env_id <= (uid_t)-1" ensures the value we 
read fits into a uid_t. If uid_t is signed then this test is always true 
and we could truncate the value we read. However if we don't trust 
SUDO_UID then we shouldn't be doing any of this so we are trusting that 
the truncation never happens which means we probably don't need this 
test in the first place.

Best Wishes

Phillip

> +			*id = env_id;
> +
> +		errno = saved_errno;
> +	}
> +}
> +
>   static inline int is_path_owned_by_current_uid(const char *path)
>   {
>   	struct stat st;
> +	uid_t euid;
> +
>   	if (lstat(path, &st))
>   		return 0;
> -	return st.st_uid == geteuid();
> +
> +	euid = geteuid();
> +	if (euid == ROOT_UID)
> +		extract_id_from_env("SUDO_UID", &euid);
> +
> +	return st.st_uid == euid;
>   }
>   
>   #define is_path_owned_by_current_user is_path_owned_by_current_uid
Carlo Marcelo Arenas Belón April 28, 2022, 6:57 p.m. UTC | #2
On Thu, Apr 28, 2022 at 11:02 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>
> On 28/04/2022 11:58, Carlo Marcelo Arenas Belón wrote:
>
> I still don't understand why you're worried about preserving errno - am
> I missing something?

I don't think so, you are correct that doing so is useless now and
could be dropped, I even said so in my message.

> It's not wrong to save it but I'm not sure why we
> need to. is_path_owned_by_current_uid() does not save it around the stat
> call so why do we need to do this here?

because the errno from is_path_owned_by_current_uid() is useful and
unlike this one is not being handled, so a caller of that function
might later use it to distinguish between a "dunno" and "no" answer to
the "is path owned by current uid" question and more importantly
understand better why "dunno" was the answer (ex: path has a loop, or
not accessible or whatever).

it also documents (in the code) that this function won't report any
issues it might have getting that information from the environment
(which extra checks on top to make sure anything suspicious is
ignored), so you can trust that if you get an answer it was a valid
one.

> > +             errno = 0;
> > +             env_id = strtoul(real_uid, &endptr, 10);
> > +             if (!errno && !*endptr && env_id <= (uid_t)-1)
>
> Thinking out loud:
> "!errno && !*endptr" ensures we have read a valid integer from SUDO_UID.
> If uid_t is unsigned then "env_id <= (uid_t)-1" ensures the value we
> read fits into a uid_t. If uid_t is signed then this test is always true

and you hopefully got a warning at build time.

> and we could truncate the value we read. However if we don't trust
> SUDO_UID then we shouldn't be doing any of this so we are trusting that
> the truncation never happens which means we probably don't need this
> test in the first place.

it is there to protect us against a system where uid_t is unsigned but
still narrower than long, and so we don't accidentally assume uid = 0
if we somehow get (the obviously tampered with) SUDO_UID = UINT_MAX +
1

Carlo
diff mbox series

Patch

diff --git a/git-compat-util.h b/git-compat-util.h
index 63ba89dd31d..dfdd3e4f81a 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -393,12 +393,50 @@  static inline int git_offset_1st_component(const char *path)
 #endif
 
 #ifndef is_path_owned_by_current_user
+
+#ifdef __TANDEM
+#define ROOT_UID 65535
+#else
+#define ROOT_UID 0
+#endif
+
+/*
+ * this helper function overrides a ROOT_UID with the one provided by
+ * an environment variable, do not use unless the original user is
+ * root
+ */
+static inline void extract_id_from_env(const char *env, uid_t *id)
+{
+	const char *real_uid = getenv(env);
+
+	/* discard any empty values */
+	if (real_uid && *real_uid) {
+		char *endptr;
+		unsigned long env_id;
+		int saved_errno = errno;
+
+		errno = 0;
+		env_id = strtoul(real_uid, &endptr, 10);
+		if (!errno && !*endptr && env_id <= (uid_t)-1)
+			*id = env_id;
+
+		errno = saved_errno;
+	}
+}
+
 static inline int is_path_owned_by_current_uid(const char *path)
 {
 	struct stat st;
+	uid_t euid;
+
 	if (lstat(path, &st))
 		return 0;
-	return st.st_uid == geteuid();
+
+	euid = geteuid();
+	if (euid == ROOT_UID)
+		extract_id_from_env("SUDO_UID", &euid);
+
+	return st.st_uid == euid;
 }
 
 #define is_path_owned_by_current_user is_path_owned_by_current_uid