9front - general discussion about 9front
 help / color / mirror / Atom feed
* [9front] [patch] git/pull: Be more careful about remote refs.
@ 2025-02-15  2:28 James Cook
  2025-02-15  2:44 ` ori
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: James Cook @ 2025-02-15  2:28 UTC (permalink / raw)
  To: 9front

(This patch needs testing, and maybe more thought: maybe git/get.c
should be changed instead of git/pull. I haven't taken the time to
understand exactly what git/get does. But at minimum I hope this
is a useful bug report.)

I found 1-2 bugs in git/pull:

1. If the remote $origin has refs under refs/remotes/$origin,
these should be ignored, but instead, git/pull saves them
to .git/refs/remotes/$origin, possibly overwriting what it
found under refs/heads in the remote.

As a concrete example, the main branch for https://github.com/Perl/perl5.git
is called blead. If I run git/pull in a local checkout, I end up
with an outdated blead because for some reason the github repo has
its own refs/remotes/origin/blead, and git/get prints it after
refs/heads/blead.

2. If the remote path has special characters like '}', the remote
server could probably trick git/pull into executing arbitrary rc
commands. I haven't actually verified this is possible, but git/pull
is pasting the output of git/get unescaped into an rc command, and
I couldn't see any safety checking for that value in git/get.c.

The below patch fixes 1 by restricting to refs/(heads|tags)/ instead
of examining every remote ref, and fixes 2 by using an environment
variable instead of dynamically constructing an rc command. (I tried
writing to awk's special ENVIRON variable, but it didn't seem to
do anything, so I used /env.)

-- 
James



--- a/sys/src/cmd/git/pull
+++ b/sys/src/cmd/git/pull
@@ -10,7 +10,7 @@
 	if(! ~ $#debug 0)
 		dflag='-d'
 	{git/get $dflag -u $upstream $url >[2=3] || die $status} | awk '
-	/^remote/{
+	/^remote refs\/(heads|tags)\//{
 		if($2=="HEAD")
 			next
 		ref=$2
@@ -17,7 +17,9 @@
 		hash=$3
 		gsub("^refs/heads", "refs/remotes/'$upstream'", ref)
 		outfile = ".git/"ref
-		system("mkdir -p `{basename -d "outfile"}");
+		print outfile > "/env/outfile"
+		close("/env/outfile")
+		system("mkdir -p `{basename -d $outfile}");
 		print hash > outfile;
 		close(outfile);
 	}

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [9front] [patch] git/pull: Be more careful about remote refs.
  2025-02-15  2:28 [9front] [patch] git/pull: Be more careful about remote refs James Cook
@ 2025-02-15  2:44 ` ori
  2025-02-15  2:51 ` ori
  2025-02-15  2:56 ` ori
  2 siblings, 0 replies; 5+ messages in thread
From: ori @ 2025-02-15  2:44 UTC (permalink / raw)
  To: 9front

I think this makes sense; there's one small change I'd make:

	system("mkdir -p `{basename -d $outfile}");

