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>
This commit is contained in:
committed by
Junio C Hamano
parent
8159f4af7d
commit
d0f810f0bc
148
refs.c
148
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);
|
||||
|
||||
Reference in New Issue
Block a user