diff mbox series

[v4,1/3] t: regression git needs safe.directory when using sudo

Message ID 20220510174616.18629-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 May 10, 2022, 5:46 p.m. UTC
Originally reported after release of v2.35.2 (and other maint branches)
for CVE-2022-24765 and blocking otherwise harmless commands that were
done using sudo in a repository that was owned by the user.

Add a new test script with very basic support to allow running git
commands through sudo, so a reproduction could be implemented and that
uses only `git status` as a proxy of the issue reported.

Note that because of the way sudo interacts with the system, a much
more complete integration with the test framework will require a lot
more work and that was therefore intentionally punted for now.

The current implementation requires the execution of a special cleanup
function which should always be kept as the last "test" or otherwise
the standard cleanup functions will fail because they can't remove
the root owned directories that are used.  This also means that if
failures are found while running, the specifics of the failure might
not be kept for further debugging and if the test was interrupted, it
will be necessary to clean the working directory manually before
restarting by running:

  $ sudo rm -rf trash\ directory.t0034-root-safe-directory/

The test file also uses at least one initial "setup" test that creates
a parallel execution directory, while ignoring the repository created
by the test framework.

Special care should be taken when invoking commands through sudo, since
the environment is otherwise independent from what the test framework
setup and might have changed the values for HOME, SHELL and dropped
several relevant environment variables for your test.  Indeed `git status`
was used as a proxy because it doesn't even require commits in the
repository to work and usually doesn't require much from the environment
to run, but a future patch will add calls to `git init` and that will
fail to honor the default branch name, unless that setting is NOT
provided through an environment variable (which means even a CI run
could fail that test if enabled incorrectly).

A new SUDO prerequisite is provided that does some sanity checking
to make sure the sudo command that will be used allows for passwordless
execution as root without restrictions and doesn't mess with git's
execution path.  This matches what is provided by the macOS agents that
are used as part of GitHub actions and probably nowhere else.

Most of those characteristics make this test mostly only suitable for
CI, but it might be executed locally if special care is taken to provide
for all of them in the local configuration and maybe making use of the
sudo credential cache by first invoking sudo, entering your password if
needed, and then invoking the test with:

  $ GIT_TEST_ALLOW_SUDO=YES ./t0034-root-safe-directory.sh

If it fails to run, then it means your local setup wouldn't work for the
test because of the configuration sudo has or other system settings, and
things that might help are to comment out sudo's secure_path config, and
make sure that the account you are using has no restrictions on the
commands it can run through sudo, just like is provided for the user in
the CI.

For example (assuming a username of marta for you) something probably
similar to the following entry in your /etc/sudoers (or equivalent) file:

  marta	ALL=(ALL:ALL) NOPASSWD: ALL

Reported-by: SZEDER Gábor <szeder.dev@gmail.com>
Helped-by: Phillip Wood <phillip.wood123@gmail.com>
Signed-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>
---
 t/t0034-root-safe-directory.sh | 45 ++++++++++++++++++++++++++++++++++
 1 file changed, 45 insertions(+)
 create mode 100755 t/t0034-root-safe-directory.sh

Comments

Junio C Hamano May 10, 2022, 10:10 p.m. UTC | #1
Carlo Marcelo Arenas Belón  <carenas@gmail.com> writes:

> Note that because of the way sudo interacts with the system, a much
> more complete integration with the test framework will require a lot
> more work and that was therefore intentionally punted for now.
>
> The current implementation requires ...
> ...
> If it fails to run, then it means your local setup wouldn't work for the
> test because of the configuration sudo has or other system settings, and
> things that might help are to comment out sudo's secure_path config, and
> make sure that the account you are using has no restrictions on the
> commands it can run through sudo, just like is provided for the user in
> the CI.
>
> For example (assuming a username of marta for you) something probably
> similar to the following entry in your /etc/sudoers (or equivalent) file:
>
>   marta	ALL=(ALL:ALL) NOPASSWD: ALL
>
> Reported-by: SZEDER Gábor <szeder.dev@gmail.com>
> Helped-by: Phillip Wood <phillip.wood123@gmail.com>
> Signed-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>