should probably not use `{} directly, but quote it:

	system("mkdir -p `''''{basename -d $outfile}")

untested, of course: if '$outfile' is terminated with a
newline, that'd need to be stripped.

I think it may also make sense to sanitize the paths in
git/get and reject printing anything with frogs.

The split of work is that git/get is responsible for
sycing the git objects, and saying what branches were
touched, and git/pull is responsible for interpreting
that and updating the branches.

So, I think you're right in this change, mostly.

Thanks!

Quoth James Cook <falsifian@falsifian.org>:
> (This patch needs testing, and maybe more thought: maybe git/get.c
> should be changed instead of git/pull. I haven't taken the time to
> understand exactly what git/get does. But at minimum I hope this
> is a useful bug report.)
> 
> I found 1-2 bugs in git/pull:
> 
> 1. If the remote $origin has refs under refs/remotes/$origin,
> these should be ignored, but instead, git/pull saves them
> to .git/refs/remotes/$origin, possibly overwriting what it
> found under refs/heads in the remote.
> 
> As a concrete example, the main branch for https://github.com/Perl/perl5.git
> is called blead. If I run git/pull in a local checkout, I end up
> with an outdated blead because for some reason the github repo has
> its own refs/remotes/origin/blead, and git/get prints it after
> refs/heads/blead.
> 
> 2. If the remote path has special characters like '}', the remote
> server could probably trick git/pull into executing arbitrary rc
> commands. I haven't actually verified this is possible, but git/pull
> is pasting the output of git/get unescaped into an rc command, and
> I couldn't see any safety checking for that value in git/get.c.
> 
> The below patch fixes 1 by restricting to refs/(heads|tags)/ instead
> of examining every remote ref, and fixes 2 by using an environment
> variable instead of dynamically constructing an rc command. (I tried
> writing to awk's special ENVIRON variable, but it didn't seem to
> do anything, so I used /env.)
> 
> -- 
> James
> 
> 
> 
> --- a/sys/src/cmd/git/pull
> +++ b/sys/src/cmd/git/pull
> @@ -10,7 +10,7 @@
>  	if(! ~ $#debug 0)
>  		dflag='-d'
>  	{git/get $dflag -u $upstream $url >[2=3] || die $status} | awk '
> -	/^remote/{
> +	/^remote refs\/(heads|tags)\//{
>  		if($2=="HEAD")
>  			next
>  		ref=$2
> @@ -17,7 +17,9 @@
>  		hash=$3
>  		gsub("^refs/heads", "refs/remotes/'$upstream'", ref)
>  		outfile = ".git/"ref
> -		system("mkdir -p `{basename -d "outfile"}");
> +		print outfile > "/env/outfile"
> +		close("/env/outfile")
> +		system("mkdir -p `{basename -d $outfile}");
>  		print hash > outfile;
>  		close(outfile);
>  	}


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [9front] [patch] git/pull: Be more careful about remote refs.
  2025-02-15  2:28 [9front] [patch] git/pull: Be more careful about remote refs James Cook
  2025-02-15  2:44 ` ori
@ 2025-02-15  2:51 ` ori
  2025-02-15  2:56 ` ori
  2 siblings, 0 replies; 5+ messages in thread
From: ori @ 2025-02-15  2:51 UTC (permalink / raw)
  To: 9front

Quoth James Cook <falsifian@falsifian.org>:

> --- a/sys/src/cmd/git/pull
> +++ b/sys/src/cmd/git/pull
> @@ -10,7 +10,7 @@
>  	if(! ~ $#debug 0)
>  		dflag='-d'
>  	{git/get $dflag -u $upstream $url >[2=3] || die $status} | awk '
> -	/^remote/{
> +	/^remote refs\/(heads|tags)\//{
>  		if($2=="HEAD")
>  			next

also, this is dead code, if the pattern above is chagned

>  		ref=$2
> @@ -17,7 +17,9 @@
>  		hash=$3
>  		gsub("^refs/heads", "refs/remotes/'$upstream'", ref)
>  		outfile = ".git/"ref
> -		system("mkdir -p `{basename -d "outfile"}");
> +		print outfile > "/env/outfile"
> +		close("/env/outfile")
> +		system("mkdir -p `{basename -d $outfile}");
>  		print hash > outfile;
>  		close(outfile);
>  	}


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [9front] [patch] git/pull: Be more careful about remote refs.
  2025-02-15  2:28 [9front] [patch] git/pull: Be more careful about remote refs James Cook
  2025-02-15  2:44 ` ori
  2025-02-15  2:51 ` ori
@ 2025-02-15  2:56 ` ori
  2025-02-15 18:07   ` James Cook
  2 siblings, 1 reply; 5+ messages in thread
From: ori @ 2025-02-15  2:56 UTC (permalink / raw)
  To: 9front

Quoth James Cook <falsifian@falsifian.org>:
> (This patch needs testing, and maybe more thought: maybe git/get.c
> should be changed instead of git/pull. I haven't taken the time to
> understand exactly what git/get does. But at minimum I hope this
> is a useful bug report.)
> 
> I found 1-2 bugs in git/pull:
> 
> 1. If the remote $origin has refs under refs/remotes/$origin,
> these should be ignored, but instead, git/pull saves them
> to .git/refs/remotes/$origin, possibly overwriting what it
> found under refs/heads in the remote.
> 
> As a concrete example, the main branch for https://github.com/Perl/perl5.git
> is called blead. If I run git/pull in a local checkout, I end up
> with an outdated blead because for some reason the github repo has
> its own refs/remotes/origin/blead, and git/get prints it after
> refs/heads/blead.
> 
> 2. If the remote path has special characters like '}', the remote
> server could probably trick git/pull into executing arbitrary rc
> commands. I haven't actually verified this is possible, but git/pull
> is pasting the output of git/get unescaped into an rc command, and
> I couldn't see any safety checking for that value in git/get.c.
> 
> The below patch fixes 1 by restricting to refs/(heads|tags)/ instead
> of examining every remote ref, and fixes 2 by using an environment
> variable instead of dynamically constructing an rc command. (I tried
> writing to awk's special ENVIRON variable, but it didn't seem to
> do anything, so I used /env.)
> 
> -- 
> James
> 
> 
> 
> --- a/sys/src/cmd/git/pull
> +++ b/sys/src/cmd/git/pull
> @@ -10,7 +10,7 @@
>  	if(! ~ $#debug 0)
>  		dflag='-d'
>  	{git/get $dflag -u $upstream $url >[2=3] || die $status} | awk '
> -	/^remote/{
> +	/^remote refs\/(heads|tags)\//{
>  		if($2=="HEAD")
>  			next
>  		ref=$2
> @@ -17,7 +17,9 @@
>  		hash=$3
>  		gsub("^refs/heads", "refs/remotes/'$upstream'", ref)
>  		outfile = ".git/"ref
> -		system("mkdir -p `{basename -d "outfile"}");
> +		print outfile > "/env/outfile"
> +		close("/env/outfile")
> +		system("mkdir -p `{basename -d $outfile}");
>  		print hash > outfile;
>  		close(outfile);
>  	}

How's this?

diff 490ebc54bb9b46a5719f059da6aef5b8b381eecf uncommitted
--- a/sys/src/cmd/git/pull
+++ b/sys/src/cmd/git/pull
@@ -10,14 +10,14 @@
 	if(! ~ $#debug 0)
 		dflag='-d'
 	{git/get $dflag -u $upstream $url >[2=3] || die $status} | awk '
-	/^remote/{
-		if($2=="HEAD")
-			next
+	/^remote refs\/(heads|tags)\//{
 		ref=$2
 		hash=$3
 		gsub("^refs/heads", "refs/remotes/'$upstream'", ref)
 		outfile = ".git/"ref
-		system("mkdir -p `{basename -d "outfile"}");
+		print outfile > "/env/outfile"
+		system("mkdir -p `$nl{basename -d $outfile}");\
+		close("/env/outfile")
 		print hash > outfile;
 		close(outfile);
 	}


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [9front] [patch] git/pull: Be more careful about remote refs.
  2025-02-15  2:56 ` ori
@ 2025-02-15 18:07   ` James Cook
  0 siblings, 0 replies; 5+ messages in thread
From: James Cook @ 2025-02-15 18:07 UTC (permalink / raw)
  To: 9front

ori@eigenstate.org wrote:
> How's this?
> 
> diff 490ebc54bb9b46a5719f059da6aef5b8b381eecf uncommitted
> --- a/sys/src/cmd/git/pull
> +++ b/sys/src/cmd/git/pull
> @@ -10,14 +10,14 @@
>  	if(! ~ $#debug 0)
>  		dflag='-d'
>  	{git/get $dflag -u $upstream $url >[2=3] || die $status} | awk '
> -	/^remote/{
> -		if($2=="HEAD")
> -			next
> +	/^remote refs\/(heads|tags)\//{
>  		ref=$2
>  		hash=$3
>  		gsub("^refs/heads", "refs/remotes/'$upstream'", ref)
>  		outfile = ".git/"ref
> -		system("mkdir -p `{basename -d "outfile"}");
> +		print outfile > "/env/outfile"
> +		system("mkdir -p `$nl{basename -d $outfile}");\
> +		close("/env/outfile")
>  		print hash > outfile;
>  		close(outfile);
>  	}

That looks good and is working for me so far.

Below is an almost-complete patch to add some validation to git/get
as discussed on IRC.

Do not commit as is because I couldn't figure out a proper way to
have the file cmd/git/test/okref.c depend on object files in cmd/git.
To run the tests with my hacky patch, first mk the .$O files in
cmd/git, then cd to cmd/git/test and mk okref.test.

Also, I've barely tested it; just tried pulling one of my private
git repos.

Also also I haven't looked carefully at the 9front coding style.
E.g. I don't see use of "const" in other code so maybe that should
go.

I implemented okref then discovered (thanks ori) that the actual
rules were different, and then implemented those as okrefname. Kept
okref as a separate function because I already had those tests, and
maybe it'll come in handy later.

-- 
James


--- a/sys/src/cmd/git/get.c
+++ b/sys/src/cmd/git/get.c
@@ -247,6 +247,8 @@
 		getfields(buf, sp, nelem(sp), 1, " \t\n\r");
 		if(strstr(sp[1], "^{}"))
 			continue;
+		if (!okrefname(sp[1]))
+			sysfatal("remote side sent invalid ref: %s", sp[1]);
 		if(fetchbranch && !branchmatch(sp[1], fetchbranch))
 			continue;
 		if(refsz == nref + 1){
--- a/sys/src/cmd/git/git.h
+++ b/sys/src/cmd/git/git.h
@@ -312,6 +312,8 @@
 void	*earealloc(void *, ulong, ulong);
 void	*erealloc(void *, ulong);
 char	*estrdup(char *);
+int okref(char *);
+int okrefname(char *);
 int	slurpdir(char *, Dir **);
 int	hparse(Hash *, char *);
 int	hassuffix(char *, char *);
--- a/sys/src/cmd/git/test/mkfile
+++ b/sys/src/cmd/git/test/mkfile
@@ -9,7 +9,17 @@
 	lca\
 	merge\
 	noam\
+	okref\
 	range
+
+OFILES=\
+	../delta.$O\
+	../objset.$O\
+	../ols.$O\
+	../pack.$O\
+	../proto.$O\
+	../util.$O\
+	../ref.$O
 
 </sys/src/cmd/mktest
 
--- a/sys/src/cmd/git/util.c
+++ b/sys/src/cmd/git/util.c
@@ -164,6 +164,57 @@
 	return s;
 }
 
+/* Checks the rules at https://git-scm.com/docs/git-check-ref-format */
+int
+okref(char *s)
+{
+	char prev = 0;
+	int haveslash = 0;
+	char *p;
+
+	if (*s == 0) return 0;
+	if ((p = strstr(s, ".lock")) != nil) {
+		if (*(p+5) == 0) return 0;  /* ends in .lock */
+	}
+
+	for (; *s != 0; prev = *s, ++s) {
+		switch (*s) {
+		case '.':
+			if (prev == 0 || prev == '/' || prev == '.') return 0;
+			break;
+		case '/':
+			if (prev == 0 || prev == '/') return 0;
+			haveslash = 1;
+			break;
+		case '{':
+			if (prev == '@') return 0;
+			break;
+		case ' ':
+		case '~':
+		case '^':
+		case ':':
+		case '?':
+		case '*':
+		case '[':
+		case '\\':
+			return 0;
+		default:
+			if (*s <= 0x20) return 0;
+		}
+	}
+	return haveslash && *(s-1) != '/' && *(s-1) != '.';
+}
+
+/* Checks the rules for a refname at
+https://git-scm.com/docs/protocol-common/2.9.5 */
+int
+okrefname(char *s)
+{
+	if (strcmp(s, "HEAD") == 0) return 1;
+	if (strstr(s, "refs/") != s) return 0;
+	return okref(s+5);
+}
+
 int
 Hfmt(Fmt *fmt)
 {

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2025-02-15 18:10 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-02-15  2:28 [9front] [patch] git/pull: Be more careful about remote refs James Cook
2025-02-15  2:44 ` ori
2025-02-15  2:51 ` ori
2025-02-15  2:56 ` ori
2025-02-15 18:07   ` James Cook

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).