diff mbox series

[v3,3/3] t0034: enhance framework to allow testing more commands under sudo

Message ID 20220503065442.95699-4-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 3, 2022, 6:54 a.m. UTC
Add a support library that provides one function that can be used
to run a "scriplet" of commands through sudo and that has an
optional parameter (currently unused) to indicate which shell to
use to do so.

Add additional negative tests as suggested by Junio and that use
new workspace that is owned by root.

Note that in order to be able to call `test_must_fail sudo git status`
or an equivalent, test_must_fail will need to be enhanced or be able
to run under sudo, so fixing that has been punted, since the only
protection it affords is for `git status` not crashing, and that is
covered already by other tests.

Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>
---
 t/lib-sudo.sh                  | 13 +++++++
 t/t0034-root-safe-directory.sh | 70 +++++++++++++++++++++++++++++++++-
 2 files changed, 81 insertions(+), 2 deletions(-)
 create mode 100644 t/lib-sudo.sh

Comments

Phillip Wood May 3, 2022, 2:12 p.m. UTC | #1
Hi Carlo

On 03/05/2022 07:54, Carlo Marcelo Arenas Belón wrote:
> Add a support library that provides one function that can be used
> to run a "scriplet" of commands through sudo and that has an
> optional parameter (currently unused) to indicate which shell to
> use to do so.
> 
> Add additional negative tests as suggested by Junio and that use
> new workspace that is owned by root.
> 
> Note that in order to be able to call `test_must_fail sudo git status`
> or an equivalent, test_must_fail will need to be enhanced or be able
> to run under sudo, so fixing that has been punted, since the only
> protection it affords is for `git status` not crashing, and that is
> covered already by other tests.
> 
> Helped-by: Junio C Hamano <gitster@pobox.com>
> Signed-off-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>
> ---
>   t/lib-sudo.sh                  | 13 +++++++
>   t/t0034-root-safe-directory.sh | 70 +++++++++++++++++++++++++++++++++-
>   2 files changed, 81 insertions(+), 2 deletions(-)
>   create mode 100644 t/lib-sudo.sh
> 
> diff --git a/t/lib-sudo.sh b/t/lib-sudo.sh
> new file mode 100644
> index 00000000000..9ebb30fc82b
> --- /dev/null
> +++ b/t/lib-sudo.sh
> @@ -0,0 +1,13 @@
> +# Helpers for running git commands under sudo.
> +
> +# Runs a scriplet passed through stdin under sudo.
> +run_with_sudo () {
> +	local ret
> +	local SH=${1-"$TEST_SHELL_PATH"}

What use do you envisage for this? It would be simpler just to use 
$TEST_SHELL_PATH directly below

> +	local RUN="$HOME/$$.sh"

Can we used a fixed name for the script? That would make things simpler 
especially debugging as one would know what file to look for. Also using 
$TEST_DIRECTORY rather than $HOME would make it clear where the file 
ends up.

> +	write_script "$RUN" "$SH"
> +	sudo "$SH" -c "\"$RUN\""

I think using write_script means we can just do 'sudo "$RUN"'

> +	ret=$?
> +	rm -f "$RUN" > +	return $ret
> +}
> diff --git a/t/t0034-root-safe-directory.sh b/t/t0034-root-safe-directory.sh
> index dd659aed4e1..a68e1d7602b 100755
> --- a/t/t0034-root-safe-directory.sh
> +++ b/t/t0034-root-safe-directory.sh
> @@ -3,6 +3,7 @@
>   test_description='verify safe.directory checks while running as root'
>   
>   . ./test-lib.sh
> +. "$TEST_DIRECTORY"/lib-sudo.sh
>   
>   if [ "$IKNOWWHATIAMDOING" != "YES" ]
>   then
> @@ -10,6 +11,12 @@ then
>   	test_done
>   fi
>   
> +if ! test_have_prereq NOT_ROOT
> +then
> +	skip_all="No, you don't; these tests can't run as root"

