summaryrefslogtreecommitdiff
path: root/refs.c
diff options
context:
space:
mode:
authorRonnie Sahlberg <sahlberg@google.com>2014-09-03 18:45:43 (GMT)
committerJunio C Hamano <gitster@pobox.com>2014-10-15 17:47:26 (GMT)
commitd0f810f0bc0d6b51722b400f70c2590713f168e8 (patch)
tree3449100647a72b1a3495274102b8bbcddf211ed1 /refs.c
parent8159f4af7d21e106ed04d69c428b395701b94205 (diff)
downloadgit-d0f810f0bc0d6b51722b400f70c2590713f168e8.zip
git-d0f810f0bc0d6b51722b400f70c2590713f168e8.tar.gz
git-d0f810f0bc0d6b51722b400f70c2590713f168e8.tar.bz2
refs.c: allow listing and deleting badly named refs
We currently do not handle badly named refs well: $ cp .git/refs/heads/master .git/refs/heads/master.....@\*@\\. $ git branch fatal: Reference has invalid format: 'refs/heads/master.....@*@\.' $ git branch -D master.....@\*@\\. error: branch 'master.....@*@\.' not found. Users cannot recover from a badly named ref without manually finding and deleting the loose ref file or appropriate line in packed-refs. Making that easier will make it easier to tweak the ref naming rules in the future, for example to forbid shell metacharacters like '`' and '"', without putting people in a state that is hard to get out of. So allow "branch --list" to show these refs and allow "branch -d/-D" and "update-ref -d" to delete them. Other commands (for example to rename refs) will continue to not handle these refs but can be changed in later patches. Details: In resolving functions, refuse to resolve refs that don't pass the git-check-ref-format(1) check unless the new RESOLVE_REF_ALLOW_BAD_NAME flag is passed. Even with RESOLVE_REF_ALLOW_BAD_NAME, refuse to resolve refs that escape the refs/ directory and do not match the pattern [A-Z_]* (think "HEAD" and "MERGE_HEAD"). In locking functions, refuse to act on badly named refs unless they are being deleted and either are in the refs/ directory or match [A-Z_]*. Just like other invalid refs, flag resolved, badly named refs with the REF_ISBROKEN flag, treat them as resolving to null_sha1, and skip them in all iteration functions except for for_each_rawref. Flag badly named refs (but not symrefs pointing to badly named refs) with a REF_BAD_NAME flag to make it easier for future callers to notice and handle them specially. For example, in a later patch for-each-ref will use this flag to detect refs whose names can confuse callers parsing for-each-ref output. In the transaction API, refuse to create or update badly named refs, but allow deleting them (unless they try to escape refs/ and don't match [A-Z_]*). Signed-off-by: Ronnie Sahlberg <sahlberg@google.com> Signed-off-by: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 'refs.c')
-rw-r--r--refs.c148
1 files changed, 119 insertions, 29 deletions
diff --git a/refs.c b/refs.c
index fe1352a..e7000f2 100644
--- a/refs.c
+++ b/refs.c
@@ -187,8 +187,8 @@ struct ref_dir {
/*
* Bit values for ref_entry::flag. REF_ISSYMREF=0x01,
- * REF_ISPACKED=0x02, and REF_ISBROKEN=0x04 are public values; see
- * refs.h.
+ * REF_ISPACKED=0x02, REF_ISBROKEN=0x04 and REF_BAD_NAME=0x08 are
+ * public values; see refs.h.
*/
/*
@@ -196,16 +196,16 @@ struct ref_dir {
* the correct peeled value for the reference, which might be
* null_sha1 if the reference is not a tag or if it is broken.
*/
-#define REF_KNOWS_PEELED 0x08
+#define REF_KNOWS_PEELED 0x10
/* ref_entry represents a directory of references */
-#define REF_DIR 0x10
+#define REF_DIR 0x20
/*
* Entry has not yet been read from disk (used only for REF_DIR
* entries representing loose references)
*/
-#define REF_INCOMPLETE 0x20
+#define REF_INCOMPLETE 0x40
/*
* A ref_entry represents either a reference or a "subdirectory" of
@@ -274,6 +274,39 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry)
return dir;
}
+/*
+ * Check if a refname is safe.
+ * For refs that start with "refs/" we consider it safe as long they do
+ * not try to resolve to outside of refs/.
+ *
+ * For all other refs we only consider them safe iff they only contain
+ * upper case characters and '_' (like "HEAD" AND "MERGE_HEAD", and not like
+ * "config").
+ */
+static int refname_is_safe(const char *refname)
+{
+ if (starts_with(refname, "refs/")) {
+ char *buf;
+ int result;
+
+ buf = xmalloc(strlen(refname) + 1);
+ /*
+ * Does the refname try to escape refs/?
+ * For example: refs/foo/../bar is safe but refs/foo/../../bar
+ * is not.
+ */
+ result = !normalize_path_copy(buf, refname + strlen("refs/"));
+ free(buf);
+ return result;
+ }
+ while (*refname) {
+ if (!isupper(*refname) && *refname != '_')
+ return 0;
+ refname++;
+ }
+ return 1;
+}
+
static struct ref_entry *create_ref_entry(const char *refname,
const unsigned char *sha1, int flag,
int check_name)
@@ -284,6 +317,8 @@ static struct ref_entry *create_ref_entry(const char *refname,
if (check_name &&
check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
die("Reference has invalid format: '%s'", refname);
+ if (!check_name && !refname_is_safe(refname))
+ die("Reference has invalid name: '%s'", refname);
len = strlen(refname) + 1;
ref = xmalloc(sizeof(struct ref_entry) + len);
hashcpy(ref->u.value.sha1, sha1);
@@ -1111,7 +1146,13 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
refname = parse_ref_line(refline, sha1);
if (refname) {
- last = create_ref_entry(refname, sha1, REF_ISPACKED, 1);
+ int flag = REF_ISPACKED;
+
+ if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+ hashclr(sha1);
+ flag |= REF_BAD_NAME | REF_ISBROKEN;
+ }
+ last = create_ref_entry(refname, sha1, flag, 0);
if (peeled == PEELED_FULLY ||
(peeled == PEELED_TAGS && starts_with(refname, "refs/tags/")))
last->flag |= REF_KNOWS_PEELED;
@@ -1249,8 +1290,13 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)
hashclr(sha1);
flag |= REF_ISBROKEN;
}
+ if (check_refname_format(refname.buf,
+ REFNAME_ALLOW_ONELEVEL)) {
+ hashclr(sha1);
+ flag |= REF_BAD_NAME | REF_ISBROKEN;
+ }
add_entry_to_dir(dir,
- create_ref_entry(refname.buf, sha1, flag, 1));
+ create_ref_entry(refname.buf, sha1, flag, 0));
}
strbuf_setlen(&refname, dirnamelen);
}
@@ -1369,10 +1415,10 @@ static struct ref_entry *get_packed_ref(const char *refname)
* A loose ref file doesn't exist; check for a packed ref. The
* options are forwarded from resolve_safe_unsafe().
*/
-static const char *handle_missing_loose_ref(const char *refname,
- int resolve_flags,
- unsigned char *sha1,
- int *flags)
+static int resolve_missing_loose_ref(const char *refname,
+ int resolve_flags,
+ unsigned char *sha1,
+ int *flags)
{
struct ref_entry *entry;
@@ -1385,14 +1431,15 @@ static const char *handle_missing_loose_ref(const char *refname,
hashcpy(sha1, entry->u.value.sha1);
if (flags)
*flags |= REF_ISPACKED;
- return refname;
+ return 0;
}
/* The reference is not a packed reference, either. */
if (resolve_flags & RESOLVE_REF_READING) {
- return NULL;
+ errno = ENOENT;
+ return -1;
} else {
hashclr(sha1);
- return refname;
+ return 0;
}
}
@@ -1403,13 +1450,29 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned
ssize_t len;
char buffer[256];
static char refname_buffer[256];
+ int bad_name = 0;
if (flags)
*flags = 0;
if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
- errno = EINVAL;
- return NULL;
+ if (flags)
+ *flags |= REF_BAD_NAME;
+
+ if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
+ !refname_is_safe(refname)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ /*
+ * dwim_ref() uses REF_ISBROKEN to distinguish between
+ * missing refs and refs that were present but invalid,
+ * to complain about the latter to stderr.
+ *
+ * We don't know whether the ref exists, so don't set
+ * REF_ISBROKEN yet.
+ */
+ bad_name = 1;
}
for (;;) {
char path[PATH_MAX];
@@ -1435,11 +1498,17 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned
*/
stat_ref:
if (lstat(path, &st) < 0) {
- if (errno == ENOENT)
- return handle_missing_loose_ref(refname,
- resolve_flags, sha1, flags);
- else
+ if (errno != ENOENT)
+ return NULL;
+ if (resolve_missing_loose_ref(refname, resolve_flags,
+ sha1, flags))
return NULL;
+ if (bad_name) {
+ hashclr(sha1);
+ if (flags)
+ *flags |= REF_ISBROKEN;
+ }
+ return refname;
}
/* Follow "normalized" - ie "refs/.." symlinks by hand */
@@ -1512,6 +1581,11 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned
errno = EINVAL;
return NULL;
}
+ if (bad_name) {
+ hashclr(sha1);
+ if (flags)
+ *flags |= REF_ISBROKEN;
+ }
return refname;
}
if (flags)
@@ -1527,8 +1601,13 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned
if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
if (flags)
*flags |= REF_ISBROKEN;
- errno = EINVAL;
- return NULL;
+
+ if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
+ !refname_is_safe(buf)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ bad_name = 1;
}
}
}
@@ -2160,18 +2239,16 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
int missing = 0;
int attempts_remaining = 3;
- if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
- errno = EINVAL;
- return NULL;
- }
-
lock = xcalloc(1, sizeof(struct ref_lock));
lock->lock_fd = -1;
if (mustexist)
resolve_flags |= RESOLVE_REF_READING;
- if (flags & REF_NODEREF && flags & REF_DELETING)
- resolve_flags |= RESOLVE_REF_NO_RECURSE;
+ if (flags & REF_DELETING) {
+ resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;
+ if (flags & REF_NODEREF)
+ resolve_flags |= RESOLVE_REF_NO_RECURSE;
+ }
refname = resolve_ref_unsafe(refname, resolve_flags,
lock->old_sha1, &type);
@@ -3519,6 +3596,13 @@ int ref_transaction_update(struct ref_transaction *transaction,
if (have_old && !old_sha1)
die("BUG: have_old is true but old_sha1 is NULL");
+ if (!is_null_sha1(new_sha1) &&
+ check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+ strbuf_addf(err, "refusing to update ref with bad name %s",
+ refname);
+ return -1;
+ }
+
update = add_update(transaction, refname);
hashcpy(update->new_sha1, new_sha1);
update->flags = flags;
@@ -3544,6 +3628,12 @@ int ref_transaction_create(struct ref_transaction *transaction,
if (!new_sha1 || is_null_sha1(new_sha1))
die("BUG: create ref with null new_sha1");
+ if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+ strbuf_addf(err, "refusing to create ref with bad name %s",
+ refname);
+ return -1;
+ }
+
update = add_update(transaction, refname);
hashcpy(update->new_sha1, new_sha1);