diff mbox series

[RFC] git-compat-util: avoid failing dir ownership checks if running priviledged

Message ID 20220426183105.99779-1-carenas@gmail.com (mailing list archive)
State New, archived
Headers show
Series [RFC] git-compat-util: avoid failing dir ownership checks if running priviledged | expand

Commit Message

Carlo Marcelo Arenas Belón April 26, 2022, 6:31 p.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 uid was
root (because git was invoked through sudo or doas) and the effetive id
that repositiry trusted for its config was different, therefore failing
the following common 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 those instead.

This assumes the environment the user is running with after going
priviledged 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 unpriviledged user.

Reported-by: Guy Maurel <guy.j@maurel.de>
Helped-by: SZEDER Gábor <szeder.dev@gmail.com>
Helped-by: Randall Becker <rsbecker@nexbridge.com>
Suggested-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>
---
Sent as an RFC as it has been only lightly tested and because some of
the assumptions I have to make, made me unconfortable.

Ex, in order to make the atoi() calls safe, I was originally doing
is_digit(), but that would require this function to move further down
to work.

It is also now big enough that would make sense for it to move into
its own compat file and outside for git-compat-util.h, but if that is
done we might not keep the "root uid is not always 0" bits that seem
useful to have for the future.

getent() is not thread safe, so it might be worth to use an alternative
but that would require a bigger change.

IMHO it should have a test added, but not sure where it would fit.

Original discussion in :

  https://lore.kernel.org/git/4ef9287b-6260-9538-7c89-cffb611520ee@maurel.de/

 git-compat-util.h | 24 +++++++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)

Comments

Derrick Stolee April 26, 2022, 7:48 p.m. UTC | #1
On 4/26/2022 2:31 PM, 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 uid was
> root (because git was invoked through sudo or doas) and the effetive id

s/effetive/effective

> that repositiry trusted for its config was different, therefore failing

s/repositiry/repository

> the following common 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 those instead.
> 
> This assumes the environment the user is running with after going
> priviledged can't be tampered with, and also does the check only for

s/priviledged/privileged 

> 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 unpriviledged user.

s/unpriviledged/unpriviledged 

> Sent as an RFC as it has been only lightly tested and because some of
> the assumptions I have to make, made me unconfortable.

 
> Ex, in order to make the atoi() calls safe, I was originally doing
> is_digit(), but that would require this function to move further down
> to work.
>
> It is also now big enough that would make sense for it to move into
> its own compat file and outside for git-compat-util.h, but if that is
> done we might not keep the "root uid is not always 0" bits that seem
> useful to have for the future.
>
> getent() is not thread safe, so it might be worth to use an alternative
> but that would require a bigger change.

These points make sense. It would be worth taking our time and
doing some refactoring in advance of this functional change.
 
> IMHO it should have a test added, but not sure where it would fit.

I wonder how we could test a multi-user scenario. The tests I
added in e47363e5a (t0033: add tests for safe.directory, 2022-04-13)
purposefully avoid the user-id functionality.

> Original discussion in :
> 
>   https://lore.kernel.org/git/4ef9287b-6260-9538-7c89-cffb611520ee@maurel.de/

I agree that the idea behind this change is a good one. The escalation
of privilege isn't a huge concern when the "real" user is the same.
The only way to trick the root user here is to set an environment
variable, in which case they might as well modify PATH and be done with
it.

