Download raw body.
do not allow versioned files in meta-data directories
Reject attempts to put versioned files into .git, .got, or .cvg directories.
Problem spotted by Runxi Yu and reported to me privately on IRC.
This has a slight security impact if users are not careful.
There is no way to directly execute arbitrary code by changing meta-data
in .got or .cvg. But if a user can be tricked into checking out a bad .got
directory then a malicious got.conf file could be installed which sets
the default "origin" remote to a malicious server. A man-in-the-middle
attack becomes possible if the user fetches from the malicious remote
server and accepts the wrong SSH host key without verification.
And I think we need to reject versioned files in .git as well, mostly to
avoid exposing Git users to bad settings used by Git clients started in a
got work tree for some reason.
ok?
M got/got.1 | 1+ 0-
M lib/fileindex.c | 40+ 1-
M lib/got_lib_fileindex.h | 2+ 0-
M lib/worktree.c | 8+ 0-
M regress/cmdline/add.sh | 91+ 0-
M regress/cmdline/checkout.sh | 50+ 0-
6 files changed, 192 insertions(+), 1 deletion(-)
commit - d8352339234706d4918c7b2c799bea32615bce6b
commit + bfb0c422d5b75974fffed99d1b7af8e98c8fd1eb
blob - 28b36f3cb34c0596473ec1cfe4251cca2302d9ba
blob + 64c195b4209cc4928aeeafbcbb22ab518ccab5b9
--- got/got.1
+++ got/got.1
@@ -670,6 +670,7 @@ Show the status of each affected file, using the follo
.Bl -column YXZ description
.It A Ta new file was added
.It E Ta file already exists in work tree's meta-data
+.It ? Ta file with a path outside of the work tree was skipped
.El
.Pp
If the
blob - dbef298e12a03f1a40e8adf8a5cffe916aca0681
blob + 61e4d2755fca1c170546162bd85ca7c301a13c1e
--- lib/fileindex.c
+++ lib/fileindex.c
@@ -39,7 +39,16 @@
#include "got_lib_hash.h"
#include "got_lib_fileindex.h"
#include "got_lib_worktree.h"
+#include "got_lib_delta.h"
+#include "got_lib_object.h"
+#include "got_lib_pack.h"
+#include "got_lib_object_cache.h"
+#include "got_lib_repository.h"
+#ifndef nitems
+#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
/* got_fileindex_entry flags */
#define GOT_FILEIDX_F_PATH_LEN 0x00000fff
#define GOT_FILEIDX_F_STAGE 0x0000f000
@@ -158,11 +167,42 @@ got_fileindex_entry_mark_skipped(struct got_fileindex_
}
const struct got_error *
+got_fileindex_entry_relpath_allowed(const char *relpath, size_t relpath_len)
+{
+ const char *forbidden[] = {
+ GOT_GIT_DIR,
+ GOT_WORKTREE_GOT_DIR,
+ GOT_WORKTREE_CVG_DIR
+ };
+ size_t i;
+
+ for (i = 0; i < nitems(forbidden); i++) {
+ if (got_path_cmp(relpath, forbidden[i],
+ relpath_len, strlen(forbidden[i])) == 0 ||
+ got_path_is_child(relpath, forbidden[i],
+ strlen(forbidden[i]))) {
+ return got_error_fmt(GOT_ERR_BAD_PATH,
+ "path '%s' cannot be added to the file-index",
+ relpath);
+ }
+ }
+
+ return NULL;
+}
+
+const struct got_error *
got_fileindex_entry_alloc(struct got_fileindex_entry **ie,
const char *relpath)
{
+ const struct got_error *err;
size_t len;
+ len = strlen(relpath);
+
+ err = got_fileindex_entry_relpath_allowed(relpath, len);
+ if (err)
+ return err;
+
*ie = calloc(1, sizeof(**ie));
if (*ie == NULL)
return got_error_from_errno("calloc");
@@ -175,7 +215,6 @@ got_fileindex_entry_alloc(struct got_fileindex_entry *
return err;
}
- len = strlen(relpath);
if (len > GOT_FILEIDX_F_PATH_LEN)
len = GOT_FILEIDX_F_PATH_LEN;
(*ie)->flags |= len;
blob - ffe9361ee22cfae0355ac626b5e8b456f4408e7d
blob + a1cb25ef6b6fbcb873a249f0c7c073e56105accb
--- lib/got_lib_fileindex.h
+++ lib/got_lib_fileindex.h
@@ -124,6 +124,8 @@ const struct got_error *got_fileindex_entry_update(str
void got_fileindex_entry_mark_skipped(struct got_fileindex_entry *);
const struct got_error *got_fileindex_entry_alloc(struct got_fileindex_entry **,
const char *);
+const struct got_error *got_fileindex_entry_relpath_allowed(const char *,
+ size_t );
void got_fileindex_entry_free(struct got_fileindex_entry *);
struct got_fileindex *got_fileindex_alloc(enum got_hash_algorithm);
blob - eb978efbf47067ad8d8d95b3a1e5873bc0ad486a
blob + 84beca72bcf731e8bbe07af4536b44fde4c8f1c0
--- lib/worktree.c
+++ lib/worktree.c
@@ -1947,6 +1947,14 @@ update_blob(struct got_worktree *worktree,
int fd1 = -1, fd2 = -1;
int update_timestamps = 0;
+ err = got_fileindex_entry_relpath_allowed(path, strlen(path));
+ if (err) {
+ if (err->code != GOT_ERR_BAD_PATH)
+ return err;
+ return (*progress_cb)(progress_arg,
+ GOT_STATUS_UNVERSIONED, path);
+ }
+
if (asprintf(&ondisk_path, "%s/%s", worktree->root_path, path) == -1)
return got_error_from_errno("asprintf");
blob - ebba1caf39b7ef4c28dcefcc7bb43a6ec5597c76
blob + f6ffb7eca936a84b2f2bc78f0b4de1823df43800
--- regress/cmdline/add.sh
+++ regress/cmdline/add.sh
@@ -485,6 +485,95 @@ test_add_symlink() {
test_done "$testroot" "$ret"
}
+test_add_file_in_dotgit() {
+ local testroot=`test_init add_file_in_dotgit`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ mkdir -p $testroot/wt/.git
+ echo "new file" > $testroot/wt/.git/foo
+
+ (cd $testroot/wt && got add .git/foo \
+ > $testroot/stdout 2> $testroot/stderr)
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "got add succeeded unexpectedly" >&2
+ test_done "$testroot" 1
+ return 1
+ fi
+
+ echo -n > $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" 1
+ return 1
+ fi
+
+ cat > $testroot/stderr.expected <<EOF
+got: path '.git/foo' cannot be added to the file-index: bad path
+EOF
+ cmp -s $testroot/stderr.expected $testroot/stderr
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stderr.expected $testroot/stderr
+ test_done "$testroot" 1
+ return 1
+ fi
+
+ test_done "$testroot" "$ret"
+}
+
+test_add_file_in_dotgot() {
+ local testroot=`test_init add_file_in_dotgot`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "new file" > $testroot/wt/.got/foo
+
+ (cd $testroot/wt && got add .got/foo \
+ > $testroot/stdout 2> $testroot/stderr)
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "got add succeeded unexpectedly" >&2
+ test_done "$testroot" 1
+ return 1
+ fi
+
+ echo -n > $testroot/stdout.expected
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" 1
+ return 1
+ fi
+
+ cat > $testroot/stderr.expected <<EOF
+got: path '.got/foo' cannot be added to the file-index: bad path
+EOF
+ cmp -s $testroot/stderr.expected $testroot/stderr
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stderr.expected $testroot/stderr
+ test_done "$testroot" 1
+ return 1
+ fi
+
+ test_done "$testroot" "$ret"
+}
+
test_parseargs "$@"
run_test test_add_basic
run_test test_double_add
@@ -495,3 +584,5 @@ run_test test_add_force_delete_commit
run_test test_add_directory
run_test test_add_clashes_with_submodule
run_test test_add_symlink
+run_test test_add_file_in_dotgit
+run_test test_add_file_in_dotgot
blob - 8ccd872d89efa4e676629a263300d659c3dd6ceb
blob + 1ac42106f911a923120f43f0bf02eb7fd88cd57f
--- regress/cmdline/checkout.sh
+++ regress/cmdline/checkout.sh
@@ -1075,6 +1075,55 @@ test_checkout_commit_keywords() {
test_done "$testroot" "$ret"
}
+test_checkout_tree_with_dot_got() {
+ local testroot=`test_init checkout_tree_with_dot_got`
+
+ mkdir -p $testroot/repo/.got
+ echo 'foo' > $testroot/repo/.got/foo
+ git -C $testroot/repo add .got/foo
+ git_commit $testroot/repo -m "adding .got/foo"
+
+ local commit_id=`git_show_head $testroot/repo`
+
+ echo "? $testroot/wt/.got/foo" > $testroot/stdout.expected
+ echo "A $testroot/wt/alpha" >> $testroot/stdout.expected
+ echo "A $testroot/wt/beta" >> $testroot/stdout.expected
+ echo "A $testroot/wt/epsilon/zeta" >> $testroot/stdout.expected
+ echo "A $testroot/wt/gamma/delta" >> $testroot/stdout.expected
+ echo "Checked out refs/heads/master: $commit_id" \
+ >> $testroot/stdout.expected
+ echo "Now shut up and hack" >> $testroot/stdout.expected
+
+ got checkout $testroot/repo $testroot/wt > $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "alpha" > $testroot/content.expected
+ echo "beta" >> $testroot/content.expected
+ echo "zeta" >> $testroot/content.expected
+ echo "delta" >> $testroot/content.expected
+ cat $testroot/wt/alpha $testroot/wt/beta $testroot/wt/epsilon/zeta \
+ $testroot/wt/gamma/delta > $testroot/content
+
+ cmp -s $testroot/content.expected $testroot/content
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/content.expected $testroot/content
+ fi
+ test_done "$testroot" "$ret"
+}
+
test_parseargs "$@"
run_test test_checkout_basic
run_test test_checkout_dir_exists
@@ -1094,3 +1143,4 @@ run_test test_checkout_quiet
run_test test_checkout_umask
run_test test_checkout_ulimit_n
run_test test_checkout_commit_keywords
+run_test test_checkout_tree_with_dot_got
do not allow versioned files in meta-data directories