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
remote.h
#ifndef REMOTE_H
#define REMOTE_H

#include "parse-options.h"
#include "hashmap.h"

enum {
	REMOTE_UNCONFIGURED = 0,
	REMOTE_CONFIG,
	REMOTE_REMOTES,
	REMOTE_BRANCHES
};

struct remote {
	struct hashmap_entry ent;  /* must be first */

	const char *name;
	int origin, configured_in_repo;

	const char *foreign_vcs;

	const char **url;
	int url_nr;
	int url_alloc;

	const char **pushurl;
	int pushurl_nr;
	int pushurl_alloc;

	const char **push_refspec;
	struct refspec *push;
	int push_refspec_nr;
	int push_refspec_alloc;

	const char **fetch_refspec;
	struct refspec *fetch;
	int fetch_refspec_nr;
	int fetch_refspec_alloc;

	/*
	 * -1 to never fetch tags
	 * 0 to auto-follow tags on heuristic (default)
	 * 1 to always auto-follow tags
	 * 2 to always fetch tags
	 */
	int fetch_tags;
	int skip_default_update;
	int mirror;
	int prune;

	const char *receivepack;
	const char *uploadpack;

	/*
	 * for curl remotes only
	 */
	char *http_proxy;
	char *http_proxy_authmethod;
};

struct remote *remote_get(const char *name);
struct remote *pushremote_get(const char *name);
int remote_is_configured(struct remote *remote, int in_repo);

typedef int each_remote_fn(struct remote *remote, void *priv);
int for_each_remote(each_remote_fn fn, void *priv);

int remote_has_url(struct remote *remote, const char *url);

struct refspec {
	unsigned force : 1;
	unsigned pattern : 1;
	unsigned matching : 1;
	unsigned exact_sha1 : 1;

	char *src;
	char *dst;
};

extern const struct refspec *tag_refspec;

struct ref {
	struct ref *next;
	struct object_id old_oid;
	struct object_id new_oid;
	struct object_id old_oid_expect; /* used by expect-old */
	char *symref;
	unsigned int
		force:1,
		forced_update:1,
		expect_old_sha1:1,
		deletion:1;

	enum {
		REF_NOT_MATCHED = 0, /* initial value */
		REF_MATCHED,
		REF_UNADVERTISED_NOT_ALLOWED
	} match_status;

	/*
	 * Order is important here, as we write to FETCH_HEAD
	 * in numeric order. And the default NOT_FOR_MERGE
	 * should be 0, so that xcalloc'd structures get it
	 * by default.
	 */
	enum {
		FETCH_HEAD_MERGE = -1,
		FETCH_HEAD_NOT_FOR_MERGE = 0,
		FETCH_HEAD_IGNORE = 1
	} fetch_head_status;

	enum {
		REF_STATUS_NONE = 0,
		REF_STATUS_OK,
		REF_STATUS_REJECT_NONFASTFORWARD,
		REF_STATUS_REJECT_ALREADY_EXISTS,
		REF_STATUS_REJECT_NODELETE,
		REF_STATUS_REJECT_FETCH_FIRST,
		REF_STATUS_REJECT_NEEDS_FORCE,
		REF_STATUS_REJECT_STALE,
		REF_STATUS_REJECT_SHALLOW,
		REF_STATUS_UPTODATE,
		REF_STATUS_REMOTE_REJECT,
		REF_STATUS_EXPECTING_REPORT,
		REF_STATUS_ATOMIC_PUSH_FAILED
	} status;
	char *remote_status;
	struct ref *peer_ref; /* when renaming */
	char name[FLEX_ARRAY]; /* more */
};

#define REF_NORMAL	(1u << 0)
#define REF_HEADS	(1u << 1)
#define REF_TAGS	(1u << 2)

extern struct ref *find_ref_by_name(const struct ref *list, const char *name);

struct ref *alloc_ref(const char *name);
struct ref *copy_ref(const struct ref *ref);
struct ref *copy_ref_list(const struct ref *ref);
void sort_ref_list(struct ref **, int (*cmp)(const void *, const void *));
extern int count_refspec_match(const char *, struct ref *refs, struct ref **matched_ref);
int ref_compare_name(const void *, const void *);

int check_ref_type(const struct ref *ref, int flags);

/*
 * Frees the entire list and peers of elements.
 */
void free_refs(struct ref *ref);

struct oid_array;
extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
				     struct ref **list, unsigned int flags,
				     struct oid_array *extra_have,
				     struct oid_array *shallow);

int resolve_remote_symref(struct ref *ref, struct ref *list);
int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid);

