Skip to content

Commit 5a77c68

Browse files
committed
object-file: avoid ODB transaction when not writing objects
In ce1661f (odb: add transaction interface, 2025-09-16), existing ODB transaction logic is adapted to create a transaction interface at the ODB layer. The intent here is for the ODB transaction interface to eventually provide an object source agnostic means to manage transactions. An unintended consequence of this change though is that `object-file.c:index_fd()` may enter the ODB transaction path even when no object write is requested. In non-repository contexts, this can result in a NULL dereference and segfault. One such case occurs when running git-diff(1) outside of a repository with "core.bigFileThreshold" forcing the streaming path in `index_fd()`: $ echo foo >foo $ echo bar >bar $ git -c core.bigFileThreshold=1 diff -- foo bar In this scenario, the caller only needs to compute the object ID. Object hashing does not require an ODB, so starting a transaction is both unnecessary and invalid. Fix the bug by avoiding the use of ODB transactions in `index_fd()` when callers are only interested in computing the object hash. Reported-by: Luca Stefani <luca.stefani.ge1@gmail.com> Signed-off-by: Justin Tobler <jltobler@gmail.com>
1 parent 1adf5bc commit 5a77c68

File tree

2 files changed

+49
-12
lines changed

2 files changed

+49
-12
lines changed

object-file.c

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1640,6 +1640,34 @@ static int index_blob_packfile_transaction(struct odb_transaction_files *transac
16401640
return 0;
16411641
}
16421642

1643+
static int hash_blob_stream(const struct git_hash_algo *hash_algo,
1644+
struct object_id *result_oid, int fd, size_t size)
1645+
{
1646+
unsigned char buf[16384];
1647+
struct git_hash_ctx ctx;
1648+
unsigned header_len;
1649+
1650+
header_len = format_object_header((char *)buf, sizeof(buf),
1651+
OBJ_BLOB, size);
1652+
hash_algo->init_fn(&ctx);
1653+
git_hash_update(&ctx, buf, header_len);
1654+
1655+
while (size) {
1656+
size_t rsize = size < sizeof(buf) ? size : sizeof(buf);
1657+
ssize_t read_result = read_in_full(fd, buf, rsize);
1658+
1659+
if ((read_result < 0) || ((size_t)read_result != rsize))
1660+
return -1;
1661+
1662+
git_hash_update(&ctx, buf, rsize);
1663+
size -= read_result;
1664+
}
1665+
1666+
git_hash_final_oid(result_oid, &ctx);
1667+
1668+
return 0;
1669+
}
1670+
16431671
int index_fd(struct index_state *istate, struct object_id *oid,
16441672
int fd, struct stat *st,
16451673
enum object_type type, const char *path, unsigned flags)
@@ -1661,18 +1689,23 @@ int index_fd(struct index_state *istate, struct object_id *oid,
16611689
ret = index_core(istate, oid, fd, xsize_t(st->st_size),
16621690
type, path, flags);
16631691
} else {
1664-
struct object_database *odb = the_repository->objects;
1665-
struct odb_transaction_files *files_transaction;
1666-
struct odb_transaction *transaction;
1667-
1668-
transaction = odb_transaction_begin(odb);
1669-
files_transaction = container_of(odb->transaction,
1670-
struct odb_transaction_files,
1671-
base);
1672-
ret = index_blob_packfile_transaction(files_transaction, oid, fd,
1673-
xsize_t(st->st_size),
1674-
path, flags);
1675-
odb_transaction_commit(transaction);
1692+
if (flags & INDEX_WRITE_OBJECT) {
1693+
struct object_database *odb = the_repository->objects;
1694+
struct odb_transaction_files *files_transaction;
1695+
struct odb_transaction *transaction;
1696+
1697+
transaction = odb_transaction_begin(odb);
1698+
files_transaction = container_of(odb->transaction,
1699+
struct odb_transaction_files,
1700+
base);
1701+
ret = index_blob_packfile_transaction(files_transaction, oid, fd,
1702+
xsize_t(st->st_size),
1703+
path, flags);
1704+
odb_transaction_commit(transaction);
1705+
} else {
1706+
ret = hash_blob_stream(the_repository->hash_algo, oid,
1707+
fd, xsize_t(st->st_size));
1708+
}
16761709
}
16771710

16781711
close(fd);

t/t1517-outside-repo.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ test_expect_success 'diff outside repository' '
9393
test_cmp expect actual
9494
'
9595

96+
test_expect_success 'hash object exceeding bigFileThreshold outside repository' '
97+
echo foo | git -c core.bigFileThreshold=1 hash-object --stdin
98+
'
99+
96100
test_expect_success 'stripspace outside repository' '
97101
nongit git stripspace -s </dev/null
98102
'

0 commit comments

Comments
 (0)