Very well written.

> +test_lazy_prereq SUDO '
> +	sudo -n id -u >u &&
> +	id -u root >r &&
> +	test_cmp u r &&
> +	command -v git >u &&
> +	sudo command -v git >r &&
> +	test_cmp u r
> +'

I vaguely recall mentions of older dash that lack "command -v" made
earlier, but implementations of dash I have handy seem to know it.
I am personally fine with this as this script has a very narrow and
limited audience in mind.

> +test_expect_success SUDO 'setup' '
> +	sudo rm -rf root &&
> +	mkdir -p root/r &&
> +	sudo chown root root &&
> +	(
> +		cd root/r &&
> +		git init
> +	)
> +'

So, "root/" is owned by root, "root/r" is owned by the tester.

> +test_expect_failure SUDO 'sudo git status as original owner' '
> +	(
> +		cd root/r &&
> +		git status &&

The tester runs "git status" in "root/r" owned by the tester and it
should succeed.

> +		sudo git status

We want the tester to be able to do the same while temporarily
becoming 'root' with "sudo", but we know it fails right now.

> +	)
> +'

Mental note.  We do not need root to be owned by 'root' with the
tests we see here.  Perhaps we would add some that requires it in
later patches.  We'll see.

> +# this MUST be always the last test
> +test_expect_success SUDO 'cleanup' '
> +	sudo rm -rf root
> +'
> +
> +test_done

So far, looking good.

Thanks.
Carlo Marcelo Arenas Belón May 10, 2022, 11:11 p.m. UTC | #2
On Tue, May 10, 2022 at 3:10 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Carlo Marcelo Arenas Belón  <carenas@gmail.com> writes:
>
> > +test_lazy_prereq SUDO '
> > +     sudo -n id -u >u &&
> > +     id -u root >r &&
> > +     test_cmp u r &&
> > +     command -v git >u &&
> > +     sudo command -v git >r &&
> > +     test_cmp u r
> > +'
>
> I vaguely recall mentions of older dash that lack "command -v" made
> earlier, but implementations of dash I have handy seem to know it.
> I am personally fine with this as this script has a very narrow and
> limited audience in mind.

I did check that, but think the report was mistaken.
Debian, Ubuntu, NetBSD and OpenBSD would fail the same way here, but
it is not because of the use of dash, as much as sudo NOT being
configured to default to `-s` mode.

dscho was right to point out that I should had usen type instead, but
that wouldn't work because of the mismatch of shells and therefore the
mismatch of outputs, so I went with command instead as an extra clever
way to make sure both the shell inside and outside were most likely
the same, even if some sudo somewhere decides in the name of security
not to respect its own "-s mode" and force a "safer" shell.

I have a real fix for this which will be released later as part of
that "better integration with the framework", which basically makes
sure all invocations through sudo are done through the test shell
(just like that ugly function that gets added in patch 3), but it
requires changing write_shell and therefore not something that is
worth doing now.

> +test_expect_success SUDO 'setup' '
> > +     sudo rm -rf root &&
> > +     mkdir -p root/r &&
> > +     sudo chown root root &&
> > +     (
> > +             cd root/r &&
> > +             git init
> > +     )
> > +'
>
> So, "root/" is owned by root, "root/r" is owned by the tester.

It doesn't need to be root, but needs to be different than "tester",
and since I know root is different and I validated in the prerequisite
that I can sudo to it, that is what is used here.

> > +test_expect_failure SUDO 'sudo git status as original owner' '
> > +     (
> > +             cd root/r &&
> > +             git status &&
>
> The tester runs "git status" in "root/r" owned by the tester and it
> should succeed.
>
> > +             sudo git status
>
> We want the tester to be able to do the same while temporarily
> becoming 'root' with "sudo", but we know it fails right now.
>
> > +     )
> > +'
>
> Mental note.  We do not need root to be owned by 'root' with the
> tests we see here.  Perhaps we would add some that requires it in
> later patches.  We'll see.