I think the message would be friendlier without the "No, you don't" and 
just said that the tests cannot be run as root.

> +	test_done
> +fi
> +
>   # this prerequisite should be added to all the tests, it not only prevents
>   # the test from failing but also warms up any authentication cache sudo
>   # might need to avoid asking for a password
> @@ -40,8 +47,67 @@ test_expect_success SUDO 'sudo git status as original owner' '
>   	)
>   '
>   
> -# this MUST be always the last test, if used more than once, the next
> -# test should do a full setup again.

Why is the comment being changed? If you want the shorter version at the 
end of this patch can't we just use that wording in patch 1?


> +# this destroys the test environment used above
> +test_expect_success SUDO 'cleanup regression' '
> +	sudo rm -rf root
> +'
> +
> +if ! test_have_prereq SUDO
> +then
> +	skip_all="You need sudo to root for all remaining tests"
> +	test_done
> +fi
> +
> +test_expect_success SUDO 'setup root owned repository' '
> +	sudo mkdir -p root/p &&
> +	sudo git init root/p
> +'
> +
> +test_expect_success 'cannot access if owned by root' '
> +	(
> +		cd root/p &&
> +		test_must_fail git status
> +	)
> +'
> +
> +test_expect_success SUDO 'cannot access with sudo' '
> +	(
> +		# TODO: test_must_fail needs additional functionality
> +		# 6a67c759489 blocks its use with sudo
> +		cd root/p &&
> +		! sudo git status
> +	)
> +'

I think Junio suggested that this should work and showed it was simple 
to make it work. It seems funny that if sudo is started as root it does 
not work.

> +test_expect_success SUDO 'can access using a workaround' '
> +	# run sudo twice
> +	(
> +		cd root/p &&
> +		run_with_sudo <<-END
> +			sudo git status
> +		END
> +	) &&
> +	# provide explicit GIT_DIR
> +	(
> +		cd root/p &&
> +		run_with_sudo <<-END
> +			GIT_DIR=.git &&
> +			GIT_WORK_TREE=. &&
> +			export GIT_DIR GIT_WORK_TREE &&
> +			git status

I'm confused by this. Does this mean we don't do the ownership checks if 
GIT_DIR and or GIT_WORK_TREE are set in the environment?


Thanks for working on this

Best Wishes

Phillip


> +		END
> +	) &&
> +	# discard SUDO_UID
> +	(
> +		cd root/p &&
> +		run_with_sudo <<-END
> +			unset SUDO_UID &&
> +			git status
> +		END
> +	)
> +'
> +
> +# this MUST be always the last test
>   test_expect_success SUDO 'cleanup' '
>   	sudo rm -rf root
>   '
Junio C Hamano May 3, 2022, 3:27 p.m. UTC | #2
Phillip Wood <phillip.wood123@gmail.com> writes:

