diff --git a/SOURCES/git-1.9.4-cve9390.patch b/SOURCES/git-1.9.4-cve9390.patch new file mode 100644 index 0000000..2637bdf --- /dev/null +++ b/SOURCES/git-1.9.4-cve9390.patch @@ -0,0 +1,568 @@ +diff --git a/Documentation/config.txt b/Documentation/config.txt +index c26a7c8..7076aa9 100644 +--- a/Documentation/config.txt ++++ b/Documentation/config.txt +@@ -234,6 +234,17 @@ core.precomposeunicode:: + When false, file names are handled fully transparent by Git, + which is backward compatible with older versions of Git. + ++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. ++ + core.trustctime:: + If false, the ctime differences between the index and the + working tree are ignored; useful when the inode change time +--- a/builtin/annotate.c ++++ b/builtin/annotate.c +@@ -5,20 +5,18 @@ + */ + #include "git-compat-util.h" + #include "builtin.h" ++#include "argv-array.h" + + int cmd_annotate(int argc, const char **argv, const char *prefix) + { +- const char **nargv; ++ struct argv_array args = ARGV_ARRAY_INIT; + int i; +- nargv = xmalloc(sizeof(char *) * (argc + 2)); + +- nargv[0] = "annotate"; +- nargv[1] = "-c"; ++ argv_array_pushl(&args, "annotate", "-c", NULL); + + for (i = 1; i < argc; i++) { +- nargv[i+1] = argv[i]; ++ argv_array_push(&args, argv[i]); + } +- nargv[argc + 1] = NULL; + +- return cmd_blame(argc + 1, nargv, prefix); ++ return cmd_blame(args.argc, args.argv, prefix); + } +diff --git a/builtin/clean.c b/builtin/clean.c +index 977a068..8926ca8 100644 +--- a/builtin/clean.c ++++ b/builtin/clean.c +@@ -48,7 +48,7 @@ enum color_clean { + CLEAN_COLOR_PROMPT = 2, + CLEAN_COLOR_HEADER = 3, + CLEAN_COLOR_HELP = 4, +- CLEAN_COLOR_ERROR = 5, ++ CLEAN_COLOR_ERROR = 5 + }; + + #define MENU_OPTS_SINGLETON 01 +diff --git a/cache.h b/cache.h +index ebe9a40..017c487 100644 +--- a/cache.h ++++ b/cache.h +@@ -587,6 +587,8 @@ extern int fsync_object_files; + extern int core_preload_index; + extern int core_apply_sparse_checkout; + extern int precomposed_unicode; ++extern int protect_hfs; ++extern int protect_ntfs; + + /* + * The character that begins a commented line in user-editable file +@@ -782,6 +784,7 @@ int longest_ancestor_length(const char *path, struct string_list *prefixes); + char *strip_path_suffix(const char *path, const char *suffix); + int daemon_avoid_alias(const char *path); + int offset_1st_component(const char *path); ++extern int is_ntfs_dotgit(const char *name); + + /* object replacement */ + #define LOOKUP_REPLACE_OBJECT 1 +diff --git a/config.c b/config.c +index 314d8ee..8984ad2 100644 +--- a/config.c ++++ b/config.c +@@ -885,6 +885,16 @@ static int git_default_core_config(const char *var, const char *value) + return 0; + } + ++ 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; ++ } ++ + /* Add other config variables here and to Documentation/config.txt. */ + return 0; + } +diff --git a/config.mak.uname b/config.mak.uname +index efaed94..f3cdcbc 100644 +--- a/config.mak.uname ++++ b/config.mak.uname +@@ -97,6 +97,7 @@ ifeq ($(uname_S),Darwin) + HAVE_DEV_TTY = YesPlease + COMPAT_OBJS += compat/precompose_utf8.o + BASIC_CFLAGS += -DPRECOMPOSE_UNICODE ++ BASIC_CFLAGS += -DPROTECT_HFS_DEFAULT=1 + endif + ifeq ($(uname_S),SunOS) + NEEDS_SOCKET = YesPlease +@@ -369,6 +370,7 @@ ifeq ($(uname_S),Windows) + EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib + PTHREAD_LIBS = + lib = ++ BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1 + ifndef DEBUG + BASIC_CFLAGS += -GL -Os -MT + BASIC_LDFLAGS += -LTCG +@@ -513,6 +515,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) + COMPAT_OBJS += compat/mingw.o compat/winansi.o \ + compat/win32/pthread.o compat/win32/syslog.o \ + compat/win32/dirent.o ++ BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1 + BASIC_LDFLAGS += -Wl,--large-address-aware + EXTLIBS += -lws2_32 + GITLIBS += git.res +diff --git a/environment.c b/environment.c +index 4a3437d..39a8c6c 100644 +--- a/environment.c ++++ b/environment.c +@@ -64,6 +64,16 @@ int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */ + struct startup_info *startup_info; + unsigned long pack_size_limit_cfg; + ++#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; ++ + /* + * The character that begins a commented line in user-editable file + * that is subject to stripspace. +diff --git a/fsck.c b/fsck.c +index 64bf279..ee7f531 100644 +--- a/fsck.c ++++ b/fsck.c +@@ -6,6 +6,7 @@ + #include "commit.h" + #include "tag.h" + #include "fsck.h" ++#include "utf8.h" + + static int fsck_walk_tree(struct tree *tree, fsck_walk_func walk, void *data) + { +@@ -175,7 +176,8 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func) + has_dot = 1; + if (!strcmp(name, "..")) + has_dotdot = 1; +- if (!strcmp(name, ".git")) ++ if (!strcasecmp(name, ".git") || is_hfs_dotgit(name) || ++ is_ntfs_dotgit(name)) + has_dotgit = 1; + has_zero_pad |= *(char *)desc.buffer == '0'; + update_tree_entry(&desc); +diff --git a/path.c b/path.c +index f9c5062..dfd58f4 100644 +--- a/path.c ++++ b/path.c +@@ -830,3 +830,36 @@ int offset_1st_component(const char *path) + return 2 + is_dir_sep(path[2]); + return is_dir_sep(path[0]); + } ++ ++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 --git a/pretty.c b/pretty.c +index 6e266dd..f64ff9a 100644 +--- a/pretty.c ++++ b/pretty.c +@@ -274,7 +274,7 @@ static void add_rfc822_quoted(struct strbuf *out, const char *s, int len) + + enum rfc2047_type { + RFC2047_SUBJECT, +- RFC2047_ADDRESS, ++ RFC2047_ADDRESS + }; + + static int is_rfc2047_special(char ch, enum rfc2047_type type) +diff --git a/read-cache.c b/read-cache.c +index 4b4effd..ee07cd6 100644 +--- a/read-cache.c ++++ b/read-cache.c +@@ -14,6 +14,7 @@ + #include "resolve-undo.h" + #include "strbuf.h" + #include "varint.h" ++#include "utf8.h" + + static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, + unsigned int options); +@@ -752,9 +753,10 @@ static int verify_dotfile(const char *rest) + * shares the path end test with the ".." case. + */ + case 'g': +- if (rest[1] != 'i') ++ case 'G': ++ if (rest[1] != 'i' && rest[1] != 'I') + break; +- if (rest[2] != 't') ++ if (rest[2] != 't' && rest[2] != 'T') + break; + rest += 2; + /* fallthrough */ +@@ -778,6 +780,10 @@ int verify_path(const char *path) + return 1; + if (is_dir_sep(c)) { + inside: ++ if (protect_hfs && is_hfs_dotgit(path)) ++ return 0; ++ if (protect_ntfs && is_ntfs_dotgit(path)) ++ return 0; + c = *path++; + if ((c == '.' && !verify_dotfile(path)) || + is_dir_sep(c) || c == '\0') +diff --git a/t/t1014-read-tree-confusing.sh b/t/t1014-read-tree-confusing.sh +new file mode 100755 +index 0000000..2f5a25d +--- /dev/null ++++ b/t/t1014-read-tree-confusing.sh +@@ -0,0 +1,62 @@ ++#!/bin/sh ++ ++test_description='check that read-tree rejects confusing paths' ++. ./test-lib.sh ++ ++test_expect_success 'create base tree' ' ++ echo content >file && ++ git add file && ++ git commit -m base && ++ blob=$(git rev-parse HEAD:file) && ++ tree=$(git rev-parse HEAD^{tree}) ++' ++ ++test_expect_success 'enable core.protectHFS for rejection tests' ' ++ git config core.protectHFS true ++' ++ ++test_expect_success 'enable core.protectNTFS for rejection tests' ' ++ git config core.protectNTFS true ++' ++ ++while read path pretty; do ++ : ${pretty:=$path} ++ case "$path" in ++ *SPACE) ++ path="${path%SPACE} " ++ ;; ++ esac ++ test_expect_success "reject $pretty at end of path" ' ++ printf "100644 blob %s\t%s" "$blob" "$path" >tree && ++ bogus=$(git mktree tree && ++ bogus=$(git mktree tree && ++ ok=$(git mktree 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 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 + + test_done +diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh +index 74de814..04118ad 100755 +--- a/t/t7300-clean.sh ++++ b/t/t7300-clean.sh +@@ -426,10 +426,10 @@ test_expect_success SANITY 'removal failure' ' + + mkdir foo && + touch foo/bar && ++ test_when_finished "chmod 755 foo" && + (exec unpack_rejects[i].strdup_strings = 1; + } + +-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, + unsigned int set, unsigned int clear) + { + clear |= CE_HASHED | CE_UNHASHED; +@@ -112,8 +112,8 @@ static void do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce, + + ce->next = NULL; + ce->ce_flags = (ce->ce_flags & ~clear) | set; +- 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); + } + + static struct cache_entry *dup_entry(const struct cache_entry *ce) +@@ -608,7 +608,9 @@ static int unpack_nondirectories(int n, unsigned long mask, + + for (i = 0; i < n; i++) + if (src[i] && src[i] != o->df_conflict_entry) +- do_add_entry(o, src[i], 0, 0); ++ if (do_add_entry(o, src[i], 0, 0)) ++ return -1; ++ + return 0; + } + +diff --git a/utf8.c b/utf8.c +index 536a9c8..015c815 100644 +--- a/utf8.c ++++ b/utf8.c +@@ -627,3 +627,67 @@ int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding) + + return chrlen; + } ++ ++/* ++ * 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 --git a/utf8.h b/utf8.h +index 65d0e42..e4d9183 100644 +--- a/utf8.h ++++ b/utf8.h +@@ -42,4 +42,12 @@ static inline char *reencode_string(const char *in, + + int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding); + ++/* ++ * 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); ++ + #endif + diff --git a/SPECS/git.spec b/SPECS/git.spec index a4515dd..7069058 100644 --- a/SPECS/git.spec +++ b/SPECS/git.spec @@ -53,7 +53,7 @@ Name: %{?scl_prefix}git Version: 1.9.4 -Release: 2%{?dist} +Release: 3%{?dist} Summary: Fast Version Control System License: GPLv2 Group: Development/Tools @@ -75,6 +75,7 @@ Patch1: git-cvsimport-Ignore-cvsps-2.2b1-Branches-output.patch # https://bugzilla.redhat.com/600411 Patch3: git-1.7-el5-emacs-support.patch Patch5: 0001-git-subtree-Use-gitexecdir-instead-of-libexecdir.patch +Patch6: git-1.9.4-cve9390.patch BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) @@ -301,6 +302,7 @@ Requires: %{?scl_prefix}emacs-git = %{version}-%{release} %patch3 -p1 %endif %patch5 -p1 +%patch6 -p1 %if %{use_prebuilt_docs} mkdir -p prebuilt_docs/{html,man} @@ -621,6 +623,10 @@ rm -rf %{buildroot} # No files for you! %changelog +* Wed Jul 01 2015 Petr Stodulka - 1.9.4-3 +- fix CVE-2014-9390 + Resolves: rhbz#1220552 + * Wed Jun 11 2014 Ondrej Oprala - 1.9.4-1 - git 1.9.4 - Use kernel.org for sources (googlecode's out-of-date)