Skip to content

Commit 0ffb5a6

Browse files
bk2204gitster
authored andcommitted
Allow cloning from repositories owned by another user
Historically, Git has allowed users to clone from an untrusted repository, and we have documented that this is safe to do so: `upload-pack` tries to avoid any dangerous configuration options or hooks from the repository it's serving, making it safe to clone an untrusted directory and run commands on the resulting clone. However, this was broken by f4aa8c8 ("fetch/clone: detect dubious ownership of local repositories", 2024-04-10) in an attempt to make things more secure. That change resulted in a variety of problems when cloning locally and over SSH, but it did not change the stated security boundary. Because the security boundary has not changed, it is safe to adjust part of the code that patch introduced. To do that and restore the previous functionality, adjust enter_repo to take two flags instead of one. The two bits are - ENTER_REPO_STRICT: callers that require exact paths (as opposed to allowing known suffixes like ".git", ".git/.git" to be omitted) can set this bit. Corresponds to the "strict" parameter that the flags word replaces. - ENTER_REPO_ANY_OWNER_OK: callers that are willing to run without ownership check can set this bit. The former is --strict-paths option of "git daemon". The latter is set only by upload-pack, which honors the claimed security boundary. Note that local clones across ownership boundaries require --no-local so that upload-pack is used. Document this fact in the manual page and provide an example. This patch was based on one written by Junio C Hamano. Signed-off-by: Junio C Hamano <[email protected]>
1 parent 46698a8 commit 0ffb5a6

File tree

7 files changed

+49
-11
lines changed

7 files changed

+49
-11
lines changed

Documentation/git-clone.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ symbolic link, the clone will fail. This is a security measure to
6363
prevent the unintentional copying of files by dereferencing the symbolic
6464
links.
6565
+
66+
This option does not work with repositories owned by other users for security
67+
reasons, and `--no-local` must be specified for the clone to succeed.
68+
+
6669
*NOTE*: this operation can race with concurrent modification to the
6770
source repository, similar to running `cp -r src dst` while modifying
6871
`src`.
@@ -381,6 +384,12 @@ $ cd my-linux
381384
$ git clone --bare -l /home/proj/.git /pub/scm/proj.git
382385
------------
383386

387+
* Clone a local repository from a different user:
388+
+
389+
------------
390+
$ git clone --no-local /home/otheruser/proj.git /pub/scm/proj.git
391+
------------
392+
384393
CONFIGURATION
385394
-------------
386395

builtin/upload-pack.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix)
3434
N_("interrupt transfer after <n> seconds of inactivity")),
3535
OPT_END()
3636
};
37+
unsigned enter_repo_flags = ENTER_REPO_ANY_OWNER_OK;
3738

3839
packet_trace_identity("upload-pack");
3940
disable_replace_refs();
@@ -49,7 +50,9 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix)
4950

5051
dir = argv[0];
5152

52-
if (!enter_repo(dir, strict))
53+
if (strict)
54+
enter_repo_flags |= ENTER_REPO_STRICT;
55+
if (!enter_repo(dir, enter_repo_flags))
5356
die("'%s' does not appear to be a git repository", dir);
5457

5558
switch (determine_protocol_version_server()) {

daemon.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ static const char *path_ok(const char *directory, struct hostinfo *hi)
149149
size_t rlen;
150150
const char *path;
151151
const char *dir;
152+
unsigned enter_repo_flags;
152153

153154
dir = directory;
154155

@@ -239,14 +240,15 @@ static const char *path_ok(const char *directory, struct hostinfo *hi)
239240
dir = rpath;
240241
}
241242

242-
path = enter_repo(dir, strict_paths);
243+
enter_repo_flags = strict_paths ? ENTER_REPO_STRICT : 0;
244+
path = enter_repo(dir, enter_repo_flags);
243245
if (!path && base_path && base_path_relaxed) {
244246
/*
245247
* if we fail and base_path_relaxed is enabled, try without
246248
* prefixing the base path
247249
*/
248250
dir = directory;
249-
path = enter_repo(dir, strict_paths);
251+
path = enter_repo(dir, enter_repo_flags);
250252
}
251253

