Skip to content

Commit 7968e90

Browse files
committed
refdb_fs: properly parse corrupted reflogs
In previous versions, libgit2 could be coerced into writing reflog messages with embedded newlines into the reflog by using `git_stash_save` with a message containing newlines. While the root cause is fixed now, it was noticed that upstream git is in fact able to read such corrupted reflog messages just fine. Make the reflog parser more lenient in order to just skip over malformatted reflog lines to bring us in line with git. This requires us to change an existing test that verified that we do indeed _fail_ to parse such logs.
1 parent 8532ed1 commit 7968e90

File tree

2 files changed

+29
-28
lines changed

2 files changed

+29
-28
lines changed

src/refdb_fs.c

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,32 +1653,31 @@ static int reflog_alloc(git_reflog **reflog, const char *name)
16531653
static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size)
16541654
{
16551655
git_parse_ctx parser = GIT_PARSE_CTX_INIT;
1656-
git_reflog_entry *entry = NULL;
16571656

16581657
if ((git_parse_ctx_init(&parser, buf, buf_size)) < 0)
16591658
return -1;
16601659

16611660
for (; parser.remain_len; git_parse_advance_line(&parser)) {
1661+
git_reflog_entry *entry;
16621662
const char *sig;
16631663
char c;
16641664

16651665
entry = git__calloc(1, sizeof(*entry));
16661666
GIT_ERROR_CHECK_ALLOC(entry);
1667-
16681667
entry->committer = git__calloc(1, sizeof(*entry->committer));
16691668
GIT_ERROR_CHECK_ALLOC(entry->committer);
16701669

16711670
if (git_parse_advance_oid(&entry->oid_old, &parser) < 0 ||
16721671
git_parse_advance_expected(&parser, " ", 1) < 0 ||
16731672
git_parse_advance_oid(&entry->oid_cur, &parser) < 0)
1674-
goto error;
1673+
goto next;
16751674

16761675
sig = parser.line;
16771676
while (git_parse_peek(&c, &parser, 0) == 0 && c != '\t' && c != '\n')
16781677
git_parse_advance_chars(&parser, 1);
16791678

16801679
if (git_signature__parse(entry->committer, &sig, parser.line, NULL, 0) < 0)
1681-
goto error;
1680+
goto next;
16821681

16831682
if (c == '\t') {
16841683
size_t len;
@@ -1692,16 +1691,18 @@ static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size)
16921691
GIT_ERROR_CHECK_ALLOC(entry->msg);
16931692
}
16941693

1695-
if (git_vector_insert(&log->entries, entry) < 0)
1696-
goto error;
1697-
}
1694+
if ((git_vector_insert(&log->entries, entry)) < 0) {
1695+
git_reflog_entry__free(entry);
1696+
return -1;
1697+
}
16981698

1699-
return 0;
1699+
continue;
17001700

1701-
error:
1702-
git_reflog_entry__free(entry);
1701+
next:
1702+
git_reflog_entry__free(entry);
1703+
}
17031704

1704-
return -1;
1705+
return 0;
17051706
}
17061707

17071708
static int create_new_reflog_file(const char *filepath)

tests/refs/reflog/reflog.c

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -222,45 +222,45 @@ void test_refs_reflog_reflog__reading_the_reflog_from_a_reference_with_no_log_re
222222
git_buf_dispose(&subtrees_log_path);
223223
}
224224

225-
void test_refs_reflog_reflog__reading_a_reflog_with_invalid_format_returns_error(void)
225+
void test_refs_reflog_reflog__reading_a_reflog_with_invalid_format_succeeds(void)
226226
{
227227
git_reflog *reflog;
228-
const git_error *error;
229228
const char *refname = "refs/heads/newline";
230229
const char *refmessage =
231230
"Reflog*message with a newline and enough content after it to pass the GIT_REFLOG_SIZE_MIN check inside reflog_parse.";
231+
const git_reflog_entry *entry;
232232
git_reference *ref;
233233
git_oid id;
234234
git_buf logpath = GIT_BUF_INIT, logcontents = GIT_BUF_INIT;
235235
char *star;
236236

237-
git_oid_fromstr(&id, current_master_tip);
238-
239-
/* create a new branch */
237+
/* Create a new branch. */
238+
cl_git_pass(git_oid_fromstr(&id, current_master_tip));
240239
cl_git_pass(git_reference_create(&ref, g_repo, refname, &id, 1, refmessage));
241240

242-
/* corrupt the branch reflog by introducing a newline inside the reflog message (we replace '*' with '\n') */
243-
git_buf_join_n(&logpath, '/', 3, git_repository_path(g_repo), GIT_REFLOG_DIR, refname);
241+
/*
242+
* Corrupt the branch reflog by introducing a newline inside the reflog message.
243+
* We do this by replacing '*' with '\n'
244+
*/
245+
cl_git_pass(git_buf_join_n(&logpath, '/', 3, git_repository_path(g_repo), GIT_REFLOG_DIR, refname));
244246
cl_git_pass(git_futils_readbuffer(&logcontents, git_buf_cstr(&logpath)));
245247
cl_assert((star = strchr(git_buf_cstr(&logcontents), '*')) != NULL);
246248
*star = '\n';
247249
cl_git_rewritefile(git_buf_cstr(&logpath), git_buf_cstr(&logcontents));
248250

249-
/* confirm that the file was rewritten successfully and now contains a '\n' in the expected location */
251+
/*
252+
* Confirm that the file was rewritten successfully
253+
* and now contains a '\n' in the expected location
254+
*/
250255
cl_git_pass(git_futils_readbuffer(&logcontents, git_buf_cstr(&logpath)));
251256
cl_assert(strstr(git_buf_cstr(&logcontents), "Reflog\nmessage") != NULL);
252257

253-
/* clear the error state so we can capture the error generated by git_reflog_read */
254-
git_error_clear();
255-
256-
cl_git_fail(git_reflog_read(&reflog, g_repo, refname));
257-
258-
error = git_error_last();
259-
260-
cl_assert(error != NULL);
261-
cl_assert_equal_s("unable to parse OID - contains invalid characters", error->message);
258+
cl_git_pass(git_reflog_read(&reflog, g_repo, refname));
259+
cl_assert(entry = git_reflog_entry_byindex(reflog, 0));
260+
cl_assert_equal_s(git_reflog_entry_message(entry), "Reflog");
262261

263262
git_reference_free(ref);
263+
git_reflog_free(reflog);
264264
git_buf_dispose(&logpath);
265265
git_buf_dispose(&logcontents);
266266
}

0 commit comments

Comments
 (0)