index: allow read of index w/ illegal entries Allow `git_index_read` to handle reading existing indexes with illegal entries. Allow the low-level `git_index_add` to add properly formed `git_index_entry`s even if they contain paths that would be illegal for the current filesystem (eg, `AUX`). Continue to disallow `git_index_add_bypath` from adding entries that are illegal universally illegal (eg, `.git`, `foo/../bar`).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
diff --git a/src/checkout.c b/src/checkout.c
index a92ad08..07b6bd6 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -1226,7 +1226,7 @@ static int checkout_verify_paths(
int action,
git_diff_delta *delta)
{
- unsigned int flags = GIT_PATH_REJECT_DEFAULTS | GIT_PATH_REJECT_DOT_GIT;
+ unsigned int flags = GIT_PATH_REJECT_WORKDIR_DEFAULTS;
if (action & CHECKOUT_ACTION__REMOVE) {
if (!git_path_isvalid(repo, delta->old_file.path, flags)) {
diff --git a/src/index.c b/src/index.c
index 5c7bd90..5728008 100644
--- a/src/index.c
+++ b/src/index.c
@@ -891,17 +891,31 @@ static void index_entry_adjust_namemask(
entry->flags |= GIT_IDXENTRY_NAMEMASK;
}
+/* When `from_workdir` is true, we will validate the paths to avoid placing
+ * paths that are invalid for the working directory on the current filesystem
+ * (eg, on Windows, we will disallow `GIT~1`, `AUX`, `COM1`, etc). This
+ * function will *always* prevent `.git` and directory traversal `../` from
+ * being added to the index.
+ */
static int index_entry_create(
git_index_entry **out,
git_repository *repo,
- const char *path)
+ const char *path,
+ bool from_workdir)
{
size_t pathlen = strlen(path), alloclen;
struct entry_internal *entry;
+ unsigned int path_valid_flags = GIT_PATH_REJECT_INDEX_DEFAULTS;
+
+ /* always reject placing `.git` in the index and directory traversal.
+ * when requested, disallow platform-specific filenames and upgrade to
+ * the platform-specific `.git` tests (eg, `git~1`, etc).
+ */
+ if (from_workdir)
+ path_valid_flags |= GIT_PATH_REJECT_WORKDIR_DEFAULTS;
- if (!git_path_isvalid(repo, path,
- GIT_PATH_REJECT_DEFAULTS | GIT_PATH_REJECT_DOT_GIT)) {
- giterr_set(GITERR_INDEX, "Invalid path: '%s'", path);
+ if (!git_path_isvalid(repo, path, path_valid_flags)) {
+ giterr_set(GITERR_INDEX, "invalid path: '%s'", path);
return -1;
}
@@ -933,7 +947,7 @@ static int index_entry_init(
"Could not initialize index entry. "
"Index is not backed up by an existing repository.");
- if (index_entry_create(&entry, INDEX_OWNER(index), rel_path) < 0)
+ if (index_entry_create(&entry, INDEX_OWNER(index), rel_path, true) < 0)
return -1;
/* write the blob to disk and get the oid and stat info */
@@ -1013,7 +1027,7 @@ static int index_entry_dup(
git_index *index,
const git_index_entry *src)
{
- if (index_entry_create(out, INDEX_OWNER(index), src->path) < 0)
+ if (index_entry_create(out, INDEX_OWNER(index), src->path, false) < 0)
return -1;
index_entry_cpy(*out, src);
@@ -1035,7 +1049,7 @@ static int index_entry_dup_nocache(
git_index *index,
const git_index_entry *src)
{
- if (index_entry_create(out, INDEX_OWNER(index), src->path) < 0)
+ if (index_entry_create(out, INDEX_OWNER(index), src->path, false) < 0)
return -1;
index_entry_cpy_nocache(*out, src);
@@ -1447,7 +1461,7 @@ static int add_repo_as_submodule(git_index_entry **out, git_index *index, const
struct stat st;
int error;
- if (index_entry_create(&entry, INDEX_OWNER(index), path) < 0)
+ if (index_entry_create(&entry, INDEX_OWNER(index), path, true) < 0)
return -1;
if ((error = git_buf_joinpath(&abspath, git_repository_workdir(repo), path)) < 0)
@@ -2884,7 +2898,7 @@ static int read_tree_cb(
if (git_buf_joinpath(&path, root, tentry->filename) < 0)
return -1;
- if (index_entry_create(&entry, INDEX_OWNER(data->index), path.ptr) < 0)
+ if (index_entry_create(&entry, INDEX_OWNER(data->index), path.ptr, false) < 0)
return -1;
entry->mode = tentry->attr;
diff --git a/src/path.c b/src/path.c
index 18b4f03..852ef57 100644
--- a/src/path.c
+++ b/src/path.c
@@ -1630,9 +1630,12 @@ static bool verify_component(
!verify_dotgit_ntfs(repo, component, len))
return false;
+ /* don't bother rerunning the `.git` test if we ran the HFS or NTFS
+ * specific tests, they would have already rejected `.git`.
+ */
if ((flags & GIT_PATH_REJECT_DOT_GIT_HFS) == 0 &&
(flags & GIT_PATH_REJECT_DOT_GIT_NTFS) == 0 &&
- (flags & GIT_PATH_REJECT_DOT_GIT) &&
+ (flags & GIT_PATH_REJECT_DOT_GIT_LITERAL) &&
len == 4 &&
component[0] == '.' &&
(component[1] == 'g' || component[1] == 'G') &&
@@ -1649,6 +1652,8 @@ GIT_INLINE(unsigned int) dotgit_flags(
{
int protectHFS = 0, protectNTFS = 0;
+ flags |= GIT_PATH_REJECT_DOT_GIT_LITERAL;
+
#ifdef __APPLE__
protectHFS = 1;
#endif
diff --git a/src/path.h b/src/path.h
index 7e156fc..875c8cb 100644
--- a/src/path.h
+++ b/src/path.h
@@ -564,15 +564,16 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or
#define GIT_PATH_REJECT_TRAILING_COLON (1 << 6)
#define GIT_PATH_REJECT_DOS_PATHS (1 << 7)
#define GIT_PATH_REJECT_NT_CHARS (1 << 8)
-#define GIT_PATH_REJECT_DOT_GIT_HFS (1 << 9)
-#define GIT_PATH_REJECT_DOT_GIT_NTFS (1 << 10)
+#define GIT_PATH_REJECT_DOT_GIT_LITERAL (1 << 9)
+#define GIT_PATH_REJECT_DOT_GIT_HFS (1 << 10)
+#define GIT_PATH_REJECT_DOT_GIT_NTFS (1 << 11)
/* Default path safety for writing files to disk: since we use the
* Win32 "File Namespace" APIs ("\\?\") we need to protect from
* paths that the normal Win32 APIs would not write.
*/
#ifdef GIT_WIN32
-# define GIT_PATH_REJECT_DEFAULTS \
+# define GIT_PATH_REJECT_FILESYSTEM_DEFAULTS \
GIT_PATH_REJECT_TRAVERSAL | \
GIT_PATH_REJECT_BACKSLASH | \
GIT_PATH_REJECT_TRAILING_DOT | \
@@ -581,9 +582,18 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or
GIT_PATH_REJECT_DOS_PATHS | \
GIT_PATH_REJECT_NT_CHARS
#else
-# define GIT_PATH_REJECT_DEFAULTS GIT_PATH_REJECT_TRAVERSAL
+# define GIT_PATH_REJECT_FILESYSTEM_DEFAULTS \
+ GIT_PATH_REJECT_TRAVERSAL
#endif
+ /* Paths that should never be written into the working directory. */
+#define GIT_PATH_REJECT_WORKDIR_DEFAULTS \
+ GIT_PATH_REJECT_FILESYSTEM_DEFAULTS | GIT_PATH_REJECT_DOT_GIT
+
+/* Paths that should never be written to the index. */
+#define GIT_PATH_REJECT_INDEX_DEFAULTS \
+ GIT_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT
+
/*
* Determine whether a path is a valid git path or not - this must not contain
* a '.' or '..' component, or a component that is ".git" (in any case).
diff --git a/src/refdb_fs.c b/src/refdb_fs.c
index 85b5034..1348c67 100644
--- a/src/refdb_fs.c
+++ b/src/refdb_fs.c
@@ -717,7 +717,7 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *
assert(file && backend && name);
- if (!git_path_isvalid(backend->repo, name, GIT_PATH_REJECT_DEFAULTS)) {
+ if (!git_path_isvalid(backend->repo, name, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
giterr_set(GITERR_INVALID, "Invalid reference name '%s'.", name);
return GIT_EINVALIDSPEC;
}
@@ -1672,7 +1672,7 @@ static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char
repo = backend->repo;
- if (!git_path_isvalid(backend->repo, refname, GIT_PATH_REJECT_DEFAULTS)) {
+ if (!git_path_isvalid(backend->repo, refname, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
giterr_set(GITERR_INVALID, "Invalid reference name '%s'.", refname);
return GIT_EINVALIDSPEC;
}
diff --git a/tests/path/core.c b/tests/path/core.c
index 064f149..3dccfe5 100644
--- a/tests/path/core.c
+++ b/tests/path/core.c
@@ -105,12 +105,12 @@ void test_path_core__isvalid_dot_git(void)
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/.GIT/bar", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar/.Git", 0));
- cl_assert_equal_b(false, git_path_isvalid(NULL, ".git", GIT_PATH_REJECT_DOT_GIT));
- cl_assert_equal_b(false, git_path_isvalid(NULL, ".git/foo", GIT_PATH_REJECT_DOT_GIT));
- cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.git", GIT_PATH_REJECT_DOT_GIT));
- cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.git/bar", GIT_PATH_REJECT_DOT_GIT));
- cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.GIT/bar", GIT_PATH_REJECT_DOT_GIT));
- cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar/.Git", GIT_PATH_REJECT_DOT_GIT));
+ cl_assert_equal_b(false, git_path_isvalid(NULL, ".git", GIT_PATH_REJECT_DOT_GIT_LITERAL));
+ cl_assert_equal_b(false, git_path_isvalid(NULL, ".git/foo", GIT_PATH_REJECT_DOT_GIT_LITERAL));
+ cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.git", GIT_PATH_REJECT_DOT_GIT_LITERAL));
+ cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.git/bar", GIT_PATH_REJECT_DOT_GIT_LITERAL));
+ cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.GIT/bar", GIT_PATH_REJECT_DOT_GIT_LITERAL));
+ cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar/.Git", GIT_PATH_REJECT_DOT_GIT_LITERAL));
cl_assert_equal_b(true, git_path_isvalid(NULL, "!git", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/!git", 0));