Skip to content

Commit 03feddd

Browse files
author
Junio C Hamano
committed
git-check-ref-format: reject funny ref names.
Update check_ref_format() function to reject ref names that: * has a path component that begins with a ".", or * has a double dots "..", or * has ASCII control character, "~", "^", ":" or SP, anywhere, or * ends with a "/". Use it in 'git-checkout -b', 'git-branch', and 'git-tag' to make sure that newly created refs are well-formed. Signed-off-by: Junio C Hamano <junkio@cox.net>
1 parent f51248e commit 03feddd

File tree

6 files changed

+103
-38
lines changed

6 files changed

+103
-38
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ PROGRAMS = \
121121
git-ssh-upload$X git-tar-tree$X git-unpack-file$X \
122122
git-unpack-objects$X git-update-index$X git-update-server-info$X \
123123
git-upload-pack$X git-verify-pack$X git-write-tree$X \
124-
git-update-ref$X git-symbolic-ref$X \
124+
git-update-ref$X git-symbolic-ref$X git-check-ref-format$X \
125125
$(SIMPLE_PROGRAMS)
126126

127127
# Backward compatibility -- to be removed after 1.0

check-ref-format.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* GIT - The information manager from hell
3+
*/
4+
5+
#include "cache.h"
6+
#include "refs.h"
7+
8+
#include <stdio.h>
9+
10+
int main(int ac, char **av)
11+
{
12+
if (ac != 2)
13+
usage("git-check-ref-format refname");
14+
if (check_ref_format(av[1]))
15+
exit(1);
16+
return 0;
17+
}

git-branch.sh

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,46 +13,50 @@ If two arguments, create a new branch <branchname> based off of <start-point>.
1313
}
1414

1515
delete_branch () {
16-
option="$1" branch_name="$2"
16+
option="$1"
17+
shift
1718
headref=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD |
1819
sed -e 's|^refs/heads/||')
19-
case ",$headref," in
20-
",$branch_name,")
21-
die "Cannot delete the branch you are on." ;;
22-
,,)
23-
die "What branch are you on anyway?" ;;
24-
esac
25-
branch=$(cat "$GIT_DIR/refs/heads/$branch_name") &&
26-
branch=$(git-rev-parse --verify "$branch^0") ||
27-
die "Seriously, what branch are you talking about?"
28-
case "$option" in
29-
-D)
30-
;;
31-
*)
32-
mbs=$(git-merge-base -a "$branch" HEAD | tr '\012' ' ')
33-
case " $mbs " in
34-
*' '$branch' '*)
35-
# the merge base of branch and HEAD contains branch --
36-
# which means that the HEAD contains everything in the HEAD.
20+
for branch_name
21+
do
22+
case ",$headref," in
23+
",$branch_name,")
24+
die "Cannot delete the branch you are on." ;;
25+
,,)
26+
die "What branch are you on anyway?" ;;
27+
esac
28+
branch=$(cat "$GIT_DIR/refs/heads/$branch_name") &&
29+
branch=$(git-rev-parse --verify "$branch^0") ||
30+
die "Seriously, what branch are you talking about?"
31+
case "$option" in
32+
-D)
3733
;;
3834
*)
39-
echo >&2 "The branch '$branch_name' is not a strict subset of your current HEAD.
40-
If you are sure you want to delete it, run 'git branch -D $branch_name'."
41-
exit 1
35+
mbs=$(git-merge-base -a "$branch" HEAD | tr '\012' ' ')
36+
case " $mbs " in
37+
*' '$branch' '*)
38+
# the merge base of branch and HEAD contains branch --
39+
# which means that the HEAD contains everything in the HEAD.
40+
;;
41+
*)
42+
echo >&2 "The branch '$branch_name' is not a strict subset of your current HEAD.
43+
If you are sure you want to delete it, run 'git branch -D $branch_name'."
44+
exit 1
45+
;;
46+
esac
4247
;;
4348
esac
44-
;;
45-
esac
46-
rm -f "$GIT_DIR/refs/heads/$branch_name"
47-
echo "Deleted branch $branch_name."
49+
rm -f "$GIT_DIR/refs/heads/$branch_name"
50+
echo "Deleted branch $branch_name."
51+
done
4852
exit 0
4953
}
5054

