summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorIliyas Jorio <iliyas@jor.io>2022-02-08 20:18:15 +0100
committerEdward Thomson <ethomson@github.com>2022-02-11 22:46:54 -0500
commit0a0cd67dcbc9406f57de17be5d1dde25897677dd (patch)
tree9842631c761395792a4c19cd1cdd87487bba840d /tests
parent1d811f0eb990ecbba406f9a7debf463669218e9d (diff)
downloadlibgit2-0a0cd67dcbc9406f57de17be5d1dde25897677dd.tar.gz
diff_file: fix crash if size of diffed file changes in workdir
"diff_file_content_load_workdir_file()" maps a file from the workdir into memory. It uses git_diff_file.size to determine the size of the memory mapping. If this value goes stale, the mmaped area would be sized incorrectly. This could occur if an external program changes the contents of the file after libgit2 had cached its size. This used to segfault if the file becomes smaller (mmaped area too large). This patch causes diff_file_content_load_workdir_file to fail without crashing if it detects that the file size has changed.
Diffstat (limited to 'tests')
-rw-r--r--tests/diff/externalmodifications.c77
1 files changed, 77 insertions, 0 deletions
diff --git a/tests/diff/externalmodifications.c b/tests/diff/externalmodifications.c
new file mode 100644
index 000000000..d18e1feee
--- /dev/null
+++ b/tests/diff/externalmodifications.c
@@ -0,0 +1,77 @@
+#include "clar_libgit2.h"
+#include "../checkout/checkout_helpers.h"
+
+#include "index.h"
+#include "repository.h"
+
+static git_repository *g_repo;
+
+void test_diff_externalmodifications__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo2");
+}
+
+void test_diff_externalmodifications__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+ g_repo = NULL;
+}
+
+void test_diff_externalmodifications__file_becomes_smaller(void)
+{
+ git_index *index;
+ git_diff *diff;
+ git_patch* patch;
+ git_str path = GIT_STR_INIT;
+ char big_string[500001];
+
+ cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "README"));
+
+ /* Modify the file with a large string */
+ memset(big_string, '\n', sizeof(big_string) - 1);
+ big_string[sizeof(big_string) - 1] = '\0';
+ cl_git_mkfile(path.ptr, big_string);
+
+ /* Get a diff */
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL));
+ cl_assert_equal_i(1, git_diff_num_deltas(diff));
+ cl_assert_equal_i(500000, git_diff_get_delta(diff, 0)->new_file.size);
+
+ /* Simulate file modification after we've gotten the diff.
+ * Write a shorter string to ensure that we don't mmap 500KB from
+ * the previous revision, which would most likely crash. */
+ cl_git_mkfile(path.ptr, "hello");
+
+ /* Attempt to get a patch */
+ cl_git_fail(git_patch_from_diff(&patch, diff, 0));
+
+ git_index_free(index);
+ git_diff_free(diff);
+ git_str_dispose(&path);
+}
+
+void test_diff_externalmodifications__file_deleted(void)
+{
+ git_index *index;
+ git_diff *diff;
+ git_patch* patch;
+ git_str path = GIT_STR_INIT;
+
+ cl_git_pass(git_str_joinpath(&path, git_repository_workdir(g_repo), "README"));
+
+ /* Get a diff */
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, NULL));
+ cl_assert_equal_i(0, git_diff_num_deltas(diff));
+
+ /* Delete the file */
+ cl_git_rmfile(path.ptr);
+
+ /* Attempt to get a patch */
+ cl_git_fail(git_patch_from_diff(&patch, diff, 0));
+
+ git_index_free(index);
+ git_diff_free(diff);
+ git_str_dispose(&path);
+}