Skip to content

Commit fb62e3f

Browse files
shejialuogitster
authored andcommitted
ref: add symref content check for files backend
We have already introduced the checks for regular refs. There is no need to check the consistency of the target which the symref points to. Instead, we just need to check the content of the symref itself. A regular file is accepted as a textual symref if it begins with "ref:", followed by zero or more whitespaces, followed by the full refname, followed only by whitespace characters. We always write a single SP after "ref:" and a single LF after the refname, but third-party reimplementations of Git may have taken advantage of the looser syntax. Put it more specific, we accept the following contents of the symref: 1. "ref: refs/heads/master " 2. "ref: refs/heads/master \n \n" 3. "ref: refs/heads/master\n\n" Thus, we could reuse "refMissingNewline" and "trailingRefContent" FSCK_INFOs to do the same retroactive tightening as we introduce for regular references. But we do not allow any other trailing garbage. The followings are bad symref contents which will be reported as fsck error by "git-fsck(1)". 1. "ref: refs/heads/master garbage\n" 2. "ref: refs/heads/master \n\n\n garbage " And we introduce a new "badReferentName(ERROR)" fsck message to report above errors to the user. In order to check the content of the symref, create a function "files_fsck_symref_target". It will first check whether the "referent" is under the "refs/" directory, if not, we will report "escapeReferent" fsck error message to notify the user this situation. Then, we will first check whether the symref content misses the newline by peeking the last byte of the "referent" to see whether it is '\n'. And we will remember the untrimmed length of the "referent" and call "strbuf_rtrim()" on "referent". Then, we will call "check_refname_format" to check whether the trimmed referent format is valid. If not, we will report to the user that the symref points to referent which has invalid format. If it is valid, we will compare the untrimmed length and trimmed length, if they are not the same, we need to warn the user there is some trailing garbage in the symref content. At last, we need to check whether the referent is a directory. We cannot distinguish whether a given reference like "refs/heads/a" is a file or a directory by using "check_refname_format". We have already checked bad file type when iterating the "refs/" directory but we ignore the directory. Thus, we need to explicitly add check here. Mentored-by: Patrick Steinhardt <[email protected]> Mentored-by: Karthik Nayak <[email protected]> Signed-off-by: shejialuo <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 102192a commit fb62e3f

File tree

4 files changed

+210
-0
lines changed

4 files changed

+210
-0
lines changed

Documentation/fsck-msgids.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@
2828
`badRefName`::
2929
(ERROR) A ref has an invalid format.
3030

31+
`badReferentFiletype`::
32+
(ERROR) The referent of a symref has a bad file type.
33+
34+
`badReferentName`::
35+
(ERROR) The referent name of a symref is invalid.
36+
3137
`badTagName`::
3238
(INFO) A tag has an invalid format.
3339

@@ -49,6 +55,9 @@
4955
`emptyName`::
5056
(WARN) A path contains an empty name.
5157

58+
`escapeReferent`::
59+
(ERROR) The referent of a symref is outside the "ref" directory.
60+
5261
`extraHeaderEntry`::
5362
(IGNORE) Extra headers found after `tagger`.
5463

fsck.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,14 @@ enum fsck_msg_type {
3434
FUNC(BAD_REF_CONTENT, ERROR) \
3535
FUNC(BAD_REF_FILETYPE, ERROR) \
3636
FUNC(BAD_REF_NAME, ERROR) \
37+
FUNC(BAD_REFERENT_FILETYPE, ERROR) \
38+
FUNC(BAD_REFERENT_NAME, ERROR) \
3739
FUNC(BAD_TIMEZONE, ERROR) \
3840
FUNC(BAD_TREE, ERROR) \
3941
FUNC(BAD_TREE_SHA1, ERROR) \
4042
FUNC(BAD_TYPE, ERROR) \
4143
FUNC(DUPLICATE_ENTRIES, ERROR) \
44+
FUNC(ESCAPE_REFERENT, ERROR) \
4245
FUNC(MISSING_AUTHOR, ERROR) \
4346
FUNC(MISSING_COMMITTER, ERROR) \
4447
FUNC(MISSING_EMAIL, ERROR) \

refs/files-backend.c

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3434,11 +3434,80 @@ typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store,
34343434
const char *refs_check_dir,
34353435
struct dir_iterator *iter);
34363436

