Skip to content

Commit 8528c31

Browse files
committed
Merge branch 'jk/submodule-fix-loose' into maint-2.13
* jk/submodule-fix-loose: 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 42e6fde + 10ecfa7 commit 8528c31

15 files changed

+472
-41
lines changed

apply.c

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

3870-
if (old_name && !verify_path(old_name))
3870+
if (old_name && !verify_path(old_name, patch->old_mode))
38713871
return error(_("invalid path '%s'"), old_name);
3872-
if (new_name && !verify_path(new_name))
3872+
if (new_name && !verify_path(new_name, patch->new_mode))
38733873
return error(_("invalid path '%s'"), new_name);
38743874
return 0;
38753875
}

builtin/submodule--helper.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,29 @@ static int is_active(int argc, const char **argv, const char *prefix)
11951195
return !is_submodule_initialized(argv[1]);
11961196
}
11971197

1198+
/*
1199+
* Exit non-zero if any of the submodule names given on the command line is
1200+
* invalid. If no names are given, filter stdin to print only valid names
1201+
* (which is primarily intended for testing).
1202+
*/
1203+
static int check_name(int argc, const char **argv, const char *prefix)
1204+
{
1205+
if (argc > 1) {
1206+
while (*++argv) {
1207+
if (check_submodule_name(*argv) < 0)
1208+
return 1;
1209+
}
1210+
} else {
1211+
struct strbuf buf = STRBUF_INIT;
1212+
while (strbuf_getline(&buf, stdin) != EOF) {
1213+
if (!check_submodule_name(buf.buf))
1214+
printf("%s\n", buf.buf);
1215+
}
1216+
strbuf_release(&buf);
1217+
}
1218+
return 0;
1219+
}
1220+
11981221
#define SUPPORT_SUPER_PREFIX (1<<0)
11991222

