Skip to content

Commit 7b01c71

Browse files
committed
Sync with Git 2.13.7
* maint-2.13: Git 2.13.7 verify_path: disallow symlinks in .gitmodules update-index: stat updated files earlier verify_dotfile: mention case-insensitivity in comment verify_path: drop clever fallthrough skip_prefix: add case-insensitive variant is_{hfs,ntfs}_dotgitmodules: add tests is_ntfs_dotgit: match other .git files is_hfs_dotgit: match other .git files is_ntfs_dotgit: use a size_t for traversing string submodule-config: verify submodule names as paths
2 parents fc849d8 + 0114f71 commit 7b01c71

16 files changed

+492
-41
lines changed

Documentation/RelNotes/2.13.7.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Git v2.13.7 Release Notes
2+
=========================
3+
4+
Fixes since v2.13.6
5+
-------------------
6+
7+
* Submodule "names" come from the untrusted .gitmodules file, but we
8+
blindly append them to $GIT_DIR/modules to create our on-disk repo
9+
paths. This means you can do bad things by putting "../" into the
10+
name. We now enforce some rules for submodule names which will cause
11+
Git to ignore these malicious names (CVE-2018-11235).
12+
13+
Credit for finding this vulnerability and the proof of concept from
14+
which the test script was adapted goes to Etienne Stalmans.
15+
16+
* It was possible to trick the code that sanity-checks paths on NTFS
17+
into reading random piece of memory (CVE-2018-11233).
18+
19+
Credit for fixing for these bugs goes to Jeff King, Johannes
20+
Schindelin and others.

apply.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3882,9 +3882,9 @@ static int check_unsafe_path(struct patch *patch)
38823882
if (!patch->is_delete)
38833883
new_name = patch->new_name;
38843884

3885-
if (old_name && !verify_path(old_name))
3885+
if (old_name && !verify_path(old_name, patch->old_mode))
38863886
return error(_("invalid path '%s'"), old_name);
3887-
if (new_name && !verify_path(new_name))
3887+
if (new_name && !verify_path(new_name, patch->new_mode))
38883888
return error(_("invalid path '%s'"), new_name);
38893889
return 0;
38903890
}

builtin/submodule--helper.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,6 +1227,29 @@ static int is_active(int argc, const char **argv, const char *prefix)
12271227
return !is_submodule_active(the_repository, argv[1]);
12281228
}
12291229

1230+
/*
1231+
* Exit non-zero if any of the submodule names given on the command line is
1232+
* invalid. If no names are given, filter stdin to print only valid names
1233+
* (which is primarily intended for testing).
1234+
*/
1235+
static int check_name(int argc, const char **argv, const char *prefix)
1236+
{
1237+
if (argc > 1) {
1238+
while (*++argv) {
1239+
if (check_submodule_name(*argv) < 0)
1240+
return 1;
1241+
}
1242+
} else {
1243+
struct strbuf buf = STRBUF_INIT;
1244+
while (strbuf_getline(&buf, stdin) != EOF) {
1245+
if (!check_submodule_name(buf.buf))
1246+
printf("%s\n", buf.buf);
1247+
}
1248+
strbuf_release(&buf);
1249+
}
1250+
return 0;
1251+
}
1252+
12301253
#define SUPPORT_SUPER_PREFIX (1<<0)
12311254