> +	euid = geteuid();
> +	if (euid == ROOT_UID) {
> +		/* we might have raised our priviledges with sudo or doas */

Similar spelling error here.

> +		const char *real_uid = getenv("SUDO_UID");
> +		if (real_uid && *real_uid)
> +			euid = atoi(real_uid);
> +		else {
> +			real_uid = getenv("DOAS_UID");
> +			if (real_uid && *real_uid)
> +				euid = atoi(real_uid);
> +		}

I imagine that something else could be added here to help Windows
users who have elevated to administrator privileges. It will use a
completely different mechanism, though, if needed at all. We can
delay that for now.

Thanks,
-Stolee
Junio C Hamano April 26, 2022, 7:56 p.m. UTC | #2
Derrick Stolee <derrickstolee@github.com> writes:

>> Original discussion in :
>> 
>>   https://lore.kernel.org/git/4ef9287b-6260-9538-7c89-cffb611520ee@maurel.de/
>
> I agree that the idea behind this change is a good one. The escalation
> of privilege isn't a huge concern when the "real" user is the same.
> The only way to trick the root user here is to set an environment
> variable, in which case they might as well modify PATH and be done with
> it.

How much do we really want to trust SUDO_UID or DOSA_UID are telling
the truth, though?

>> +	euid = geteuid();
>> +	if (euid == ROOT_UID) {
>> +		/* we might have raised our priviledges with sudo or doas */
>
> Similar spelling error here.
>
>> +		const char *real_uid = getenv("SUDO_UID");
>> +		if (real_uid && *real_uid)
>> +			euid = atoi(real_uid);
>> +		else {
>> +			real_uid = getenv("DOAS_UID");
>> +			if (real_uid && *real_uid)
>> +				euid = atoi(real_uid);
>> +		}
>
> I imagine that something else could be added here to help Windows
> users who have elevated to administrator privileges. It will use a
> completely different mechanism, though, if needed at all. We can
> delay that for now.
>
> Thanks,
> -Stolee
Randall S. Becker April 26, 2022, 8:10 p.m. UTC | #3
On April 26, 2022 3:56 PM, Junio C Hamano wrote:
>Subject: Re: [RFC PATCH] git-compat-util: avoid failing dir ownership checks if
>running priviledged
>
>Derrick Stolee <derrickstolee@github.com> writes:
>
>>> Original discussion in :
>>>
>>>
>>> https://lore.kernel.org/git/4ef9287b-6260-9538-7c89-cffb611520ee@maur
>>> el.de/
>>
>> I agree that the idea behind this change is a good one. The escalation
>> of privilege isn't a huge concern when the "real" user is the same.
>> The only way to trick the root user here is to set an environment
>> variable, in which case they might as well modify PATH and be done
>> with it.
>
>How much do we really want to trust SUDO_UID or DOSA_UID are telling the
>truth, though?
>
>>> +	euid = geteuid();
>>> +	if (euid == ROOT_UID) {
>>> +		/* we might have raised our priviledges with sudo or doas */
>>
>> Similar spelling error here.
>>
>>> +		const char *real_uid = getenv("SUDO_UID");
>>> +		if (real_uid && *real_uid)
>>> +			euid = atoi(real_uid);
>>> +		else {
>>> +			real_uid = getenv("DOAS_UID");
>>> +			if (real_uid && *real_uid)
>>> +				euid = atoi(real_uid);

This should be strtol() instead of atoi(). Putting garbage into DOAS_UID might end up causing some unwanted effects since atoi() could then return 0 or some partial value. The result should also be checked for sanity and the end pointer should point to a '\0'. My team has effectively banned the use of atoi() in new code and is migrating to strtol() or strtoll() as code is touched.

>>> +		}
>>
>> I imagine that something else could be added here to help Windows
>> users who have elevated to administrator privileges. It will use a
>> completely different mechanism, though, if needed at all. We can delay
>> that for now.
>>
>> Thanks,
>> -Stolee
Carlo Marcelo Arenas Belón April 26, 2022, 8:12 p.m. UTC | #4
On Tue, Apr 26, 2022 at 12:56 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> How much do we really want to trust SUDO_UID or DOSA_UID are telling
> the truth, though?

IMHO since we are only trusting this if the EUID is root it would require that
the root account was compromised already or running in a tampered environment.

for the absolutely paranoid we could trace back the process tree to make sure
the current session was indeed created by that tool, but if we are going that
way I think that trusting the ownership of the pty as was proposed[1] originally
would be simpler and is indeed how other tools (like who) deal with
that problem.

The advantage of trusting these variables is that we can keep the more common
case simpler and avoid the reported regression.

Carlo

[1] https://lore.kernel.org/git/20220425084003.nf267feurpqyvmsd@carlos-mbp.lan/
Carlo Marcelo Arenas Belón April 26, 2022, 8:26 p.m. UTC | #5
On Tue, Apr 26, 2022 at 12:48 PM Derrick Stolee
<derrickstolee@github.com> wrote:

> These points make sense. It would be worth taking our time and
> doing some refactoring in advance of this functional change.

Problem with taking our time or making it a bigger change is that
it is currently broken in the last maint versions so it would be ideal
to backport quickly there as a "regression".

> I wonder how we could test a multi-user scenario. The tests I
> added in e47363e5a (t0033: add tests for safe.directory, 2022-04-13)
> purposefully avoid the user-id functionality.

It would need to be a CI only test (where passwordless sudo is allowed)

> I imagine that something else could be added here to help Windows
> users who have elevated to administrator privileges. It will use a
> completely different mechanism, though, if needed at all. We can
> delay that for now.

Note this function is *NIX only, there is another one for Windows where
a similar fix (if needed) could be implemented

Carlo
Carlo Marcelo Arenas Belón April 26, 2022, 8:45 p.m. UTC | #6
On Tue, Apr 26, 2022 at 1:10 PM <rsbecker@nexbridge.com> wrote:
>
> Putting garbage into DOAS_UID might end up causing some unwanted effects

Since it was the root user who put garbage there, we will have to
trust it was not
unwanted.  My proposal to use is_digit() was to make sure we didn't get garbage
from the getenv() call (ex: "") that would confuse the logic, but if
there is some sudo
version that is saving the uid as "ThisIsGarbage" then that is a bug
better handled
somewhere else.

Agree with you that using strtol is better, but the added checks and
logic make it
more complicated and go against the assumption made in the commit message
that the environment CAN'T be tampered with.

Carlo
Junio C Hamano April 26, 2022, 9:10 p.m. UTC | #7
Carlo Arenas <carenas@gmail.com> writes:

> Agree with you that using strtol is better, but the added checks
> and logic make it more complicated and go against the assumption
> made in the commit message that the environment CAN'T be tampered
> with.

I think we require strto*l() here, to relieve us from worrying about
"is int large enough?" question.
Derrick Stolee April 29, 2022, 4:16 p.m. UTC | #8
On 4/26/2022 3:48 PM, Derrick Stolee wrote:
> On 4/26/2022 2:31 PM, Carlo Marcelo Arenas Belón wrote:

>> +		const char *real_uid = getenv("SUDO_UID");
>> +		if (real_uid && *real_uid)
>> +			euid = atoi(real_uid);
>> +		else {
>> +			real_uid = getenv("DOAS_UID");
>> +			if (real_uid && *real_uid)
>> +				euid = atoi(real_uid);
>> +		}
> 
> I imagine that something else could be added here to help Windows
> users who have elevated to administrator privileges. It will use a
> completely different mechanism, though, if needed at all. We can
> delay that for now.

Just chiming in to say that the way we do this check on Windows,
there is no difference between a user and the same user with
elevated privileges (the "Security Identifier (SID)" is the same
in both cases).

Thanks,
-Stolee
diff mbox series

Patch

diff --git a/git-compat-util.h b/git-compat-util.h
index 58fd813bd01..2ed97b47979 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -437,12 +437,34 @@  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
+
 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) {
+		/* we might have raised our priviledges with sudo or doas */
+		const char *real_uid = getenv("SUDO_UID");
+		if (real_uid && *real_uid)
+			euid = atoi(real_uid);
+		else {
+			real_uid = getenv("DOAS_UID");
+			if (real_uid && *real_uid)
+				euid = atoi(real_uid);
+		}
+	}
+	return st.st_uid == euid;
 }
 
 #define is_path_owned_by_current_user is_path_owned_by_current_uid