252254
if (!path) {

path.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -794,15 +794,15 @@ char *interpolate_path(const char *path, int real_home)
794794
* links. User relative paths are also returned as they are given,
795795
* except DWIM suffixing.
796796
*/
797-
const char *enter_repo(const char *path, int strict)
797+
const char *enter_repo(const char *path, unsigned flags)
798798
{
799799
static struct strbuf validated_path = STRBUF_INIT;
800800
static struct strbuf used_path = STRBUF_INIT;
801801

802802
if (!path)
803803
return NULL;
804804

805-
if (!strict) {
805+
if (!(flags & ENTER_REPO_STRICT)) {
806806
static const char *suffix[] = {
807807
"/.git", "", ".git/.git", ".git", NULL,
808808
};
@@ -846,7 +846,8 @@ const char *enter_repo(const char *path, int strict)
846846
if (!suffix[i])
847847
return NULL;
848848
gitfile = read_gitfile(used_path.buf);
849-
die_upon_dubious_ownership(gitfile, NULL, used_path.buf);
849+
if (!(flags & ENTER_REPO_ANY_OWNER_OK))
850+
die_upon_dubious_ownership(gitfile, NULL, used_path.buf);
850851
if (gitfile) {
851852
strbuf_reset(&used_path);
852853
strbuf_addstr(&used_path, gitfile);
@@ -857,7 +858,8 @@ const char *enter_repo(const char *path, int strict)
857858
}
858859
else {
859860
const char *gitfile = read_gitfile(path);
860-
die_upon_dubious_ownership(gitfile, NULL, path);
861+
if (!(flags & ENTER_REPO_ANY_OWNER_OK))
862+
die_upon_dubious_ownership(gitfile, NULL, path);
861863
if (gitfile)
862864
path = gitfile;
863865
if (chdir(path))

path.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,22 @@ int validate_headref(const char *ref);
184184
int adjust_shared_perm(const char *path);
185185

186186
char *interpolate_path(const char *path, int real_home);
187-
const char *enter_repo(const char *path, int strict);
187+
188+
/* The bits are as follows:
189+
*
190+
* - ENTER_REPO_STRICT: callers that require exact paths (as opposed
191+
* to allowing known suffixes like ".git", ".git/.git" to be
192+
* omitted) can set this bit.
193+
*
194+
* - ENTER_REPO_ANY_OWNER_OK: callers that are willing to run without
195+
* ownership check can set this bit.
196+
*/
197+
enum {
198+
ENTER_REPO_STRICT = (1<<0),
199+
ENTER_REPO_ANY_OWNER_OK = (1<<1),
200+
};
201+
202+
const char *enter_repo(const char *path, unsigned flags);
188203
const char *remove_leading_path(const char *in, const char *prefix);
189204
const char *relative_path(const char *in, const char *prefix, struct strbuf *sb);
190205
int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);

t/t0411-clone-from-partial.sh

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ test_expect_success 'local clone must not fetch from promisor remote and execute
2828
test_must_fail git clone \
2929
--upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \
3030
evil clone1 2>err &&
31-
test_grep "detected dubious ownership" err &&
3231
test_grep ! "fake-upload-pack running" err &&
3332
test_path_is_missing script-executed
3433
'
@@ -38,7 +37,6 @@ test_expect_success 'clone from file://... must not fetch from promisor remote a
3837
test_must_fail git clone \
3938
--upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \
4039
"file://$(pwd)/evil" clone2 2>err &&
41-
test_grep "detected dubious ownership" err &&
4240
test_grep ! "fake-upload-pack running" err &&
4341
test_path_is_missing script-executed
4442
'
@@ -48,7 +46,6 @@ test_expect_success 'fetch from file://... must not fetch from promisor remote a
4846
test_must_fail git fetch \
4947
--upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \
5048
"file://$(pwd)/evil" 2>err &&
51-
test_grep "detected dubious ownership" err &&
5249
test_grep ! "fake-upload-pack running" err &&
5350
test_path_is_missing script-executed
5451
'

t/t5605-clone-local.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,16 @@ test_expect_success 'cloning a local path with --no-local does not hardlink' '
153153
! repo_is_hardlinked force-nonlocal
154154
'
155155

156+
test_expect_success 'cloning a local path with --no-local from a different user succeeds' '
157+
git clone --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \
158+
--no-local a nonlocal-otheruser 2>err &&
159+
! repo_is_hardlinked nonlocal-otheruser &&
160+
# Verify that this is a git repository.
161+
git -C nonlocal-otheruser rev-parse --show-toplevel &&
162+
! test_grep "detected dubious ownership" err
163+
164+
'
165+
156166
test_expect_success 'cloning locally respects "-u" for fetching refs' '
157167
test_must_fail git clone --bare -u false a should_not_work.git
158168
'

0 commit comments

Comments
 (0)