I am not good with subtle messages in a foreign language, but is this
a way to imply that I shouldn't need to chown and instead use the
GIT_TEST_PRETEND feature more?

frankly I might had overused sudo, but it is because every extra
invocation refreshes the cache, and all tests depend on SUDO anyway,
so I wanted to make sure they were also more easily reconizable for
the real thing.

Carlo
Junio C Hamano May 10, 2022, 11:44 p.m. UTC | #3
Carlo Arenas <carenas@gmail.com> writes:

>> > +test_lazy_prereq SUDO '
>> > +     sudo -n id -u >u &&
>> > +     id -u root >r &&
>> > +     test_cmp u r &&
>> > +     command -v git >u &&
>> > +     sudo command -v git >r &&
>> > +     test_cmp u r
>> > +'
>>
>> I vaguely recall mentions of older dash that lack "command -v" made
>> earlier, but implementations of dash I have handy seem to know it.
>> I am personally fine with this as this script has a very narrow and
>> limited audience in mind.
>
> I did check that, but think the report was mistaken.
> Debian, Ubuntu, NetBSD and OpenBSD would fail the same way here, but
> it is not because of the use of dash, as much as sudo NOT being
> configured to default to `-s` mode.

OK.

> dscho was right to point out that I should had usen type instead, but
> that wouldn't work because of the mismatch of shells and therefore the
> mismatch of outputs, so I went with command instead as an extra clever
> way to make sure both the shell inside and outside were most likely
> the same, even if some sudo somewhere decides in the name of security
> not to respect its own "-s mode" and force a "safer" shell.

In this particular case, "command -v" is the right thing to use, as
you care where the command is found on the $PATH and "type --path"
is *NOT* portable.

>> > +     sudo chown root root &&
>> > +     (
>> > +             cd root/r &&
>> > +             git init
>> > +     )
>> > +'
>>
>> So, "root/" is owned by root, "root/r" is owned by the tester.
>
> It doesn't need to be root, but needs to be different than "tester",

To make sure you do not misunderstand, I know the ownership of
root/r and root/p matter---tester and root must be different.  And
we use these two as sample repositories owned by two different
users.

What I am pointing out here is the root/ directory itself.  I do not
think its ownership does not matter anywhere in these new tests (not
just this patch, but after applying all three patches).

>> > +test_expect_failure SUDO 'sudo git status as original owner' '
>> > +     (
>> > +             cd root/r &&
>> > +             git status &&
>>
>> The tester runs "git status" in "root/r" owned by the tester and it
>> should succeed.
>>
>> > +             sudo git status
>>
>> We want the tester to be able to do the same while temporarily
>> becoming 'root' with "sudo", but we know it fails right now.
>>
>> > +     )
>> > +'
>>
>> Mental note.  We do not need root to be owned by 'root' with the
>> tests we see here.  Perhaps we would add some that requires it in
>> later patches.  We'll see.
>
> I am not good with subtle messages in a foreign language, but is this
> a way to imply that I shouldn't need to chown and instead use the
> GIT_TEST_PRETEND feature more?

No.  I just said I made a mental note of the fact that the ownership
of root/ directory (not root/r which is the other directory this
test script creates in this step) does not matter at least in patch
1/3, but I cannot say, by that observation alone, that chown we saw
earlier should be removed, because patches 2/3 and 3/3 might start
requiring root/ to be owned by 'root'.  Hence "I made a mental note
here.  We do not have anything that requires the above chown in this
patch, but we might see something that makes it a good idea to keep
the chown in later patches".

I do not think GIT_TEST_PRETEND needs to be involved at all.  It's
just root/ can be left owned by the tester, because we only care
about the owners of root/p and root/r in these three patches.