/*
 * Remove and free all but the first of any entries in the input list
 * that map the same remote reference to the same local reference.  If
 * there are two entries that map different remote references to the
 * same local reference, emit an error message and die.  Return a
 * pointer to the head of the resulting list.
 */
struct ref *ref_remove_duplicates(struct ref *ref_map);

int valid_fetch_refspec(const char *refspec);
struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec);
extern struct refspec *parse_push_refspec(int nr_refspec, const char **refspec);

void free_refspec(int nr_refspec, struct refspec *refspec);

extern int query_refspecs(struct refspec *specs, int nr, struct refspec *query);
char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
		     const char *name);

int check_push_refs(struct ref *src, int nr_refspec, const char **refspec);
int match_push_refs(struct ref *src, struct ref **dst,
		    int nr_refspec, const char **refspec, int all);
void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
	int force_update);

/*
 * Given a list of the remote refs and the specification of things to
 * fetch, makes a (separate) list of the refs to fetch and the local
 * refs to store into.
 *
 * *tail is the pointer to the tail pointer of the list of results
 * beforehand, and will be set to the tail pointer of the list of
 * results afterward.
 *
 * missing_ok is usually false, but when we are adding branch.$name.merge
 * it is Ok if the branch is not at the remote anymore.
 */
int get_fetch_map(const struct ref *remote_refs, const struct refspec *refspec,
		  struct ref ***tail, int missing_ok);

struct ref *get_remote_ref(const struct ref *remote_refs, const char *name);

/*
 * For the given remote, reads the refspec's src and sets the other fields.
 */
int remote_find_tracking(struct remote *remote, struct refspec *refspec);

struct branch {
	const char *name;
	const char *refname;

	const char *remote_name;
	const char *pushremote_name;

	const char **merge_name;
	struct refspec **merge;
	int merge_nr;
	int merge_alloc;

	const char *push_tracking_ref;
};

struct branch *branch_get(const char *name);
const char *remote_for_branch(struct branch *branch, int *explicit);
const char *pushremote_for_branch(struct branch *branch, int *explicit);

int branch_has_merge_config(struct branch *branch);
int branch_merge_matches(struct branch *, int n, const char *);

/**
 * Return the fully-qualified refname of the tracking branch for `branch`.
 * I.e., what "branch@{upstream}" would give you. Returns NULL if no
 * upstream is defined.
 *
 * If `err` is not NULL and no upstream is defined, a more specific error
 * message is recorded there (if the function does not return NULL, then
 * `err` is not touched).
 */
const char *branch_get_upstream(struct branch *branch, struct strbuf *err);

/**
 * Return the tracking branch that corresponds to the ref we would push to
 * given a bare `git push` while `branch` is checked out.
 *
 * The return value and `err` conventions match those of `branch_get_upstream`.
 */
const char *branch_get_push(struct branch *branch, struct strbuf *err);

/* Flags to match_refs. */
enum match_refs_flags {
	MATCH_REFS_NONE		= 0,
	MATCH_REFS_ALL 		= (1 << 0),
	MATCH_REFS_MIRROR	= (1 << 1),
	MATCH_REFS_PRUNE	= (1 << 2),
	MATCH_REFS_FOLLOW_TAGS	= (1 << 3)
};

/* Reporting of tracking info */
int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
		       const char **upstream_name);
int format_tracking_info(struct branch *branch, struct strbuf *sb);

struct ref *get_local_heads(void);
/*
 * Find refs from a list which are likely to be pointed to by the given HEAD
 * ref. If 'all' is false, returns the most likely ref; otherwise, returns a
 * list of all candidate refs. If no match is found (or 'head' is NULL),
 * returns NULL. All returns are newly allocated and should be freed.
 */
struct ref *guess_remote_head(const struct ref *head,
			      const struct ref *refs,
			      int all);

/* Return refs which no longer exist on remote */
struct ref *get_stale_heads(struct refspec *refs, int ref_count, struct ref *fetch_map);

/*
 * Compare-and-swap
 */
#define CAS_OPT_NAME "force-with-lease"

struct push_cas_option {
	unsigned use_tracking_for_rest:1;
	struct push_cas {
		unsigned char expect[20];
		unsigned use_tracking:1;
		char *refname;
	} *entry;
	int nr;
	int alloc;
};

extern int parseopt_push_cas_option(const struct option *, const char *arg, int unset);

extern int is_empty_cas(const struct push_cas_option *);
void apply_push_cas(struct push_cas_option *, struct remote *, struct ref *);

#endif
back to top