5155
while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac
5256
do
5357
case "$1" in
5458
-d | -D)
55-
delete_branch "$1" "$2"
59+
delete_branch "$@"
5660
exit
5761
;;
5862
--)
@@ -93,6 +97,9 @@ branchname="$1"
9397

9498
rev=$(git-rev-parse --verify "$head") || exit
9599

96-
[ -e "$GIT_DIR/refs/heads/$branchname" ] && die "$branchname already exists"
100+
[ -e "$GIT_DIR/refs/heads/$branchname" ] &&
101+
die "$branchname already exists."
102+
git-check-ref-format "heads/$branchname" ||
103+
die "we do not like '$branchname' as a branch name."
97104

98105
echo $rev > "$GIT_DIR/refs/heads/$branchname"

git-checkout.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ while [ "$#" != "0" ]; do
1717
die "git checkout: -b needs a branch name"
1818
[ -e "$GIT_DIR/refs/heads/$newbranch" ] &&
1919
die "git checkout: branch $newbranch already exists"
20+
git-check-ref-format "heads/$newbranch" ||
21+
die "we do not like '$newbranch' as a branch name."
2022
;;
2123
"-f")
2224
force=1

git-tag.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ if [ -e "$GIT_DIR/refs/tags/$name" -a -z "$force" ]; then
5353
die "tag '$name' already exists"
5454
fi
5555
shift
56+
git-check-ref-format "tags/$name" ||
57+
die "we do not like '$name' as a tag name."
5658

5759
object=$(git-rev-parse --verify --default HEAD "$@") || exit 1
5860
type=$(git-cat-file -t $object) || exit 1

refs.c

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -334,17 +334,54 @@ int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1)
334334
return retval;
335335
}
336336

337+
/*
338+
* Make sure "ref" is something reasonable to have under ".git/refs/";
339+
* We do not like it if:
340+
*
341+
* - any path component of it begins with ".", or
342+
* - it has double dots "..", or
343+
* - it has ASCII control character, "~", "^", ":" or SP, anywhere, or
344+
* - it ends with a "/".
345+
*/
346+
347+
static inline int bad_ref_char(int ch)
348+
{
349+
return (((unsigned) ch) <= ' ' ||
350+
ch == '~' || ch == '^' || ch == ':');
351+
}
352+
337353
int check_ref_format(const char *ref)
338354
{
339-
char *middle;
340-
if (ref[0] == '.' || ref[0] == '/')
341-
return -1;
342-
middle = strchr(ref, '/');
343-
if (!middle || !middle[1])
344-
return -1;
345-
if (strchr(middle + 1, '/'))
346-
return -1;
347-
return 0;
355+
int ch, level;
356+
const char *cp = ref;
357+
358+
level = 0;
359+
while (1) {
360+
while ((ch = *cp++) == '/')
361+
; /* tolerate duplicated slashes */
362+
if (!ch)
363+
return -1; /* should not end with slashes */
364+
365+
/* we are at the beginning of the path component */
366+
if (ch == '.' || bad_ref_char(ch))
367+
return -1;
368+
369+
/* scan the rest of the path component */
370+
while ((ch = *cp++) != 0) {
371+
if (bad_ref_char(ch))
372+
return -1;
373+
if (ch == '/')
374+
break;
375+
if (ch == '.' && *cp == '.')
376+
return -1;
377+
}
378+
level++;
379+
if (!ch) {
380+
if (level < 2)
381+
return -1; /* at least of form "heads/blah" */
382+
return 0;
383+
}
384+
}
348385
}
349386

350387
int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1)

0 commit comments

Comments
 (0)