diff mbox series

[1/1] Git.pm: add semicolon after catch statement

Message ID 20221016212236.12453-2-michael@mcclimon.org (mailing list archive)
State Accepted
Commit 77a1310e6b3f39f405275faa8c5af02cf5453cb6
Headers show
Series Git.pm: add semicolon after catch statement | expand

Commit Message

Michael McClimon Oct. 16, 2022, 9:22 p.m. UTC
When attempting to initialize a repository object in an unsafe
directory, a syntax error is reported (Can't use string as a HASH ref
while strict refs in use). Fix this runtime error by adding the required
semicolon after the catch statement.

Without the semicolon, the result of the following line (i.e., the
result of Cwd::abs_path) is passed as the third argument to Error.pm's
catch function. That function expects that its third argument,
$clauses, is a hash reference, and trying to access a string as a hash
reference is a fatal error.

[1] https://lore.kernel.org/git/20221011182607.f1113fff-9333-427d-ba45-741a78fa6040@korelogic.com/

Reported-by: Hank Leininger <hlein@korelogic.com>
Signed-off-by: Michael McClimon <michael@mcclimon.org>
---
 perl/Git.pm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

Comments

Jeff King Oct. 16, 2022, 11:18 p.m. UTC | #1
On Sun, Oct 16, 2022 at 05:22:36PM -0400, Michael McClimon wrote:

> When attempting to initialize a repository object in an unsafe
> directory, a syntax error is reported (Can't use string as a HASH ref
> while strict refs in use). Fix this runtime error by adding the required
> semicolon after the catch statement.
> 
> Without the semicolon, the result of the following line (i.e., the
> result of Cwd::abs_path) is passed as the third argument to Error.pm's
> catch function. That function expects that its third argument,
> $clauses, is a hash reference, and trying to access a string as a hash
> reference is a fatal error.

Curiously this works as expected for me, both before and after your
patch. I wonder if it depends on perl version. Mine is 5.34.

I've never used Error.pm's try/catch before, so I don't know what's
normal. Regular if/unless doesn't need it, but certainly an earlier
catch uses a semicolon. So it seems like a reasonable fix.

> diff --git a/perl/Git.pm b/perl/Git.pm
> index 080cdc2a..cf15ead6 100644
> --- a/perl/Git.pm
> +++ b/perl/Git.pm
> @@ -217,7 +217,7 @@ sub repository {
>  			} catch Git::Error::Command with {
>  				# Mimic git-rev-parse --git-dir error message:
>  				throw Error::Simple("fatal: Not a git repository: $dir");
> -			}
> +			};

I'd assume t9700 passes for you, since I don't think we cover this case.
Maybe it's worth squashing this in:

