Commit 906c123b76844b1bba457451f3571b64afa77006

Stefan Sperling 2020-07-23T14:21:29

do not allow symlinks pointing into the .got directory; noticed by semarie

diff --git a/lib/worktree.c b/lib/worktree.c
index be2544b..879e8cb 100644
--- a/lib/worktree.c
+++ b/lib/worktree.c
@@ -954,6 +954,7 @@ install_symlink(struct got_worktree *worktree, const char *ondisk_path,
 	char target_path[PATH_MAX];
 	size_t len, target_len = 0;
 	char *resolved_path = NULL, *abspath = NULL;
+	char *path_got = NULL;
 	const uint8_t *buf = got_object_blob_get_read_buf(blob);
 	size_t hdrlen = got_object_blob_get_hdrlen(blob);
 
@@ -1025,6 +1026,23 @@ install_symlink(struct got_worktree *worktree, const char *ondisk_path,
 		goto done;
 	}
 
+	/* Do not allow symlinks pointing into the .got directory. */
+	if (asprintf(&path_got, "%s/%s", worktree->root_path,
+	    GOT_WORKTREE_GOT_DIR) == -1) {
+		err = got_error_from_errno("asprintf");
+		goto done;
+	}
+	if (got_path_is_child(resolved_path ? resolved_path : (abspath ?
+	    abspath : target_path), path_got, strlen(path_got))) {
+		/* install as a regular file */
+		got_object_blob_rewind(blob);
+		err = install_blob(worktree, ondisk_path, path,
+		    GOT_DEFAULT_FILE_MODE, st_mode, blob,
+		    restoring_missing_file, reverting_versioned_file,
+		    repo, progress_cb, progress_arg);
+		goto done;
+	}
+
 	if (symlink(target_path, ondisk_path) == -1) {
 		if (errno == EEXIST) {
 			struct stat sb;
@@ -1108,6 +1126,7 @@ install_symlink(struct got_worktree *worktree, const char *ondisk_path,
 done:
 	free(resolved_path);
 	free(abspath);
+	free(path_got);
 	return err;
 }
 
diff --git a/regress/cmdline/checkout.sh b/regress/cmdline/checkout.sh
index 6cde66b..a7cf834 100755
--- a/regress/cmdline/checkout.sh
+++ b/regress/cmdline/checkout.sh
@@ -511,6 +511,7 @@ function test_checkout_symlink {
 	(cd $testroot/repo && ln -s /etc/passwd passwd.link)
 	(cd $testroot/repo && ln -s ../beta epsilon/beta.link)
 	(cd $testroot/repo && ln -s nonexistent nonexistent.link)
+	(cd $testroot/repo && ln -s .got/foo dotgotfoo.link)
 	(cd $testroot/repo && git add .)
 	git_commit $testroot/repo -m "add symlinks"
 
@@ -587,6 +588,23 @@ function test_checkout_symlink {
 	ret="$?"
 	if [ "$ret" != "0" ]; then
 		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+	fi
+
+	if [ -h $testroot/wt/dotgotfoo.link ]; then
+		echo -n "dotgotfoo.link symlink points into .got dir: " >&2
+		readlink $testroot/wt/dotgotfoo.link >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	echo -n ".got/foo" > $testroot/content.expected
+	cp $testroot/wt/dotgotfoo.link $testroot/content
+
+	cmp -s $testroot/content.expected $testroot/content
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/content.expected $testroot/content
 	fi
 	test_done "$testroot" "$ret"
 }