3437+
/*
3438+
* Check the symref "referent" and "referent_path". For textual symref,
3439+
* "referent" would be the content after "refs:".
3440+
*/
3441+
static int files_fsck_symref_target(struct fsck_options *o,
3442+
struct fsck_ref_report *report,
3443+
struct strbuf *referent,
3444+
struct strbuf *referent_path)
3445+
{
3446+
size_t len = referent->len - 1;
3447+
struct stat st;
3448+
int ret = 0;
3449+
3450+
if (!starts_with(referent->buf, "refs/")) {
3451+
ret = fsck_report_ref(o, report,
3452+
FSCK_MSG_ESCAPE_REFERENT,
3453+
"points to ref outside the refs directory");
3454+
goto out;
3455+
}
3456+
3457+
if (referent->buf[referent->len - 1] != '\n') {
3458+
ret = fsck_report_ref(o, report,
3459+
FSCK_MSG_REF_MISSING_NEWLINE,
3460+
"missing newline");
3461+
len++;
3462+
}
3463+
3464+
strbuf_rtrim(referent);
3465+
if (check_refname_format(referent->buf, 0)) {
3466+
ret = fsck_report_ref(o, report,
3467+
FSCK_MSG_BAD_REFERENT_NAME,
3468+
"points to refname with invalid format");
3469+
goto out;
3470+
}
3471+
3472+
if (len != referent->len) {
3473+
ret = fsck_report_ref(o, report,
3474+
FSCK_MSG_TRAILING_REF_CONTENT,
3475+
"trailing garbage in ref");
3476+
}
3477+
3478+
/*
3479+
* Dangling symrefs are common and so we don't report them.
3480+
*/
3481+
if (lstat(referent_path->buf, &st)) {
3482+
if (errno != ENOENT) {
3483+
ret = error_errno(_("unable to stat '%s'"),
3484+
referent_path->buf);
3485+
}
3486+
goto out;
3487+
}
3488+
3489+
/*
3490+
* We cannot distinguish whether "refs/heads/a" is a directory or not by
3491+
* using "check_refname_format(referent->buf, 0)". Instead, we need to
3492+
* check the file type of the target.
3493+
*/
3494+
if (S_ISDIR(st.st_mode)) {
3495+
ret = fsck_report_ref(o, report,
3496+
FSCK_MSG_BAD_REFERENT_FILETYPE,
3497+
"points to the directory");
3498+
goto out;
3499+
}
3500+
3501+
out:
3502+
return ret;
3503+
}
3504+
34373505
static int files_fsck_refs_content(struct ref_store *ref_store,
34383506
struct fsck_options *o,
34393507
const char *refs_check_dir,
34403508
struct dir_iterator *iter)
34413509
{
3510+
struct strbuf referent_path = STRBUF_INIT;
34423511
struct strbuf ref_content = STRBUF_INIT;
34433512
struct strbuf referent = STRBUF_INIT;
34443513
struct strbuf refname = STRBUF_INIT;
@@ -3484,12 +3553,24 @@ static int files_fsck_refs_content(struct ref_store *ref_store,
34843553
"trailing garbage in ref");
34853554
goto cleanup;
34863555
}
3556+
} else {
3557+
strbuf_addf(&referent_path, "%s/%s",
3558+
ref_store->gitdir, referent.buf);
3559+
/*
3560+
* the referent may contain the spaces and the newline, need to
3561+
* trim for path.
3562+
*/
3563+
strbuf_rtrim(&referent_path);
3564+
ret = files_fsck_symref_target(o, &report,
3565+
&referent,
3566+
&referent_path);
34873567
}
34883568

34893569
cleanup:
34903570
strbuf_release(&refname);
34913571
strbuf_release(&ref_content);
34923572
strbuf_release(&referent);
3573+
strbuf_release(&referent_path);
34933574
return ret;
34943575
}
34953576

t/t0602-reffiles-fsck.sh

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,4 +209,121 @@ test_expect_success 'regular ref content should be checked (aggregate)' '
209209
test_cmp expect sorted_err
210210
'
211211