12321255
struct cmd_struct {
@@ -1248,6 +1271,7 @@ static struct cmd_struct commands[] = {
12481271
{"push-check", push_check, 0},
12491272
{"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
12501273
{"is-active", is_active, 0},
1274+
{"check-name", check_name, 0},
12511275
};
12521276

12531277
int cmd_submodule__helper(int argc, const char **argv, const char *prefix)

builtin/update-index.c

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -359,10 +359,9 @@ static int process_directory(const char *path, int len, struct stat *st)
359359
return error("%s: is a directory - add files inside instead", path);
360360
}
361361

362-
static int process_path(const char *path)
362+
static int process_path(const char *path, struct stat *st, int stat_errno)
363363
{
364364
int pos, len;
365-
struct stat st;
366365
const struct cache_entry *ce;
367366

368367
len = strlen(path);
@@ -386,13 +385,13 @@ static int process_path(const char *path)
386385
* First things first: get the stat information, to decide
387386
* what to do about the pathname!
388387
*/
389-
if (lstat(path, &st) < 0)
390-
return process_lstat_error(path, errno);
388+
if (stat_errno)
389+
return process_lstat_error(path, stat_errno);
391390

392-
if (S_ISDIR(st.st_mode))
393-
return process_directory(path, len, &st);
391+
if (S_ISDIR(st->st_mode))
392+
return process_directory(path, len, st);
394393

395-
return add_one_path(ce, path, len, &st);
394+
return add_one_path(ce, path, len, st);
396395
}
397396

398397
static int add_cacheinfo(unsigned int mode, const struct object_id *oid,
@@ -401,7 +400,7 @@ static int add_cacheinfo(unsigned int mode, const struct object_id *oid,
401400
int size, len, option;
402401
struct cache_entry *ce;
403402

404-
if (!verify_path(path))
403+
if (!verify_path(path, mode))
405404
return error("Invalid path '%s'", path);
406405

407406
len = strlen(path);
@@ -444,7 +443,17 @@ static void chmod_path(char flip, const char *path)
444443

445444
static void update_one(const char *path)
446445
{
447-
if (!verify_path(path)) {
446+
int stat_errno = 0;
447+
struct stat st;
448+
449+
if (mark_valid_only || mark_skip_worktree_only || force_remove)
450+
st.st_mode = 0;
451+
else if (lstat(path, &st) < 0) {
452+
st.st_mode = 0;
453+
stat_errno = errno;
454+
} /* else stat is valid */
455+
456+
if (!verify_path(path, st.st_mode)) {
448457
fprintf(stderr, "Ignoring path %s\n", path);
449458
return;
450459
}
@@ -465,7 +474,7 @@ static void update_one(const char *path)
465474
report("remove '%s'", path);
466475
return;
467476
}
468-
if (process_path(path))
477+
if (process_path(path, &st, stat_errno))
469478
die("Unable to process path %s", path);
470479
report("add '%s'", path);
471480
}
@@ -535,7 +544,7 @@ static void read_index_info(int nul_term_line)
535544
path_name = uq.buf;
536545
}
537546

538-
if (!verify_path(path_name)) {
547+
if (!verify_path(path_name, mode)) {
539548
fprintf(stderr, "Ignoring path %s\n", path_name);
540549
continue;
541550
}

cache.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ extern int write_locked_index(struct index_state *, struct lock_file *lock, unsi
606606
extern int discard_index(struct index_state *);
607607
extern void move_index_extensions(struct index_state *dst, struct index_state *src);
608608
extern int unmerged_index(const struct index_state *);
609-
extern int verify_path(const char *path);
609+
extern int verify_path(const char *path, unsigned mode);
610610
extern int strcmp_offset(const char *s1, const char *s2, size_t *first_change);
611611
extern int index_dir_exists(struct index_state *istate, const char *name, int namelen);
612612
extern void adjust_dirname_case(struct index_state *istate, char *name);
@@ -1137,7 +1137,15 @@ int normalize_path_copy(char *dst, const char *src);
11371137
int longest_ancestor_length(const char *path, struct string_list *prefixes);
11381138
char *strip_path_suffix(const char *path, const char *suffix);
11391139
int daemon_avoid_alias(const char *path);
1140-
extern int is_ntfs_dotgit(const char *name);
1140+
1141+
/*
1142+
* These functions match their is_hfs_dotgit() counterparts; see utf8.h for
1143+
* details.
1144+
*/
1145+
int is_ntfs_dotgit(const char *name);
1146+
int is_ntfs_dotgitmodules(const char *name);
1147+
int is_ntfs_dotgitignore(const char *name);
1148+
int is_ntfs_dotgitattributes(const char *name);
11411149

11421150
/*
11431151
* Returns true iff "str" could be confused as a command-line option when

git-compat-util.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,23 @@ static inline int sane_iscase(int x, int is_lower)
980980
return (x & 0x20) == 0;
981981
}
982982

983+
/*
984+
* Like skip_prefix, but compare case-insensitively. Note that the comparison
985+
* is done via tolower(), so it is strictly ASCII (no multi-byte characters or
986+
* locale-specific conversions).
987+
*/
988+
static inline int skip_iprefix(const char *str, const char *prefix,
989+
const char **out)
990+
{
991+
do {
992+
if (!*prefix) {
993+
*out = str;
994+
return 1;
995+
}
996+
} while (tolower(*str++) == tolower(*prefix++));
997+
return 0;
998+
}
999+
9831000
static inline int strtoul_ui(char const *s, int base, unsigned int *result)
9841001
{
9851002
unsigned long ul;

git-submodule.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,11 @@ Use -f if you really want to add it." >&2
229229
sm_name="$sm_path"
230230
fi
231231

232+
if ! git submodule--helper check-name "$sm_name"
233+
then
234+
die "$(eval_gettext "'$sm_name' is not a valid submodule name")"
235+
fi
236+
232237
# perhaps the path exists and is already a git repo, else clone it
233238
if test -e "$sm_path"
234239
then

path.c

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1304,7 +1304,7 @@ static int only_spaces_and_periods(const char *path, size_t len, size_t skip)
13041304

13051305
int is_ntfs_dotgit(const char *name)
13061306
{
1307-
int len;
1307+
size_t len;
13081308

13091309
for (len = 0; ; len++)
13101310
if (!name[len] || name[len] == '\\' || is_dir_sep(name[len])) {
@@ -1321,6 +1321,90 @@ int is_ntfs_dotgit(const char *name)
13211321
}
13221322
}
13231323

1324+
static int is_ntfs_dot_generic(const char *name,
1325+
const char *dotgit_name,
1326+
size_t len,
1327+
const char *dotgit_ntfs_shortname_prefix)
1328+
{
1329+
int saw_tilde;
1330+
size_t i;
1331+
1332+
if ((name[0] == '.' && !strncasecmp(name + 1, dotgit_name, len))) {
1333+
i = len + 1;
1334+
only_spaces_and_periods:
1335+
for (;;) {
1336+
char c = name[i++];
1337+
if (!c)
1338+
return 1;
1339+
if (c != ' ' && c != '.')
1340+
return 0;
1341+
}
1342+
}
1343+
1344+
/*
1345+
* Is it a regular NTFS short name, i.e. shortened to 6 characters,
1346+
* followed by ~1, ... ~4?
1347+
*/
1348+
if (!strncasecmp(name, dotgit_name, 6) && name[6] == '~' &&
1349+
name[7] >= '1' && name[7] <= '4') {
1350+
i = 8;
1351+
goto only_spaces_and_periods;
1352+
}
1353+
1354+
/*
1355+
* Is it a fall-back NTFS short name (for details, see
1356+
* https://en.wikipedia.org/wiki/8.3_filename?
1357+
*/
1358+
for (i = 0, saw_tilde = 0; i < 8; i++)
1359+
if (name[i] == '\0')
1360+
return 0;
1361+
else if (saw_tilde) {
1362+
if (name[i] < '0' || name[i] > '9')
1363+
return 0;
1364+
} else if (name[i] == '~') {
1365+
if (name[++i] < '1' || name[i] > '9')
1366+
return 0;
1367+
saw_tilde = 1;
1368+
} else if (i >= 6)
1369+
return 0;
1370+
else if (name[i] < 0) {
1371+
/*
1372+
* We know our needles contain only ASCII, so we clamp
1373+
* here to make the results of tolower() sane.
1374+
*/
1375+
return 0;
1376+
} else if (tolower(name[i]) != dotgit_ntfs_shortname_prefix[i])
1377+
return 0;
1378+
1379+
goto only_spaces_and_periods;
1380+
}
1381+
1382+
/*
1383+
* Inline helper to make sure compiler resolves strlen() on literals at
1384+
* compile time.
1385+
*/
1386+
static inline int is_ntfs_dot_str(const char *name, const char *dotgit_name,
1387+
const char *dotgit_ntfs_shortname_prefix)
1388+
{
1389+
return is_ntfs_dot_generic(name, dotgit_name, strlen(dotgit_name),
1390+
dotgit_ntfs_shortname_prefix);
1391+
}
1392+
1393+
int is_ntfs_dotgitmodules(const char *name)
1394+
{
1395+
return is_ntfs_dot_str(name, "gitmodules", "gi7eba");
1396+
}
1397+
1398+
int is_ntfs_dotgitignore(const char *name)
1399+
{
1400+
return is_ntfs_dot_str(name, "gitignore", "gi250a");
1401+
}
1402+
1403+
int is_ntfs_dotgitattributes(const char *name)
1404+
{
1405+
return is_ntfs_dot_str(name, "gitattributes", "gi7d29");
1406+
}
1407+
13241408
int looks_like_command_line_option(const char *str)
13251409
{
13261410
return str && str[0] == '-';

0 commit comments

Comments
 (0)