Thanks.
Carlo Marcelo Arenas Belón May 11, 2022, 12:56 a.m. UTC | #4
On Tue, May 10, 2022 at 4:44 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Carlo Arenas <carenas@gmail.com> writes:
> >>
> >> Mental note.  We do not need root to be owned by 'root' with the
> >> tests we see here.  Perhaps we would add some that requires it in
> >> later patches.  We'll see.
> >
> > I am not good with subtle messages in a foreign language, but is this
> > a way to imply that I shouldn't need to chown and instead use the
> > GIT_TEST_PRETEND feature more?
>
> No.  I just said I made a mental note of the fact that the ownership
> of root/ directory (not root/r which is the other directory this
> test script creates in this step) does not matter at least in patch
> 1/3, but I cannot say, by that observation alone, that chown we saw
> earlier should be removed, because patches 2/3 and 3/3 might start
> requiring root/ to be owned by 'root'.  Hence "I made a mental note
> here.  We do not have anything that requires the above chown in this
> patch, but we might see something that makes it a good idea to keep
> the chown in later patches".

got it; we actually DO (or at least I intended to, but didn't because
of a bug and complicating the tests unnecessarily).but then as usual I
didn't document it well, apologize for that.

`root` is called that way because it was meant to be a ceiling of
sorts and used as a safetynet (like GIT_CEILING in the test suite) to
block the tests in this file for going up one more level and using the
default git repository from the suite by mistake.  That is also why it
is owned by root.

of course later I find out that for it to really stop the walking it
needed a .git on its own and to create one I needed to do it as root
which I didn't even attempt to do until patch 3 (at that time too, I
was thinking maybe I could get patch 1 and 2 ready first, so my name
wouldn't be on every git update and one of the reasons why nobody can
get anything merged into next)

one option would be to consolidate its use with the root repository
that gets created in patch 3, which I could have done originally
instead of just using the ones you provided almost AS-IS, or as you
pointed out we could remove the safety net since it is not needed and
it is buggy anyway.

will remove the chown in v5 otherwise but I think the plan above
shouldn't be that intrusive and might be better.

Carlo
Junio C Hamano May 11, 2022, 1:11 a.m. UTC | #5
Carlo Arenas <carenas@gmail.com> writes:

> one option would be to consolidate its use with the root repository
> that gets created in patch 3, which I could have done originally
> instead of just using the ones you provided almost AS-IS, or as you
> pointed out we could remove the safety net since it is not needed and
> it is buggy anyway.

I agree that there is no point to chown the directory, especially if
it does not offer any additional safety of any sort.  And quite
honstly, a test that is designed to be run ONLY in a very controlled
CI environment, it does not need one that complicates the tests.

Thanks.
diff mbox series

Patch

diff --git a/t/t0034-root-safe-directory.sh b/t/t0034-root-safe-directory.sh
new file mode 100755
index 0000000000..2e4492a66d
--- /dev/null
+++ b/t/t0034-root-safe-directory.sh
@@ -0,0 +1,45 @@ 
+#!/bin/sh
+
+test_description='verify safe.directory checks while running as root'
+
+. ./test-lib.sh
+
+if [ "$GIT_TEST_ALLOW_SUDO" != "YES" ]
+then
+	skip_all="You must set env var GIT_TEST_ALLOW_SUDO=YES in order to run this test"
+	test_done
+fi
+
+test_lazy_prereq SUDO '
+	sudo -n id -u >u &&
+	id -u root >r &&
+	test_cmp u r &&
+	command -v git >u &&
+	sudo command -v git >r &&
+	test_cmp u r
+'
+
+test_expect_success SUDO 'setup' '
+	sudo rm -rf root &&
+	mkdir -p root/r &&
+	sudo chown root root &&
+	(
+		cd root/r &&
+		git init
+	)
+'
+
+test_expect_failure SUDO 'sudo git status as original owner' '
+	(
+		cd root/r &&
+		git status &&
+		sudo git status
+	)
+'
+
+# this MUST be always the last test
+test_expect_success SUDO 'cleanup' '
+	sudo rm -rf root
+'
+
+test_done