https://github.com/git/git
Revision 0383bbb9015898cbc79abd7b64316484d7713b44 authored by Jeff King on 30 April 2018, 07:25:25 UTC, committed by Jeff King on 22 May 2018, 03:50:11 UTC
Submodule "names" come from the untrusted .gitmodules file,
but we blindly append them to $GIT_DIR/modules to create our
on-disk repo paths. This means you can do bad things by
putting "../" into the name (among other things).

Let's sanity-check these names to avoid building a path that
can be exploited. There are two main decisions:

  1. What should the allowed syntax be?

     It's tempting to reuse verify_path(), since submodule
     names typically come from in-repo paths. But there are
     two reasons not to:

       a. It's technically more strict than what we need, as
          we really care only about breaking out of the
          $GIT_DIR/modules/ hierarchy.  E.g., having a
          submodule named "foo/.git" isn't actually
          dangerous, and it's possible that somebody has
          manually given such a funny name.

       b. Since we'll eventually use this checking logic in
          fsck to prevent downstream repositories, it should
          be consistent across platforms. Because
          verify_path() relies on is_dir_sep(), it wouldn't
          block "foo\..\bar" on a non-Windows machine.

  2. Where should we enforce it? These days most of the
     .gitmodules reads go through submodule-config.c, so
     I've put it there in the reading step. That should
     cover all of the C code.

     We also construct the name for "git submodule add"
     inside the git-submodule.sh script. This is probably
     not a big deal for security since the name is coming
     from the user anyway, but it would be polite to remind
     them if the name they pick is invalid (and we need to
     expose the name-checker to the shell anyway for our
     test scripts).

     This patch issues a warning when reading .gitmodules
     and just ignores the related config entry completely.
     This will generally end up producing a sensible error,
     as it works the same as a .gitmodules file which is
     missing a submodule entry (so "submodule update" will
     barf, but "git clone --recurse-submodules" will print
     an error but not abort the clone.

     There is one minor oddity, which is that we print the
     warning once per malformed config key (since that's how
     the config subsystem gives us the entries). So in the
     new test, for example, the user would see three
     warnings. That's OK, since the intent is that this case
     should never come up outside of malicious repositories
     (and then it might even benefit the user to see the
     message multiple times).

Credit for finding this vulnerability and the proof of
concept from which the test script was adapted goes to
Etienne Stalmans.

Signed-off-by: Jeff King <peff@peff.net>
1 parent 42e6fde
Raw File
Tip revision: 0383bbb9015898cbc79abd7b64316484d7713b44 authored by Jeff King on 30 April 2018, 07:25:25 UTC
submodule-config: verify submodule names as paths
Tip revision: 0383bbb
abspath.c
#include "cache.h"

/*
 * Do not use this for inspecting *tracked* content.  When path is a
 * symlink to a directory, we do not want to say it is a directory when
 * dealing with tracked content in the working tree.
 */
int is_directory(const char *path)
{
	struct stat st;
	return (!stat(path, &st) && S_ISDIR(st.st_mode));
}

/* removes the last path component from 'path' except if 'path' is root */
static void strip_last_component(struct strbuf *path)
{
	size_t offset = offset_1st_component(path->buf);
	size_t len = path->len;

	/* Find start of the last component */
	while (offset < len && !is_dir_sep(path->buf[len - 1]))
		len--;
	/* Skip sequences of multiple path-separators */
	while (offset < len && is_dir_sep(path->buf[len - 1]))
		len--;

	strbuf_setlen(path, len);
}

/* get (and remove) the next component in 'remaining' and place it in 'next' */
static void get_next_component(struct strbuf *next, struct strbuf *remaining)
{
	char *start = NULL;
	char *end = NULL;

	strbuf_reset(next);

	/* look for the next component */
	/* Skip sequences of multiple path-separators */
	for (start = remaining->buf; is_dir_sep(*start); start++)
		; /* nothing */
	/* Find end of the path component */
	for (end = start; *end && !is_dir_sep(*end); end++)
		; /* nothing */

	strbuf_add(next, start, end - start);
	/* remove the component from 'remaining' */
	strbuf_remove(remaining, 0, end - remaining->buf);
}

/* copies root part from remaining to resolved, canonicalizing it on the way */
static void get_root_part(struct strbuf *resolved, struct strbuf *remaining)
{
	int offset = offset_1st_component(remaining->buf);

	strbuf_reset(resolved);
	strbuf_add(resolved, remaining->buf, offset);
#ifdef GIT_WINDOWS_NATIVE
	convert_slashes(resolved->buf);
#endif
	strbuf_remove(remaining, 0, offset);
}

/* We allow "recursive" symbolic links. Only within reason, though. */
#ifndef MAXSYMLINKS
#define MAXSYMLINKS 32
#endif

/*
 * Return the real path (i.e., absolute path, with symlinks resolved
 * and extra slashes removed) equivalent to the specified path.  (If
 * you want an absolute path but don't mind links, use
 * absolute_path().)  Places the resolved realpath in the provided strbuf.
 *
 * The directory part of path (i.e., everything up to the last
 * dir_sep) must denote a valid, existing directory, but the last
 * component need not exist.  If die_on_error is set, then die with an
 * informative error message if there is a problem.  Otherwise, return
 * NULL on errors (without generating any output).
 */
char *strbuf_realpath(struct strbuf *resolved, const char *path,
		      int die_on_error)
{
	struct strbuf remaining = STRBUF_INIT;
	struct strbuf next = STRBUF_INIT;
	struct strbuf symlink = STRBUF_INIT;
	char *retval = NULL;
	int num_symlinks = 0;
	struct stat st;

	if (!*path) {
		if (die_on_error)
			die("The empty string is not a valid path");
		else
			goto error_out;
	}

	strbuf_addstr(&remaining, path);
	get_root_part(resolved, &remaining);

	if (!resolved->len) {
		/* relative path; can use CWD as the initial resolved path */
		if (strbuf_getcwd(resolved)) {
			if (die_on_error)
				die_errno("unable to get current working directory");
			else
				goto error_out;
		}
	}

	/* Iterate over the remaining path components */
	while (remaining.len > 0) {
		get_next_component(&next, &remaining);

		if (next.len == 0) {
			continue; /* empty component */
		} else if (next.len == 1 && !strcmp(next.buf, ".")) {
			continue; /* '.' component */
		} else if (next.len == 2 && !strcmp(next.buf, "..")) {
			/* '..' component; strip the last path component */
			strip_last_component(resolved);
			continue;
		}

		/* append the next component and resolve resultant path */
		if (!is_dir_sep(resolved->buf[resolved->len - 1]))
			strbuf_addch(resolved, '/');
		strbuf_addbuf(resolved, &next);

		if (lstat(resolved->buf, &st)) {
			/* error out unless this was the last component */
			if (errno != ENOENT || remaining.len) {
				if (die_on_error)
					die_errno("Invalid path '%s'",
						  resolved->buf);
				else
					goto error_out;
			}
		} else if (S_ISLNK(st.st_mode)) {
			ssize_t len;
			strbuf_reset(&symlink);

			if (num_symlinks++ > MAXSYMLINKS) {
				errno = ELOOP;

				if (die_on_error)
					die("More than %d nested symlinks "
					    "on path '%s'", MAXSYMLINKS, path);
				else
					goto error_out;
			}

			len = strbuf_readlink(&symlink, resolved->buf,
					      st.st_size);
			if (len < 0) {
				if (die_on_error)
					die_errno("Invalid symlink '%s'",
						  resolved->buf);
				else
					goto error_out;
			}

			if (is_absolute_path(symlink.buf)) {
				/* absolute symlink; set resolved to root */
				get_root_part(resolved, &symlink);
			} else {
				/*
				 * relative symlink
				 * strip off the last component since it will
				 * be replaced with the contents of the symlink
				 */
				strip_last_component(resolved);
			}

			/*
			 * if there are still remaining components to resolve
			 * then append them to symlink
			 */
			if (remaining.len) {
				strbuf_addch(&symlink, '/');
				strbuf_addbuf(&symlink, &remaining);
			}

			/*
			 * use the symlink as the remaining components that
			 * need to be resloved
			 */
			strbuf_swap(&symlink, &remaining);
		}
	}

	retval = resolved->buf;

error_out:
	strbuf_release(&remaining);
	strbuf_release(&next);
	strbuf_release(&symlink);

	if (!retval)
		strbuf_reset(resolved);

	return retval;
}

const char *real_path(const char *path)
{
	static struct strbuf realpath = STRBUF_INIT;
	return strbuf_realpath(&realpath, path, 1);
}

const char *real_path_if_valid(const char *path)
{
	static struct strbuf realpath = STRBUF_INIT;
	return strbuf_realpath(&realpath, path, 0);
}

char *real_pathdup(const char *path, int die_on_error)
{
	struct strbuf realpath = STRBUF_INIT;
	char *retval = NULL;

	if (strbuf_realpath(&realpath, path, die_on_error))
		retval = strbuf_detach(&realpath, NULL);

	strbuf_release(&realpath);

	return retval;
}

/*
 * Use this to get an absolute path from a relative one. If you want
 * to resolve links, you should use real_path.
 */
const char *absolute_path(const char *path)
{
	static struct strbuf sb = STRBUF_INIT;
	strbuf_reset(&sb);
	strbuf_add_absolute_path(&sb, path);
	return sb.buf;
}

char *absolute_pathdup(const char *path)
{
	struct strbuf sb = STRBUF_INIT;
	strbuf_add_absolute_path(&sb, path);
	return strbuf_detach(&sb, NULL);
}

char *prefix_filename(const char *pfx, const char *arg)
{
	struct strbuf path = STRBUF_INIT;
	size_t pfx_len = pfx ? strlen(pfx) : 0;

	if (!pfx_len)
		; /* nothing to prefix */
	else if (is_absolute_path(arg))
		pfx_len = 0;
	else
		strbuf_add(&path, pfx, pfx_len);

	strbuf_addstr(&path, arg);
#ifdef GIT_WINDOWS_NATIVE
	convert_slashes(path.buf + pfx_len);
#endif
	return strbuf_detach(&path, NULL);
}
back to top