mv: handle lstat() failure correctly

When moving a directory onto another with `git mv` various checks are performed. One of of these validates that the destination is not existing. When calling `lstat` on the destination path and it fails as the path doesn't exist, some environments seem to overwrite the passed in `stat` memory nonetheless (I observed this issue on debian 12 of x86_64, running on OrbStack on ARM, emulated with Rosetta). This would affect the code that followed as it would still acccess a now modified `st` structure, which now seems to contain uninitialized memory. `S_ISDIR(st_dir_mode)` would then typically return false causing the code to run into a bad case. The fix avoids overwriting the existing `st` structure, providing an alternative that exists only for that purpose. Note that this patch minimizes complexity instead of stack-frame size. Signed-off-by: Sebastian Thiel <sebastian.thiel@icloud.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Sebastian Thiel committed Aug 9, 2023 at 07:47 UTC 72695d8214791161a943086e894874b4fd71ba9f
2 files changed +9 -2
builtin/mv.c
+2 -2
@@ -183,7 +183,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
183 int src_dir_nr = 0, src_dir_alloc = 0;
184 struct strbuf a_src_dir = STRBUF_INIT;
185 enum update_mode *modes, dst_mode = 0;
186 - struct stat st;
186 + struct stat st, dest_st;
187 struct string_list src_for_dst = STRING_LIST_INIT_NODUP;
188 struct lock_file lock_file = LOCK_INIT;
189 struct cache_entry *ce;
@@ -303,7 +303,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
303 goto act_on_entry;
304 }
305 if (S_ISDIR(st.st_mode)
306 - && lstat(dst, &st) == 0) {
306 + && lstat(dst, &dest_st) == 0) {
307 bad = _("cannot move directory over file");
308 goto act_on_entry;
309 }
t/t7001-mv.sh
+7
@@ -174,6 +174,13 @@ test_expect_success 'do not move directory over existing directory' '
174 test_must_fail git mv path2 path0
175 '
176
177 +test_expect_success 'rename directory to non-existing directory' '
178 + mkdir dir-a &&
179 + >dir-a/f &&
180 + git add dir-a &&
181 + git mv dir-a non-existing-dir
182 +'
183 +
184 test_expect_success 'move into "."' '
185 git mv path1/path2/ .
186 '