[BUG] Uninitialized `db` freed in `git_refdb_open` on allocation failure
メンテナーはふだん 12 日以内に返信
まだ誰も着手していません。
評価
調査の方向性
src/libgit2/refdb.c の git_refdb_open から始めてください。ここではローカル変数 db が初期化子なしで宣言されており、git_refdb_new が失敗した後のエラーパスで解放されています。添付の 06.c アロケーション失敗再現コードを libgit2 に対してビルドし、スキャンと併せて実行して N = 2 でのセグメンテーション違反を確認してください。db を NULL で初期化し、スキャンがクラッシュせず適切なエラーで完了すれば完了です。
索引モデルが issue の本文から書いたものです。
説明
libgit2 commit 07b89d13e src/libgit2/refdb.c:42-74. Introduced by 806a0062f ("repository: wire up refStorage extension"); not in any release.
Bug
db is never initialized, and git_refdb_new does not write *out when its allocation fails. The error path then frees the garbage pointer, so the process gets SIGSEGV where it should get -1:
git_refdb *db; /* 43: uninitialized */
...
if ((error = git_refdb_new(&db, repo)) < 0) /* db left unset on OOM */
goto out;
...
out:
if (error)
git_refdb_free(db); /* 74: frees garbage */
The fix is git_refdb *db = NULL; (git_refdb_free ignores NULL).
Reproduce
06.c installs a counting allocator through GIT_OPT_SET_ALLOCATOR that returns NULL for the Nth allocation, which looks exactly like a real OOM to libgit2. It creates a one-commit repo, opens it, and runs git_reference_lookup("HEAD"), which opens the refdb, with the Nth allocation failing, for N = 1..200. Only allocations made during the lookup can fail. The crashing N depends on the build, which is why the program scans instead of hard-coding it.
gcc -g -O0 06.c -I"$LIBGIT2_INCLUDE" "$LIBGIT2_LIBDIR/libgit2.a" -lz -lpthread -ldl -o 06_c
./06_c # scan
gdb -ex run -ex bt --args ./06_c <N> # single index
alloc #0: OK
alloc #1: clean error (handled)
Segmentation fault # N = 2, exit 139
#0 git_atomic32_dec src/util/thread.h:136
#1 git_refdb_free src/libgit2/refdb.c:126
#2 git_refdb_open src/libgit2/refdb.c:74
#3 git_repository_refdb__weakptr src/libgit2/repository.c:1656
#4 git_reference_lookup_resolved src/libgit2/refs.c:252
06.c
#include <git2.h>
#include <git2/sys/alloc.h>
#include <stdio.h>
#include <stdlib.h>
static long fail_at, count; /* fail the fail_at-th allocation; 0 = never */
static int should_fail(void) { return fail_at && ++count == fail_at; }
static void *x_malloc(size_t n, const char *file, int line)
{
(void)file; (void)line;
return should_fail() ? NULL : malloc(n ? n : 1);
}
static void *x_realloc(void *p, size_t n, const char *file, int line)
{
(void)file; (void)line;
return should_fail() ? NULL : realloc(p, n ? n : 1);
}
/* A repo with one (empty-tree) commit. */
static int make_repo(const char *dir)
{
git_repository *repo;
git_signature *sig = NULL;
git_index *idx = NULL;
git_tree *tree = NULL;
git_oid id;
int rc;
if ((rc = git_repository_init(&repo, dir, 0)) < 0)
return rc;
if ((rc = git_signature_new(&sig, "r", "[email protected]", 1234567890, 0)) == 0 &&
(rc = git_repository_index(&idx, repo)) == 0 &&
(rc = git_index_write_tree(&id, idx)) == 0 &&
(rc = git_tree_lookup(&tree, repo, &id)) == 0)
rc = git_commit_create_v(&id, repo, "HEAD", sig, sig, NULL, "init", tree, 0);
git_tree_free(tree);
git_index_free(idx);
git_signature_free(sig);
git_repository_free(repo);
return rc;
}
/* Open the repo, then look up HEAD (which opens the refdb) with the nth
* allocation failing. Returns 0 on success, <0 on a clean error; the bug crashes. */
static int run(const char *dir, long n)
{
git_repository *repo;
git_reference *ref;
int rc;
if ((rc = git_repository_open(&repo, dir)) < 0)
return rc;
fail_at = n, count = 0;
rc = git_reference_lookup(&ref, repo, "HEAD");
fail_at = 0;
if (rc == 0)
git_reference_free(ref);
git_repository_free(repo);
printf(" alloc #%ld: %s\n", n, rc ? "clean error (handled)" : "OK");
return rc;
}
int main(int argc, char **argv)
{
git_allocator alloc = { x_malloc, x_realloc, free };
char dir[] = "/tmp/06_c_XXXXXX";
long only = argc > 1 ? atol(argv[1]) : 0, n;
setvbuf(stdout, NULL, _IONBF, 0);
if (!mkdtemp(dir) || git_libgit2_init() < 0 ||
git_libgit2_opts(GIT_OPT_SET_ALLOCATOR, &alloc) ||
make_repo(dir) || run(dir, 0))
return fprintf(stderr, "06_c: setup failed\n"), 1;
if (only)
return run(dir, only) ? 2 : 0;
for (n = 1; n <= 200; n++)
run(dir, n);
puts("06_c: no crash -- bug did NOT reproduce");
return 1;
}
- 主要言語
- C
- スター
- 10.6k
- フォーク
- 2.7k
- 平均マージ
- 11日 9時間
- マージ済み PR(30日)
- 8
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
libgit2/libgit2 のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
メンテナーはふだん 12 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
メンテナーはふだん 12 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 12 日以内に返信
-
hashsig: the similarity heap evicts but never inserts, so the retained set is not the extremes対応中かも @tasodoufu が 17 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
メンテナーはふだん 12 日以内に返信
-
`git_remote_disconnect` leaves open file descriptors behind対応中かも @civodul が 34 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 86/100
メンテナーはふだん 12 日以内に返信
libgit2/libgit2 の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
DaveGamble/cJSON#1100 ·
-
UB: (int) cast of a user double outside int range in dispatch / vm_index_is_int / flat_index_atオープンarea:runtime-vm
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
InauguralSystems/EigenScript#1748 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
leanprover/lean4#15677 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 2 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
wasm-micro-runtime/wasm-micro-runtime#5159 ·
メンテナーはふだん 7 日以内に返信