Thread (2 messages) flat view 2 messages, 2 authors, 2016-06-15

Re: [PATCH] resolve_gitlink_ref_recursive(): verify format of symbolic refs

From: Michael Haggerty <hidden>
Date: 2016-06-15 23:01:46

On 06/27/2014 07:59 PM, Junio C Hamano wrote:
Junio C Hamano [off-list ref] writes:
quoted
Michael Haggerty [off-list ref] writes:
quoted
When reading a symbolic ref via resolve_gitlink_ref_recursive(), check
that the reference name that is pointed at is formatted correctly,
using the same check as resolve_ref_unsafe() uses for non-gitlink
references.  This prevents bogosity like

    ref: ../../other/file

from causing problems.
I do agree that a textual symref "ref: ../../x/y" that is stored in
".git/HEAD" or in ".git/refs/L" will step outside ".git/" and it is
problematic.  But if ".git/refs/heads/a/b/LINK" has "ref: ../../x"
in it, shouldn't we interpret it as referring to the ref at
"refs/heads/x"?
I've never seen that usage, nor seen it advocated.  Symrefs are not
propagated via the Git protocol, so even if somebody were doing this
privately, it could hardly be a project-wide practice.  I can't think of
a practical use for this feature.  And it would be mildly annoying to
implement.  So my inclination is to forbid it.
Actually, the textual symrefs have been invented to replace symbolic
links used for .git/HEAD on symlink-incapable filesystems, and we do
even not let the filesystem follow symlinks.  The rule we have there
are like so:

		/* Follow "normalized" - ie "refs/.." symlinks by hand */
		if (S_ISLNK(st.st_mode)) {
			len = readlink(path, buffer, sizeof(buffer)-1);
			if (len < 0) {
				if (errno == ENOENT || errno == EINVAL)
					/* inconsistent with lstat; retry */
					goto stat_ref;
				else
					return NULL;
			}
			buffer[len] = 0;
			if (starts_with(buffer, "refs/") &&
					!check_refname_format(buffer, 0)) {
				strcpy(refname_buffer, buffer);
				refname = refname_buffer;
				if (flag)
					*flag |= REF_ISSYMREF;
				continue;
			}
		}

So we should do exactly the same check, I would think, no?
I think you overlooked that if the (starts_with() &&
!check_refname_format()) check fails, execution falls through, ending up
here:

		/*
		 * Anything else, just open it and try to use it as
		 * a ref
		 */
		fd = open(path, O_RDONLY);
		if (fd < 0) {
			if (errno == ENOENT)
				/* inconsistent with lstat; retry */
				goto stat_ref;
			else
				return NULL;
		}
		len = read_in_full(fd, buffer, sizeof(buffer)-1);
		close(fd);
		[...etc...]

This has been the behavior since time immemorial [1].

In fact, another bug (which I probably introduced) is that in the case
of a symlink that points at a non-existent file, this code goes into an
infinite loop due to the "if (errno == ENOENT) goto stat_ref" in the
code that I quoted.  My mistake was forgetting that lstat() is statting
the link whereas open() follows the link, so the success of the former
does not imply that the latter should not ENOENT.

I suggest we fix both problems by making the code behave the way you
*thought* it behaves: symlinks are never followed via the filesystem,
but if the symlink contents have the form of a legitimate refname that
starts with "refs/", then we follow it the same way as we would follow a
textual-style symref.
In a typical clone, the ".git/refs/remotes/origin/HEAD" textual
symref stores "ref: refs/remotes/origin/master" and it is neither
"ref: master" nor "ref: ./master", so it should be sensible to
insist on "must start with 'refs/' and its format valid."
Yes, we don't even have a notation for "relative refnames" because we
would have no way to distinguish them from "absolute refnames" except
maybe via some artifice like a "./" prefix.

Michael

[1] Where by "time immemorial" I mean "since before I ever touched refs.c".

-- 
Michael Haggerty
mhagger@alum.mit.edu
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help