diff --git a/t/t9700/test.pl b/t/t9700/test.pl
index e046f7db76..5bd3687f37 100755
--- a/t/t9700/test.pl
+++ b/t/t9700/test.pl
@@ -30,6 +30,12 @@ sub adjust_dirsep {
 # set up
 our $abs_repo_dir = cwd();
 ok(our $r = Git->repository(Directory => "."), "open repository");
+{
+	local $ENV{GIT_TEST_ASSUME_DIFFERENT_OWNER} = 1;
+	my $failed = eval { Git->repository(Directory => ".") };
+	ok(!$failed, "reject unsafe repository");
+	like($@, qr/not a git repository/i, "unsafe error message");
+}
 
 # config
 is($r->config("test.string"), "value", "config scalar: string");
Michael McClimon Oct. 17, 2022, 2:17 a.m. UTC | #2
> Curiously this works as expected for me, both before and after your
> patch. I wonder if it depends on perl version. Mine is 5.34.

Hm, curious indeed! It reliably fails without my patch and passes with it on
all the versions I had lying around (5.8, 5.18, 5.24, 5.26, 5.28, 5.30, 5.34,
and 5.36).

> I've never used Error.pm's try/catch before, so I don't know what's
> normal. Regular if/unless doesn't need it, but certainly an earlier
> catch uses a semicolon. So it seems like a reasonable fix.

Ha, well Perl is...let's say special. try/catch is not a language construct
(until 5.34, where it is experimental), and so it's always implemented by
subroutines. One upshot of this is that try/catch needs a semicolon, because
it's sugar for try(sub { ... }), and statements need semicolons separating
them. 

Compare these two examples: -MO=Deparse,-p tells perl to deparse the program
and print it with parentheses:

    $ perl -MError -MO=Deparse,-p -e 'try { die "bad" } catch Pkg with { warn "caught" }; print "after"'
    do {
        die('bad')
    }->try('Pkg'->catch(do {
        warn('caught')
    }->with));
    print('after');
    -e syntax OK

    $ perl -MError -MO=Deparse,-p -e 'try { die "bad" } catch Pkg with { warn "caught" } print "after"'
    do {
        die('bad')
    }->try('Pkg'->catch(do {
        warn('caught')
    }->with(print('after'))));
    -e syntax OK

The first here is the good case case (with semicolon), and you can see that
print('after') is its own statement. The bad case, with no semicolon, passes
it as an argument to with(), which is what's causing the error here: something
called by with() is trying to use it as a hash reference, and it's a string.

> I'd assume t9700 passes for you, since I don't think we cover this case.
> Maybe it's worth squashing this in:
> 
> diff --git a/t/t9700/test.pl b/t/t9700/test.pl
> index e046f7db76..5bd3687f37 100755
> --- a/t/t9700/test.pl
> +++ b/t/t9700/test.pl
> @@ -30,6 +30,12 @@ sub adjust_dirsep {
>  # set up
>  our $abs_repo_dir = cwd();
>  ok(our $r = Git->repository(Directory => "."), "open repository");
> +{
> +	local $ENV{GIT_TEST_ASSUME_DIFFERENT_OWNER} = 1;
> +	my $failed = eval { Git->repository(Directory => ".") };
> +	ok(!$failed, "reject unsafe repository");
> +	like($@, qr/not a git repository/i, "unsafe error message");
> +}
>  
>  # config
>  is($r->config("test.string"), "value", "config scalar: string");

Curiously, t9700 passes for me with this suggestion both with and without my
patch. You'd only see this bug in bare repositories, though, and the one set
up in t9700 is not bare. I can see about trying to make it do so, but I'll
need to do a bit more reading of how even the tests are set up and run first.
Thanks!
Jeff King Oct. 17, 2022, 5:34 p.m. UTC | #3
[+cc people who worked on safe-directory stuff; please check out the
included test and final comments]

On Sun, Oct 16, 2022 at 10:17:53PM -0400, Michael McClimon wrote:

> > Curiously this works as expected for me, both before and after your
> > patch. I wonder if it depends on perl version. Mine is 5.34.
> 
> Hm, curious indeed! It reliably fails without my patch and passes with it on
> all the versions I had lying around (5.8, 5.18, 5.24, 5.26, 5.28, 5.30, 5.34,
> and 5.36).

Doh, sorry to mislead you; I hadn't noticed this was in the bare
repository code path until you pointed it out. I get the same outcome as
you and the OP once that is fixed (both in t9700 and in my manual
testing).

> Ha, well Perl is...let's say special. try/catch is not a language construct
> (until 5.34, where it is experimental), and so it's always implemented by
> subroutines. One upshot of this is that try/catch needs a semicolon, because
> it's sugar for try(sub { ... }), and statements need semicolons separating
> them.

Right, I imagined it was something like that. Your fix is definitely the
right thing, then.

> Curiously, t9700 passes for me with this suggestion both with and without my
> patch. You'd only see this bug in bare repositories, though, and the one set
> up in t9700 is not bare. I can see about trying to make it do so, but I'll
> need to do a bit more reading of how even the tests are set up and run first.

Yeah, this test is particularly confusing because unlike most of our
suite, it drives the test harness using a separate perl script. So you
have setup in one file and tests in another.

You'd want something like:

diff --git a/t/t9700-perl-git.sh b/t/t9700-perl-git.sh
index 4aa5d90d32..53a838a8e8 100755
--- a/t/t9700-perl-git.sh
+++ b/t/t9700-perl-git.sh
@@ -45,6 +45,10 @@ test_expect_success \
      git config --add test.pathmulti bar
      '
 
+test_expect_success 'set up bare repository' '
+        git init --bare bare.git
+'
+
 test_expect_success 'use t9700/test.pl to test Git.pm' '
 	"$PERL_PATH" "$TEST_DIRECTORY"/t9700/test.pl 2>stderr &&
 	test_must_be_empty stderr
diff --git a/t/t9700/test.pl b/t/t9700/test.pl
index e046f7db76..917b09cdf9 100755
--- a/t/t9700/test.pl
+++ b/t/t9700/test.pl
@@ -30,6 +30,12 @@ sub adjust_dirsep {
 # set up
 our $abs_repo_dir = cwd();
 ok(our $r = Git->repository(Directory => "."), "open repository");
+{
+	local $ENV{GIT_TEST_ASSUME_DIFFERENT_OWNER} = 1;
+	my $failed = eval { Git->repository(Directory => "$abs_repo_dir/bare.git") };
+	ok(!$failed, "reject unsafe bare repository");
+	like($@, qr/not a git repository/i, "unsafe error message");
+}
 
 # config
 is($r->config("test.string"), "value", "config scalar: string");

But curiously this still does not pass after your patch, because we seem
to actually open the repository! I think this is because the C code
allows an explicit GIT_DIR to override the safe-directory checks. But in
this case that GIT_DIR is set by Git.pm searching for the directory, not
because the user desires it.

(I know that I used a "Directory" in the example above, but that is only
to avoid extra chdir-ing around in the test script; calling a bare
Git->repository() triggers the same behavior in the right directory).

So your patch is definitely still the right thing to do, but it feels
like a hole in the safe-directory mechanism, at least when called via
Git.pm. +cc folks who worked on that.

-Peff
Michael McClimon Oct. 18, 2022, 1:39 a.m. UTC | #4
> Yeah, this test is particularly confusing because unlike most of our
> suite, it drives the test harness using a separate perl script. So you
> have setup in one file and tests in another.

Oh good, I'm glad it wasn't just me; this was very helpful, thanks.

> 
> But curiously this still does not pass after your patch, because we seem
> to actually open the repository! I think this is because the C code
> allows an explicit GIT_DIR to override the safe-directory checks. But in
> this case that GIT_DIR is set by Git.pm searching for the directory, not
> because the user desires it.

Aha; this gets to what I was saying in the cover letter, which is that my
patch only fixes the runtime error from perl, and Git.pm happily carries on in
an unsafe directory. Fixing _that_ is probably beyond my knowledge!

> So your patch is definitely still the right thing to do, but it feels
> like a hole in the safe-directory mechanism, at least when called via
> Git.pm. +cc folks who worked on that.

If we wanted a test for just the runtime error, something like the following
(including your earlier suggestion to set up the bare repo) demonstrates the
bug and my fix. It doesn't seem like a particularly valuable test on its own,
IMO, but I'm happy to reroll if we want it. Thanks!

diff --git a/t/t9700/test.pl b/t/t9700/test.pl
index e046f7db..72681849 100755
--- a/t/t9700/test.pl
+++ b/t/t9700/test.pl
@@ -30,6 +30,11 @@ sub adjust_dirsep {
 # set up
 our $abs_repo_dir = cwd();
 ok(our $r = Git->repository(Directory => "."), "open repository");
+{
+       local $ENV{GIT_TEST_ASSUME_DIFFERENT_OWNER} = 1;
+       eval { Git->repository(Directory => "$abs_repo_dir/bare.git") };
+       unlike($@, qr/as a HASH ref/i, "no error from perl");
+}

 # config
 is($r->config("test.string"), "value", "config scalar: string");
Johannes Schindelin Nov. 10, 2022, 3:10 p.m. UTC | #5
Hi Peff,

On Mon, 17 Oct 2022, Jeff King wrote:

> [... talking about safe.directory ...]
>
> But curiously this still does not pass after your patch, because we seem
> to actually open the repository! I think this is because the C code
> allows an explicit GIT_DIR to override the safe-directory checks.

Yes, I remember that this was something we discussed at some length during
the embargo: what to do with the explicitly-specified `GIT_DIR` when
verifying the ownership, and my recollection is that we asserted that
setting `GIT_DIR` qualifies for "they know what they're doing" (in
particular when it is done in a script, not interactively).

Ciao,
Dscho
Jeff King Nov. 10, 2022, 9:41 p.m. UTC | #6
On Thu, Nov 10, 2022 at 04:10:22PM +0100, Johannes Schindelin wrote:

> Hi Peff,
> 
> On Mon, 17 Oct 2022, Jeff King wrote:
> 
> > [... talking about safe.directory ...]
> >
> > But curiously this still does not pass after your patch, because we seem
> > to actually open the repository! I think this is because the C code
> > allows an explicit GIT_DIR to override the safe-directory checks.
> 
> Yes, I remember that this was something we discussed at some length during
> the embargo: what to do with the explicitly-specified `GIT_DIR` when
> verifying the ownership, and my recollection is that we asserted that
> setting `GIT_DIR` qualifies for "they know what they're doing" (in
> particular when it is done in a script, not interactively).

Thanks for confirming. I'm not sure if you read the rest of the thread,
but the bug turned out to be in Git.pm, which sets GIT_DIR without
knowing what it's doing. :)

We ended up with 20da61f25f (Git.pm: trust rev-parse to find bare
repositories, 2022-10-22) as the fix.

-Peff
diff mbox series

Patch

diff --git a/perl/Git.pm b/perl/Git.pm
index 080cdc2a..cf15ead6 100644
--- a/perl/Git.pm
+++ b/perl/Git.pm
@@ -217,7 +217,7 @@  sub repository {
 			} catch Git::Error::Command with {
 				# Mimic git-rev-parse --git-dir error message:
 				throw Error::Simple("fatal: Not a git repository: $dir");
-			}
+			};
 
 			$opts{Repository} = Cwd::abs_path($dir);
 		}