212+
test_expect_success 'textual symref content should be checked (individual)' '
213+
test_when_finished "rm -rf repo" &&
214+
git init repo &&
215+
branch_dir_prefix=.git/refs/heads &&
216+
tag_dir_prefix=.git/refs/tags &&
217+
cd repo &&
218+
test_commit default &&
219+
mkdir -p "$branch_dir_prefix/a/b" &&
220+
221+
printf "ref: refs/heads/branch\n" >$branch_dir_prefix/branch-good &&
222+
git refs verify 2>err &&
223+
rm $branch_dir_prefix/branch-good &&
224+
test_must_be_empty err &&
225+
226+
printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline-1 &&
227+
git refs verify 2>err &&
228+
cat >expect <<-EOF &&
229+
warning: refs/heads/branch-no-newline-1: refMissingNewline: missing newline
230+
EOF
231+
rm $branch_dir_prefix/branch-no-newline-1 &&
232+
test_cmp expect err &&
233+
234+
printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
235+
git refs verify 2>err &&
236+
cat >expect <<-EOF &&
237+
warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: missing newline
238+
warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: trailing garbage in ref
239+
EOF
240+
rm $branch_dir_prefix/a/b/branch-trailing-1 &&
241+
test_cmp expect err &&
242+
243+
printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
244+
git refs verify 2>err &&
245+
cat >expect <<-EOF &&
246+
warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: trailing garbage in ref
247+
EOF
248+
rm $branch_dir_prefix/a/b/branch-trailing-2 &&
249+
test_cmp expect err &&
250+
251+
printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
252+
git refs verify 2>err &&
253+
cat >expect <<-EOF &&
254+
warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: trailing garbage in ref
255+
EOF
256+
rm $branch_dir_prefix/a/b/branch-trailing-3 &&
257+
test_cmp expect err &&
258+
259+
printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
260+
git refs verify 2>err &&
261+
cat >expect <<-EOF &&
262+
warning: refs/heads/a/b/branch-complicated: refMissingNewline: missing newline
263+
warning: refs/heads/a/b/branch-complicated: trailingRefContent: trailing garbage in ref
264+
EOF
265+
rm $branch_dir_prefix/a/b/branch-complicated &&
266+
test_cmp expect err &&
267+
268+
printf "ref: refs/heads/.branch\n" >$branch_dir_prefix/branch-bad-1 &&
269+
test_must_fail git refs verify 2>err &&
270+
cat >expect <<-EOF &&
271+
error: refs/heads/branch-bad-1: badReferentName: points to refname with invalid format
272+
EOF
273+
rm $branch_dir_prefix/branch-bad-1 &&
274+
test_cmp expect err &&
275+
276+
printf "ref: reflogs/heads/main\n" >$branch_dir_prefix/branch-bad-2 &&
277+
test_must_fail git refs verify 2>err &&
278+
cat >expect <<-EOF &&
279+
error: refs/heads/branch-bad-2: escapeReferent: points to ref outside the refs directory
280+
EOF
281+
rm $branch_dir_prefix/branch-bad-2 &&
282+
test_cmp expect err &&
283+
284+
printf "ref: refs/heads/a\n" >$branch_dir_prefix/branch-bad-3 &&
285+
test_must_fail git refs verify 2>err &&
286+
cat >expect <<-EOF &&
287+
error: refs/heads/branch-bad-3: badReferentFiletype: points to the directory
288+
EOF
289+
rm $branch_dir_prefix/branch-bad-3 &&
290+
test_cmp expect err
291+
'
292+
293+
test_expect_success 'textual symref content should be checked (aggregate)' '
294+
test_when_finished "rm -rf repo" &&
295+
git init repo &&
296+
branch_dir_prefix=.git/refs/heads &&
297+
tag_dir_prefix=.git/refs/tags &&
298+
cd repo &&
299+
test_commit default &&
300+
mkdir -p "$branch_dir_prefix/a/b" &&
301+
302+
printf "ref: refs/heads/branch\n" >$branch_dir_prefix/branch-good &&
303+
printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline-1 &&
304+
printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 &&
305+
printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 &&
306+
printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 &&
307+
printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated &&
308+
printf "ref: refs/heads/.branch\n" >$branch_dir_prefix/branch-bad-1 &&
309+
printf "ref: reflogs/heads/main\n" >$branch_dir_prefix/branch-bad-2 &&
310+
printf "ref: refs/heads/a\n" >$branch_dir_prefix/branch-bad-3 &&
311+
312+
test_must_fail git refs verify 2>err &&
313+
cat >expect <<-EOF &&
314+
error: refs/heads/branch-bad-1: badReferentName: points to refname with invalid format
315+
error: refs/heads/branch-bad-2: escapeReferent: points to ref outside the refs directory
316+
error: refs/heads/branch-bad-3: badReferentFiletype: points to the directory
317+
warning: refs/heads/a/b/branch-complicated: refMissingNewline: missing newline
318+
warning: refs/heads/a/b/branch-complicated: trailingRefContent: trailing garbage in ref
319+
warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: missing newline
320+
warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: trailing garbage in ref
321+
warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: trailing garbage in ref
322+
warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: trailing garbage in ref
323+
warning: refs/heads/branch-no-newline-1: refMissingNewline: missing newline
324+
EOF
325+
sort err >sorted_err &&
326+
test_cmp expect sorted_err
327+
'
328+
212329
test_done

0 commit comments

Comments
 (0)