12001223
struct cmd_struct {
@@ -1216,6 +1239,7 @@ static struct cmd_struct commands[] = {
12161239
{"push-check", push_check, 0},
12171240
{"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
12181241
{"is-active", is_active, 0},
1242+
{"check-name", check_name, 0},
12191243
};
12201244

12211245
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
@@ -358,10 +358,9 @@ static int process_directory(const char *path, int len, struct stat *st)
358358
return error("%s: is a directory - add files inside instead", path);
359359
}
360360

361-
static int process_path(const char *path)
361+
static int process_path(const char *path, struct stat *st, int stat_errno)
362362
{
363363
int pos, len;
364-
struct stat st;
365364
const struct cache_entry *ce;
366365

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

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

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

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

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

406405
len = strlen(path);
@@ -443,7 +442,17 @@ static void chmod_path(char flip, const char *path)
443442

444443
static void update_one(const char *path)
445444
{
446-
if (!verify_path(path)) {
445+
int stat_errno = 0;
446+
struct stat st;
447+
448+
if (mark_valid_only || mark_skip_worktree_only || force_remove)
449+
st.st_mode = 0;
450+
else if (lstat(path, &st) < 0) {
451+
st.st_mode = 0;
452+
stat_errno = errno;
453+
} /* else stat is valid */
454+
455+
if (!verify_path(path, st.st_mode)) {
447456
fprintf(stderr, "Ignoring path %s\n", path);
448457
return;
449458
}
@@ -464,7 +473,7 @@ static void update_one(const char *path)
464473
report("remove '%s'", path);
465474
return;
466475
}
467-
if (process_path(path))
476+
if (process_path(path, &st, stat_errno))
468477
die("Unable to process path %s", path);
469478
report("add '%s'", path);
470479
}
@@ -534,7 +543,7 @@ static void read_index_info(int nul_term_line)
534543
path_name = uq.buf;
535544
}
536545

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

cache.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,7 @@ extern int read_index_unmerged(struct index_state *);
598598
extern int write_locked_index(struct index_state *, struct lock_file *lock, unsigned flags);
599599
extern int discard_index(struct index_state *);
600600
extern int unmerged_index(const struct index_state *);
601-
extern int verify_path(const char *path);
601+
extern int verify_path(const char *path, unsigned mode);
602602
extern int strcmp_offset(const char *s1, const char *s2, size_t *first_change);
603603
extern int index_dir_exists(struct index_state *istate, const char *name, int namelen);
604604
extern void adjust_dirname_case(struct index_state *istate, char *name);
@@ -1188,7 +1188,15 @@ int normalize_path_copy(char *dst, const char *src);
11881188
int longest_ancestor_length(const char *path, struct string_list *prefixes);
11891189
char *strip_path_suffix(const char *path, const char *suffix);
11901190
int daemon_avoid_alias(const char *path);
1191-
extern int is_ntfs_dotgit(const char *name);
1191+
1192+
/*
1193+
* These functions match their is_hfs_dotgit() counterparts; see utf8.h for
1194+
* details.
1195+
*/
1196+
int is_ntfs_dotgit(const char *name);
1197+
int is_ntfs_dotgitmodules(const char *name);
1198+
int is_ntfs_dotgitignore(const char *name);
1199+
int is_ntfs_dotgitattributes(const char *name);
11921200

11931201
/*
11941202
* 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
@@ -956,6 +956,23 @@ static inline int sane_iscase(int x, int is_lower)
956956
return (x & 0x20) == 0;
957957
}
958958

959+
/*
960+
* Like skip_prefix, but compare case-insensitively. Note that the comparison
961+
* is done via tolower(), so it is strictly ASCII (no multi-byte characters or
962+
* locale-specific conversions).
963+
*/
964+
static inline int skip_iprefix(const char *str, const char *prefix,
965+
const char **out)
966+
{
967+
do {
968+
if (!*prefix) {
969+
*out = str;
970+
return 1;
971+
}
972+
} while (tolower(*str++) == tolower(*prefix++));
973+
return 0;
974+
}
975+
959976
static inline int strtoul_ui(char const *s, int base, unsigned int *result)
960977
{
961978
unsigned long ul;

git-submodule.sh

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

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

path.c

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

12251225
int is_ntfs_dotgit(const char *name)
12261226
{
1227-
int len;
1227+
size_t len;
12281228

12291229
for (len = 0; ; len++)
12301230
if (!name[len] || name[len] == '\\' || is_dir_sep(name[len])) {
@@ -1241,6 +1241,90 @@ int is_ntfs_dotgit(const char *name)
12411241
}
12421242
}
12431243

1244+
static int is_ntfs_dot_generic(const char *name,
1245+
const char *dotgit_name,
1246+
size_t len,
1247+
const char *dotgit_ntfs_shortname_prefix)
1248+
{
1249+
int saw_tilde;
1250+
size_t i;
1251+
1252+
if ((name[0] == '.' && !strncasecmp(name + 1, dotgit_name, len))) {
1253+
i = len + 1;
1254+
only_spaces_and_periods:
1255+
for (;;) {
1256+
char c = name[i++];
1257+
if (!c)
1258+
return 1;
1259+
if (c != ' ' && c != '.')
1260+
return 0;
1261+
}
1262+
}
1263+
1264+
/*
1265+
* Is it a regular NTFS short name, i.e. shortened to 6 characters,
1266+
* followed by ~1, ... ~4?
1267+
*/
1268+
if (!strncasecmp(name, dotgit_name, 6) && name[6] == '~' &&
1269+
name[7] >= '1' && name[7] <= '4') {
1270+
i = 8;
1271+
goto only_spaces_and_periods;
1272+
}
1273+
1274+
/*
1275+
* Is it a fall-back NTFS short name (for details, see
1276+
* https://en.wikipedia.org/wiki/8.3_filename?
1277+
*/
1278+
for (i = 0, saw_tilde = 0; i < 8; i++)
1279+
if (name[i] == '\0')
1280+
return 0;
1281+
else if (saw_tilde) {
1282+
if (name[i] < '0' || name[i] > '9')
1283+
return 0;
1284+
} else if (name[i] == '~') {
1285+
if (name[++i] < '1' || name[i] > '9')
1286+
return 0;
1287+
saw_tilde = 1;
1288+
} else if (i >= 6)
1289+
return 0;
1290+
else if (name[i] < 0) {
1291+
/*
1292+
* We know our needles contain only ASCII, so we clamp
1293+
* here to make the results of tolower() sane.
1294+
*/
1295+
return 0;
1296+
} else if (tolower(name[i]) != dotgit_ntfs_shortname_prefix[i])
1297+
return 0;
1298+
1299+
goto only_spaces_and_periods;
1300+
}
1301+
1302+
/*
1303+
* Inline helper to make sure compiler resolves strlen() on literals at
1304+
* compile time.
1305+
*/
1306+
static inline int is_ntfs_dot_str(const char *name, const char *dotgit_name,
1307+
const char *dotgit_ntfs_shortname_prefix)
1308+
{
1309+
return is_ntfs_dot_generic(name, dotgit_name, strlen(dotgit_name),
1310+
dotgit_ntfs_shortname_prefix);
1311+
}
1312+
1313+
int is_ntfs_dotgitmodules(const char *name)
1314+
{
1315+
return is_ntfs_dot_str(name, "gitmodules", "gi7eba");
1316+
}
1317+
1318+
int is_ntfs_dotgitignore(const char *name)
1319+
{
1320+
return is_ntfs_dot_str(name, "gitignore", "gi250a");
1321+
}
1322+
1323+
int is_ntfs_dotgitattributes(const char *name)
1324+
{
1325+
return is_ntfs_dot_str(name, "gitattributes", "gi7d29");
1326+
}
1327+
12441328
int looks_like_command_line_option(const char *str)
12451329
{
12461330
return str && str[0] == '-';

read-cache.c

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ struct cache_entry *make_cache_entry(unsigned int mode,
732732
int size, len;
733733
struct cache_entry *ce, *ret;
734734

735-
if (!verify_path(path)) {
735+
if (!verify_path(path, mode)) {
736736
error("Invalid path '%s'", path);
737737
return NULL;
738738
}
@@ -796,7 +796,7 @@ int ce_same_name(const struct cache_entry *a, const struct cache_entry *b)
796796
* Also, we don't want double slashes or slashes at the
797797
* end that can make pathnames ambiguous.
798798
*/
799-
static int verify_dotfile(const char *rest)
799+
static int verify_dotfile(const char *rest, unsigned mode)
800800
{
801801
/*
802802
* The first character was '.', but that
@@ -810,25 +810,37 @@ static int verify_dotfile(const char *rest)
810810

811811
switch (*rest) {
812812
/*
813-
* ".git" followed by NUL or slash is bad. This
814-
* shares the path end test with the ".." case.
813+
* ".git" followed by NUL or slash is bad. Note that we match
814+
* case-insensitively here, even if ignore_case is not set.
815+
* This outlaws ".GIT" everywhere out of an abundance of caution,
816+
* since there's really no good reason to allow it.
817+
*
818+
* Once we've seen ".git", we can also find ".gitmodules", etc (also
819+
* case-insensitively).
815820
*/
816821
case 'g':
817822
case 'G':
818823
if (rest[1] != 'i' && rest[1] != 'I')
819824
break;
820825
if (rest[2] != 't' && rest[2] != 'T')
821826
break;
822-
rest += 2;
823-
/* fallthrough */
827+
if (rest[3] == '\0' || is_dir_sep(rest[3]))
828+
return 0;
829+
if (S_ISLNK(mode)) {
830+
rest += 3;
831+
if (skip_iprefix(rest, "modules", &rest) &&
832+
(*rest == '\0' || is_dir_sep(*rest)))
833+
return 0;
834+
}
835+
break;
824836
case '.':
825837
if (rest[1] == '\0' || is_dir_sep(rest[1]))
826838
return 0;
827839
}
828840
return 1;
829841
}
830842

831-
int verify_path(const char *path)
843+
int verify_path(const char *path, unsigned mode)
832844
{
833845
char c;
834846

@@ -841,12 +853,25 @@ int verify_path(const char *path)
841853
return 1;
842854
if (is_dir_sep(c)) {
843855
inside:
844-
if (protect_hfs && is_hfs_dotgit(path))
845-
return 0;
846-
if (protect_ntfs && is_ntfs_dotgit(path))
847-
return 0;
856+
if (protect_hfs) {
857+
if (is_hfs_dotgit(path))
858+
return 0;
859+
if (S_ISLNK(mode)) {
860+
if (is_hfs_dotgitmodules(path))
861+
return 0;
862+
}
863+
}
864+
if (protect_ntfs) {
865+
if (is_ntfs_dotgit(path))
866+
return 0;
867+
if (S_ISLNK(mode)) {
868+
if (is_ntfs_dotgitmodules(path))
869+
return 0;
870+
}
871+
}
872+
848873
c = *path++;
849-
if ((c == '.' && !verify_dotfile(path)) ||
874+
if ((c == '.' && !verify_dotfile(path, mode)) ||
850875
is_dir_sep(c) || c == '\0')
851876
return 0;
852877
}
@@ -1163,7 +1188,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
11631188

11641189
if (!ok_to_add)
11651190
return -1;
1166-
if (!verify_path(ce->name))
1191+
if (!verify_path(ce->name, ce->ce_mode))
11671192
return error("Invalid path '%s'", ce->name);
11681193

11691194
if (!skip_df_check &&

0 commit comments

Comments
 (0)