Skip to contentSkip to conversation

fast-import: disallow "." and ".." path components

PATCH Superseded Elijah Newren via GitGitGadget 7 participants 14 messages Nov 25, 2024 – Nov 28, 2024 Documentation

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 fast-import: disallow "." and ".." path components

From: Elijah Newren <[email protected]>

If a user specified e.g.
M 100644 :1 ../some-file
then fast-import previously would happily create a git history where there is a tree in the top-level directory named "..", and with a file inside that directory named "some-file". The top-level ".." directory causes problems. While git checkout will die with errors and fsck will report hasDotdot problems, the user is going to have problems trying to remove the problematic file. Simply avoid creating this bad history in the first place.

---
fast-import: disallow "." and ".." path components

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1831%2Fnewren%2Fdisallow-dotdot-fast-import-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1831/newren/disallow-dotdot-fast-import-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1831

 builtin/fast-import.c  |  3 +++
 t/t9300-fast-import.sh | 20 ++++++++++++++++++++
 2 files changed, 23 insertions(+)
builtin/fast-import.c +3 -0
index 76d5c20f141..e0e0ca3657a 100644
@@ -1466,6 +1466,9 @@ static int tree_content_set(
 		root->tree = t = grow_tree_content(t, t->entry_count);
 	e = new_tree_entry();
 	e->name = to_atom(p, n);
+	if (!strcmp(e->name->str_dat, ".") || !strcmp(e->name->str_dat, "..")) {
+		die("path %s contains invalid component", p);
+	}
 	e->versions[0].mode = 0;
 	oidclr(&e->versions[0].oid, the_repository->hash_algo);
 	t->entries[t->entry_count++] = e;
t/t9300-fast-import.sh +20 -0
index 6224f54d4d2..caf3dc003a0 100755
@@ -522,6 +522,26 @@ test_expect_success 'B: fail on invalid committer (5)' '
 	test_must_fail git fast-import <input
 '
 
+test_expect_success 'B: fail on invalid file path' '
+	cat >input <<-INPUT_END &&
+	blob
+	mark :1
+	data <<EOF
+	File contents
+	EOF
+
+	commit refs/heads/badpath
+	committer Name <email> $GIT_COMMITTER_DATE
+	data <<COMMIT
+	Commit Message
+	COMMIT
+	M 100644 :1 ../invalid-path
+	INPUT_END
+
+	test_when_finished "git update-ref -d refs/heads/badpath" &&
+	test_must_fail git fast-import <input
+'
+
 ###
 ### series C
 ###

base-commit: 04eaff62f286226f501dd21f069e0e257aee11a6

Signature · 1 line
gitgitgadget
#1 lore raw
Quoted 14 lines · Elijah Newren via GitGitGadget

If a user specified e.g.
M 100644 :1 ../some-file
then fast-import previously would happily create a git history where there is a tree in the top-level directory named "..", and with a file inside that directory named "some-file". The top-level ".." directory causes problems. While git checkout will die with errors and fsck will report hasDotdot problems, the user is going to have problems trying to remove the problematic file. Simply avoid creating this bad history in the first place.

---

builtin/fast-import.c static int tree_content_set( · line 1469

Quoted 4 lines
builtin/fast-import.c +3 -0
@@ -1466,6 +1466,9 @@ static int tree_content_set(
        e->name = to_atom(p, n);
+       if (!strcmp(e->name->str_dat, ".") || !strcmp(e->name->str_dat, "..")) {
+               die("path %s contains invalid component", p);
+       }

Probably not worth a reroll, but is_dot_or_dotdot() might be usable here.

(And -- style nit -- the braces could be dropped.)

#2 lore raw
Quoted 24 lines · Eric Sunshine
Quoted 14 lines · Elijah Newren via GitGitGadget

If a user specified e.g.
M 100644 :1 ../some-file
then fast-import previously would happily create a git history where there is a tree in the top-level directory named "..", and with a file inside that directory named "some-file". The top-level ".." directory causes problems. While git checkout will die with errors and fsck will report hasDotdot problems, the user is going to have problems trying to remove the problematic file. Simply avoid creating this bad history in the first place.

---

builtin/fast-import.c static int tree_content_set( · line 1469

Quoted 4 lines
builtin/fast-import.c +3 -0
@@ -1466,6 +1466,9 @@ static int tree_content_set(
        e->name = to_atom(p, n);
+       if (!strcmp(e->name->str_dat, ".") || !strcmp(e->name->str_dat, "..")) {
+               die("path %s contains invalid component", p);
+       }

Probably not worth a reroll, but is_dot_or_dotdot() might be usable here.

Quoted 1 line

(And -- style nit -- the braces could be dropped.)

Good catches, thanks. I think they are worth a reroll; I'll send one in.

#3 lore raw

PATCH v2 1/1 fast-import: disallow "." and ".." path components

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

If a user specified e.g.
M 100644 :1 ../some-file
then fast-import previously would happily create a git history where there is a tree in the top-level directory named "..", and with a file inside that directory named "some-file". The top-level ".." directory causes problems. While git checkout will die with errors and fsck will report hasDotdot problems, the user is going to have problems trying to remove the problematic file. Simply avoid creating this bad history in the first place.

---
fast-import: disallow "." and ".." path components

Changes since v1:

* make use of is_dot_or_dotdot() from dir.h
* fix style issue

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1831%2Fnewren%2Fdisallow-dotdot-fast-import-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1831/newren/disallow-dotdot-fast-import-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/1831

Range-diff vs v1:

1: 86ea3df0351 ! 1: 447b6794a4a fast-import: disallow "." and ".." path components

     @@ builtin/fast-import.c: static int tree_content_set(
       		root->tree = t = grow_tree_content(t, t->entry_count);
       	e = new_tree_entry();
       	e->name = to_atom(p, n);
     -+	if (!strcmp(e->name->str_dat, ".") || !strcmp(e->name->str_dat, "..")) {
     ++	if (is_dot_or_dotdot(e->name->str_dat))
      +		die("path %s contains invalid component", p);
     -+	}
       	e->versions[0].mode = 0;
       	oidclr(&e->versions[0].oid, the_repository->hash_algo);
       	t->entries[t->entry_count++] = e;
 builtin/fast-import.c  |  2 ++
 t/t9300-fast-import.sh | 20 ++++++++++++++++++++
 2 files changed, 22 insertions(+)
builtin/fast-import.c +2 -0
index 76d5c20f141..995ef76f9d6 100644
@@ -1466,6 +1466,8 @@ static int tree_content_set(
 		root->tree = t = grow_tree_content(t, t->entry_count);
 	e = new_tree_entry();
 	e->name = to_atom(p, n);
+	if (is_dot_or_dotdot(e->name->str_dat))
+		die("path %s contains invalid component", p);
 	e->versions[0].mode = 0;
 	oidclr(&e->versions[0].oid, the_repository->hash_algo);
 	t->entries[t->entry_count++] = e;
t/t9300-fast-import.sh +20 -0
index 6224f54d4d2..caf3dc003a0 100755
@@ -522,6 +522,26 @@ test_expect_success 'B: fail on invalid committer (5)' '
 	test_must_fail git fast-import <input
 '
 
+test_expect_success 'B: fail on invalid file path' '
+	cat >input <<-INPUT_END &&
+	blob
+	mark :1
+	data <<EOF
+	File contents
+	EOF
+
+	commit refs/heads/badpath
+	committer Name <email> $GIT_COMMITTER_DATE
+	data <<COMMIT
+	Commit Message
+	COMMIT
+	M 100644 :1 ../invalid-path
+	INPUT_END
+
+	test_when_finished "git update-ref -d refs/heads/badpath" &&
+	test_must_fail git fast-import <input
+'
+
 ###
 ### series C
 ###

base-commit: 04eaff62f286226f501dd21f069e0e257aee11a6

Signature · 1 line
gitgitgadget
#4 lore raw
Quoted 11 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

If a user specified e.g.
M 100644 :1 ../some-file
then fast-import previously would happily create a git history where there is a tree in the top-level directory named "..", and with a file inside that directory named "some-file". The top-level ".." directory causes problems. While git checkout will die with errors and fsck will report hasDotdot problems, the user is going to have problems trying to remove the problematic file. Simply avoid creating this bad history in the first place.

Makes sense.

More generally this made me wonder whether we should maybe extract some bits out of "fsck.c" so that we don't have to duplicate the checks done there in git-fast-import(1). This would for example include checks for ".git" and its HFS/NTFS variants as well as tree entry length checks for names longer than 4096 characters.

This of course does not have to be part of your patch, which looks good to me.

Thanks!

Patrick

#7 lore raw
In reply to Elijah Newren via GitGitGadget

Hi. I see that this is in next now so the following might be irrelevant.

builtin/fast-import.c static int tree_content_set( · line 1470

Quoted 12 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>
[...]

builtin/fast-import.c +2 -0
index 76d5c20f141..995ef76f9d6 100644
@@ -1466,6 +1466,8 @@ static int tree_content_set(
 		root->tree = t = grow_tree_content(t, t->entry_count);
 	e = new_tree_entry();
 	e->name = to_atom(p, n);
+	if (is_dot_or_dotdot(e->name->str_dat))
+		die("path %s contains invalid component", p);

Nit: single-quoting the path seems more common:

    $ git grep "\"path '%s'" ':!po/' | wc -l
    17
    $ git grep "\"path %s" ':!po/' | wc -l
    4
Quoted 15 lines
builtin/fast-import.c +0 -0
 	e->versions[0].mode = 0;
 	oidclr(&e->versions[0].oid, the_repository->hash_algo);
 	t->entries[t->entry_count++] = e;
t/t9300-fast-import.sh +4 -0
index 6224f54d4d2..caf3dc003a0 100755
@@ -522,6 +522,26 @@ test_expect_success 'B: fail on invalid committer (5)' '
 	test_must_fail git fast-import <input
 '
 
+test_expect_success 'B: fail on invalid file path' '
+	cat >input <<-INPUT_END &&
+	blob
+	mark :1

t/t9300-fast-import.sh test_expect_success 'B: fail on invalid committer (5)' ' · line 538

Quoted 10 lines
t/t9300-fast-import.sh +10 -0
+	data <<EOF
+	File contents
+	EOF
+
+	commit refs/heads/badpath
+	committer Name <email> $GIT_COMMITTER_DATE
+	data <<COMMIT
+	Commit Message
+	COMMIT
+	M 100644 :1 ../invalid-path

Maybe the test could be parameterized so that both .. and . can be tested? Like in test_path_eol_success.

Signature · 1 line
Kristoffer Haugsbakk
#8 lore raw

Re* [PATCH v2] fast-import: disallow "." and ".." path components

Quoted 9 lines · Kristoffer Haugsbakk

Review

Quoted 2 lines
(quoted diff) +2 -0
+	if (is_dot_or_dotdot(e->name->str_dat))
+		die("path %s contains invalid component", p);

Nit: single-quoting the path seems more common:

    $ git grep "\"path '%s'" ':!po/' | wc -l
    17
    $ git grep "\"path %s" ':!po/' | wc -l
    4

Ah, I missed that one. Thanks for catching.

We probably should write it down.

--- >8 ---
[PATCH] CodingGuidelines: a handful of error message guidelines

It is more efficient to have something in the coding guidelines document to point at, when we want to review and comment on a new message in the codebase to make sure it "fits" in the set of existing messages.

Let's write down established best practice we are aware of.

---

* I am writing what I think is the established practice from memory; clarifications, corrections, and additions are all welcome.

 Documentation/CodingGuidelines | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)
Documentation/CodingGuidelines +9 -3
index 87904791cb..0444391983 100644
@@ -703,16 +703,22 @@ Program Output
 
 Error Messages
 
- - Do not end error messages with a full stop.
+ - Do not end a single-sentence error message with a full stop.
 
  - Do not capitalize the first word, only because it is the first word
-   in the message ("unable to open %s", not "Unable to open %s").  But
+   in the message ("unable to open '%s'", not "Unable to open '%s'").  But
    "SHA-3 not supported" is fine, because the reason the first word is
    capitalized is not because it is at the beginning of the sentence,
    but because the word would be spelled in capital letters even when
    it appeared in the middle of the sentence.
 
- - Say what the error is first ("cannot open %s", not "%s: cannot open")
+ - Say what the error is first ("cannot open '%s'", not "%s: cannot open").
+
+ - Enclose the subject of an error inside a pair of single quotes,
+   e.g. `die(_("unable to open '%s'"), path)`.
+
+ - Unless there is a compelling reason not to, error messages should
+   be marked for `_("translation")`.
 
 
 Externally Visible Names
#5 lore raw
In reply to Patrick Steinhardt
Quoted 13 lines · Patrick Steinhardt
Quoted 11 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

If a user specified e.g.
M 100644 :1 ../some-file
then fast-import previously would happily create a git history where there is a tree in the top-level directory named "..", and with a file inside that directory named "some-file". The top-level ".." directory causes problems. While git checkout will die with errors and fsck will report hasDotdot problems, the user is going to have problems trying to remove the problematic file. Simply avoid creating this bad history in the first place.

Quoted 7 lines

Makes sense.

More generally this made me wonder whether we should maybe extract some bits out of "fsck.c" so that we don't have to duplicate the checks done there in git-fast-import(1). This would for example include checks for ".git" and its HFS/NTFS variants as well as tree entry length checks for names longer than 4096 characters.

I had the same thought, but I think the right code to be using is verify_path(). That's what ultimately is used to let names into the index from trees, from update-index, or from other tools like git-apply.

So I'd consider that authoritative, and fsck is mostly trying to follow those rules while looking at only a single tree at a time. But fast-import should have the whole path as a string, just like the index code does).

-Peff

#9 lore raw

Re: Re* [PATCH v2] fast-import: disallow "." and ".." path components

In reply to Junio C Hamano
Quoted 21 lines · Junio C Hamano

[PATCH] CodingGuidelines: a handful of error message guidelines

It is more efficient to have something in the coding guidelines document to point at, when we want to review and comment on a new message in the codebase to make sure it "fits" in the set of existing messages.

Let's write down established best practice we are aware of.

---

w/Documentation/CodingGuidelines renamed from c/Documentation/CodingGuidelines +2 -2
@@ -703,16 +703,22 @@ Program Output
 Error Messages
 
- - Do not end error messages with a full stop.
+ - Do not end a single-sentence error message with a full stop.
 
  - Do not capitalize the first word, only because it is the first word
-   in the message ("unable to open %s", not "Unable to open %s").  But
+   in the message ("unable to open '%s'", not "Unable to open '%s'").  But

w/Documentation/CodingGuidelines Program Output · line 717

Quoted 10 lines
w/Documentation/CodingGuidelines renamed from c/Documentation/CodingGuidelines +4 -1
    "SHA-3 not supported" is fine, because the reason the first word is
    capitalized is not because it is at the beginning of the sentence,
    but because the word would be spelled in capital letters even when
    it appeared in the middle of the sentence.
 
- - Say what the error is first ("cannot open %s", not "%s: cannot open")
+ - Say what the error is first ("cannot open '%s'", not "%s: cannot open").
+
+ - Enclose the subject of an error inside a pair of single quotes,
+   e.g. `die(_("unable to open '%s'"), path)`.

These changes all seem fine.

w/Documentation/CodingGuidelines Program Output · line 719

Quoted 2 lines
w/Documentation/CodingGuidelines renamed from c/Documentation/CodingGuidelines +2 -0
+ - Unless there is a compelling reason not to, error messages should
+   be marked for `_("translation")`.

We might want to spell this out more fully, such as stating that messages from porcelain commands should be marked for translation, but messages in plumbing should not. Also, perhaps mention explicitly that BUG("message") should not be marked for translation since they are intended to be read by Git developers, not by end-users.

#6 lore raw
In reply to Jeff King
Quoted 3 lines · Jeff King

I had the same thought, but I think the right code to be using is verify_path(). That's what ultimately is used to let names into the index from trees, from update-index, or from other tools like git-apply.

Yeah, I agree that is the right helper to use.

#10 lore raw

PATCH v2 1/1 CodingGuidelines: a handful of error message guidelines

In reply to Junio C Hamano

Taking input from comments by Eric (thanks) on the previous round, this iteration adds a bit more about Porcelain/Plumbing and BUG().

diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index 71e4742fd5..2b8f99f333 100644
--- a/Documentation/CodingGuidelines
+++ b/Documentation/CodingGuidelines
@@ -703,8 +703,15 @@ Error Messages
- Enclose the subject of an error inside a pair of single quotes, e.g. die(_("unable to open '%s'"), path).

- - Unless there is a compelling reason not to, error messages should
- be marked for _("translation").
+ - Unless there is a compelling reason not to, error messages from the
+ Porcelain command should be marked for _("translation").
+
+ - Error messages from the plumbing commands are sometimes meant for
+ machine consumption and should not be marked for _("translation")
+ to keep them 'grep'-able.
+
+ - BUG("message") are for communicating the specific error to
+ developers, and not to be translated.

Externally Visible Names

--- >8 ---
It is more efficient to have something in the coding guidelines document to point at, when we want to review and comment on a new message in the codebase to make sure it "fits" in the set of existing messages.

Let's write down established best practice we are aware of.

---
 Documentation/CodingGuidelines | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)
Documentation/CodingGuidelines +16 -3
index 3263245b03..2b8f99f333 100644
@@ -689,16 +689,29 @@ Program Output
 
 Error Messages
 
- - Do not end error messages with a full stop.
+ - Do not end a single-sentence error message with a full stop.
 
  - Do not capitalize the first word, only because it is the first word
-   in the message ("unable to open %s", not "Unable to open %s").  But
+   in the message ("unable to open '%s'", not "Unable to open '%s'").  But
    "SHA-3 not supported" is fine, because the reason the first word is
    capitalized is not because it is at the beginning of the sentence,
    but because the word would be spelled in capital letters even when
    it appeared in the middle of the sentence.
 
- - Say what the error is first ("cannot open %s", not "%s: cannot open")
+ - Say what the error is first ("cannot open '%s'", not "%s: cannot open").
+
+ - Enclose the subject of an error inside a pair of single quotes,
+   e.g. `die(_("unable to open '%s'"), path)`.
+
+ - Unless there is a compelling reason not to, error messages from the
+   Porcelain command should be marked for `_("translation")`.
+
+ - Error messages from the plumbing commands are sometimes meant for
+   machine consumption and should not be marked for `_("translation")`
+   to keep them 'grep'-able.
+
+ - BUG("message") are for communicating the specific error to
+   developers, and not to be translated.
 
 
 Externally Visible Names
Signature · 1 line
2.47.1-499-g8536fed62d
#11 lore raw

Re: [PATCH v2] CodingGuidelines: a handful of error message guidelines

Quoted 12 lines · Junio C Hamano

It is more efficient to have something in the coding guidelines document to point at, when we want to review and comment on a new message in the codebase to make sure it "fits" in the set of existing messages.

Let's write down established best practice we are aware of.

---

Documentation/CodingGuidelines Program Output · line 697

Quoted 10 lines
Documentation/CodingGuidelines +7 -1
@@ -689,16 +689,29 @@ Program Output
 Error Messages
 
- - Say what the error is first ("cannot open %s", not "%s: cannot open")
+ - Say what the error is first ("cannot open '%s'", not "%s: cannot open").
+
+ - Enclose the subject of an error inside a pair of single quotes,
+   e.g. `die(_("unable to open '%s'"), path)`.
+
+ - Unless there is a compelling reason not to, error messages from the
+   Porcelain command should be marked for `_("translation")`.

Here you capitalize "Porcelain" but below, "plumbing" is all lowercase.

Documentation/CodingGuidelines Program Output · line 711

Quoted 3 lines
Documentation/CodingGuidelines +3 -0
+ - Error messages from the plumbing commands are sometimes meant for
+   machine consumption and should not be marked for `_("translation")`
+   to keep them 'grep'-able.

Using the same example, _("translation"), for both the "should be" and "should not be" cases may very well confuse readers. (It certainly confused me.) Perhaps mirroring the example of an item earlier in the list would be clearer:

- Unless there is a compelling reason not to, error messages from porcelain commands should be marked for translation, e.g. die(_("bad revision"))

- Error messages from plumbing commands are sometimes meant for machine consumption, thus should not be marked for translation, e.g. die("bad revision")

Documentation/CodingGuidelines Program Output · line 714

Quoted 2 lines
Documentation/CodingGuidelines +2 -0
+ - BUG("message") are for communicating the specific error to
+   developers, and not to be translated.

Okay, although could be slightly more explicit:

- BUG("message") is for communicating a specific failure to developers, not end-users, thus should not be translated.

#12 lore raw

Re: [PATCH v2] CodingGuidelines: a handful of error message guidelines

Quoted 4 lines · Eric Sunshine
Quoted 2 lines

+ - Unless there is a compelling reason not to, error messages from the
+ Porcelain command should be marked for _("translation").

Here you capitalize "Porcelain" but below, "plumbing" is all lowercase.

;-) I think that is how we spell them in our documentation when we contrast them against each other.

Quoted 9 lines

Documentation/CodingGuidelines Program Output · line 711

Quoted 3 lines

+ - Error messages from the plumbing commands are sometimes meant for

Documentation/CodingGuidelines +2 -0
+   machine consumption and should not be marked for `_("translation")`
+   to keep them 'grep'-able.

Using the same example, _("translation"), for both the "should be" and "should not be" cases may very well confuse readers. (It certainly confused me.) Perhaps mirroring the example of an item earlier in the list would be clearer:

Quoted 7 lines

- Unless there is a compelling reason not to, error messages from porcelain commands should be marked for translation, e.g. die(_("bad revision"))

- Error messages from plumbing commands are sometimes meant for machine consumption, thus should not be marked for translation, e.g. die("bad revision")

Thanks, that is much better. Let me steal it verbatim in the hopefully final reroll.

Quoted 7 lines
Quoted 2 lines

+ - BUG("message") are for communicating the specific error to
+ developers, and not to be translated.

Okay, although could be slightly more explicit:

- BUG("message") is for communicating a specific failure to developers, not end-users, thus should not be translated.

The way I read your rewrite is that the "communitation" mentioned is between the program and the user who saw the message. I wanted to say that the message is seen first by an end-user, and then is communicated to developers. And not translating is one way to make sure the message is not mangled, and stays grep-able, during the game of telephone.

Would this work better?

- In order to help the user who saw BUG("message") to accurately communicate it to developers, do not mark them for translation.

Thanks.

#13 lore raw

Re: [PATCH v2] CodingGuidelines: a handful of error message guidelines

Quoted 7 lines · Junio C Hamano
Quoted 3 lines · Eric Sunshine
Quoted 1 line

+ Porcelain command should be marked for _("translation").

Here you capitalize "Porcelain" but below, "plumbing" is all lowercase.

;-) I think that is how we spell them in our documentation when we contrast them against each other.

I must not have been paying close enough attention.

Quoted 15 lines
Quoted 7 lines
Quoted 2 lines

+ - BUG("message") are for communicating the specific error to
+ developers, and not to be translated.

Okay, although could be slightly more explicit:

- BUG("message") is for communicating a specific failure to developers, not end-users, thus should not be translated.

The way I read your rewrite is that the "communitation" mentioned is between the program and the user who saw the message. I wanted to say that the message is seen first by an end-user, and then is communicated to developers. And not translating is one way to make sure the message is not mangled, and stays grep-able, during the game of telephone.

Quoted 4 lines

Would this work better?

- In order to help the user who saw BUG("message") to accurately communicate it to developers, do not mark them for translation.

Let's not spend too much time fine-tuning this. I found your original clearer than this rewrite. It was just the "and not to be" bit that made my reading hiccup. Taking your original but substituting in "thus" may help:

- BUG("message") are for communicating the specific error to developers, thus should not be translated.