(I'd be brief as it is my day-off today).

>> +test_expect_success SUDO 'cannot access with sudo' '
>> +	(
>> +		# TODO: test_must_fail needs additional functionality
>> +		# 6a67c759489 blocks its use with sudo
>> +		cd root/p &&
>> +		! sudo git status
>> +	)
>> +'
>
> I think Junio suggested that this should work and showed it was simple
> to make it work. It seems funny that if sudo is started as root it
> does not work.

It does feel odd.  Any attacker who can prepare a root-owned trap do
not have to trick "sudo git" to fall there, so I personally do not
see much value in stopping this particular pattern, but since
workarounds are easy (like double sudo), I do not mind if this does
not work *IF* there is a good reason to stop this.

>> +test_expect_success SUDO 'can access using a workaround' '
>> +	# run sudo twice
>> +	(
>> +		cd root/p &&
>> +		run_with_sudo <<-END
>> +			sudo git status
>> +		END
>> +	) &&
>> +	# provide explicit GIT_DIR
>> +	(
>> +		cd root/p &&
>> +		run_with_sudo <<-END
>> +			GIT_DIR=.git &&
>> +			GIT_WORK_TREE=. &&
>> +			export GIT_DIR GIT_WORK_TREE &&
>> +			git status
>
> I'm confused by this. Does this mean we don't do the ownership checks
> if GIT_DIR and or GIT_WORK_TREE are set in the environment?

That's by design.  Remember, we are protecting people from "cd"ing
into a (sub)directory, unknowingly, that happens to be controled by
a Git repository somebody else prepared as a trap.  So we try to
ensure who owns such a repository when we do the discovery.  Users
who set these environment variables to tell Git that they are aware
that they are working with that directory are different from the
target audience.

> Thanks for working on this

Ditto.  And thanks for reviewing and raising questions.  I agree
with all the things you said in the part of the message I did not
touch in this reply.
Carlo Marcelo Arenas Belón May 6, 2022, 4:54 p.m. UTC | #3
On Tue, May 3, 2022 at 7:12 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
> > diff --git a/t/lib-sudo.sh b/t/lib-sudo.sh
> > new file mode 100644
> > index 00000000000..9ebb30fc82b
> > --- /dev/null
> > +++ b/t/lib-sudo.sh
> > @@ -0,0 +1,13 @@
> > +# Helpers for running git commands under sudo.
> > +
> > +# Runs a scriplet passed through stdin under sudo.
> > +run_with_sudo () {
> > +     local ret
> > +     local SH=${1-"$TEST_SHELL_PATH"}
>
> What use do you envisage for this? It would be simpler just to use
> $TEST_SHELL_PATH directly below

Since this function is a ripoff of write_script which has this feature
(except I changed the shell used), I thought it might come handy later
and that getting the first version with a hardcoded value wasn't
elegant enough, but you are right it is not needed, so is gone in the
next RFC.

> > +     local RUN="$HOME/$$.sh"
>
> Can we use a fixed name for the script? That would make things simpler
> especially debugging as one would know what file to look for. Also using
> $TEST_DIRECTORY rather than $HOME would make it clear where the file
> ends up.

Frankly (and speaking by experience because this broke a lot for me),
debugging is painful enough already that having to find the shell
script would be the least of your concerns.

The main reason why it is not hardcoded to a known name is that by
adding a slightly more random name, it makes it slightly more
difficult for someone to try to trick us into running something else
they prepared instead, and the fact that it will be run as root makes
it much more enticing.

Sorry about not using TEST_DIRECTORY to begin with, they are set to
the same value and thought HOME was clearer and shorter, but has been
fixed in the next RFC.

> > +     write_script "$RUN" "$SH"
> > +     sudo "$SH" -c "\"$RUN\""
>
> I think using write_script means we can just do 'sudo "$RUN"'

We could, but by doing it this way we ensure:
* we specifically require sudo to allow running a shell, and that
shell is the one we want it to run (a strict sudo wouldn't do that)
* we obscure a little bit what sudo is running from it and rely more
in the shell, so it won't try to be helpful and block it (running
shell scripts is something a strict sudo might not allow as well)

On that last point I am still debating if the prerequisite should be
enhanced to detect that case and fail, but since it is restrictive
enough already it might not be worth doing now.

> > -# this MUST be always the last test, if used more than once, the next
> > -# test should do a full setup again.
>
> Why is the comment being changed? If you want the shorter version at the
> end of this patch can't we just use that wording in patch 1?

It was my failed attempt to document better how they are used, since
they are tricky enough to use already, but it is no longer needed and
is gone in the next RFC.

> > +test_expect_success SUDO 'cannot access with sudo' '
> > +     (
> > +             # TODO: test_must_fail needs additional functionality
> > +             # 6a67c759489 blocks its use with sudo
> > +             cd root/p &&
> > +             ! sudo git status
> > +     )
> > +'
>
> I think Junio suggested that this should work and showed it was simple
> to make it work. It seems funny that if sudo is started as root it does
> not work.

It is not when sudo is started as root, but when the user runs `sudo
-s` to get a shell and then LATER tries to use git with that shell.

I make a better attempt to explain it in a different thread[1], and is
tied to the documentation patch which might need further improvements
to make clear (hopefully not as extensive as my attempts to do so)

I am instead marking it as a known bug for the next RFC

Carlo

[1] https://lore.kernel.org/git/20220429012438.37o4uaxsrfdu2b6x@carlos-mbp.lan/
diff mbox series

Patch

diff --git a/t/lib-sudo.sh b/t/lib-sudo.sh
new file mode 100644
index 00000000000..9ebb30fc82b
--- /dev/null
+++ b/t/lib-sudo.sh
@@ -0,0 +1,13 @@ 
+# Helpers for running git commands under sudo.
+
+# Runs a scriplet passed through stdin under sudo.
+run_with_sudo () {
+	local ret
+	local SH=${1-"$TEST_SHELL_PATH"}
+	local RUN="$HOME/$$.sh"
+	write_script "$RUN" "$SH"
+	sudo "$SH" -c "\"$RUN\""
+	ret=$?
+	rm -f "$RUN"
+	return $ret
+}
diff --git a/t/t0034-root-safe-directory.sh b/t/t0034-root-safe-directory.sh
index dd659aed4e1..a68e1d7602b 100755
--- a/t/t0034-root-safe-directory.sh
+++ b/t/t0034-root-safe-directory.sh
@@ -3,6 +3,7 @@ 
 test_description='verify safe.directory checks while running as root'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-sudo.sh
 
 if [ "$IKNOWWHATIAMDOING" != "YES" ]
 then
@@ -10,6 +11,12 @@  then
 	test_done
 fi
 
+if ! test_have_prereq NOT_ROOT
+then
+	skip_all="No, you don't; these tests can't run as root"
+	test_done
+fi
+
 # this prerequisite should be added to all the tests, it not only prevents
 # the test from failing but also warms up any authentication cache sudo
 # might need to avoid asking for a password
@@ -40,8 +47,67 @@  test_expect_success SUDO 'sudo git status as original owner' '
 	)
 '
 
-# this MUST be always the last test, if used more than once, the next
-# test should do a full setup again.
+# this destroys the test environment used above
+test_expect_success SUDO 'cleanup regression' '
+	sudo rm -rf root
+'
+
+if ! test_have_prereq SUDO
+then
+	skip_all="You need sudo to root for all remaining tests"
+	test_done
+fi
+
+test_expect_success SUDO 'setup root owned repository' '
+	sudo mkdir -p root/p &&
+	sudo git init root/p
+'
+
+test_expect_success 'cannot access if owned by root' '
+	(
+		cd root/p &&
+		test_must_fail git status
+	)
+'
+
+test_expect_success SUDO 'cannot access with sudo' '
+	(
+		# TODO: test_must_fail needs additional functionality
+		# 6a67c759489 blocks its use with sudo
+		cd root/p &&
+		! sudo git status
+	)
+'
+
+test_expect_success SUDO 'can access using a workaround' '
+	# run sudo twice
+	(
+		cd root/p &&
+		run_with_sudo <<-END
+			sudo git status
+		END
+	) &&
+	# provide explicit GIT_DIR
+	(
+		cd root/p &&
+		run_with_sudo <<-END
+			GIT_DIR=.git &&
+			GIT_WORK_TREE=. &&
+			export GIT_DIR GIT_WORK_TREE &&
+			git status
+		END
+	) &&
+	# discard SUDO_UID
+	(
+		cd root/p &&
+		run_with_sudo <<-END
+			unset SUDO_UID &&
+			git status
+		END
+	)
+'
+
+# this MUST be always the last test
 test_expect_success SUDO 'cleanup' '
 	sudo rm -rf root
 '