]> git.ipfire.org Git - thirdparty/git.git/commitdiff
object-file: avoid ODB transaction when not writing objects
authorJustin Tobler <jltobler@gmail.com>
Tue, 7 Apr 2026 20:17:30 +0000 (15:17 -0500)
committerJunio C Hamano <gitster@pobox.com>
Wed, 8 Apr 2026 00:32:36 +0000 (17:32 -0700)
In ce1661f9da (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>
[jc: adjusted to fd13909e (Merge branch 'jt/odb-transaction', 2025-10-02)]
Signed-off-by: Junio C Hamano <gitster@pobox.com>
object-file.c
t/t1517-outside-repo.sh

index 4675c8ed6b67eb8b1f054aa7326f380d9a0a29b5..5d72e65bdece014f96cc2538c3412f5166f7a41f 100644 (file)
@@ -1599,6 +1599,34 @@ static int index_blob_packfile_transaction(struct odb_transaction *transaction,
        return 0;
 }
 
+static int hash_blob_stream(const struct git_hash_algo *hash_algo,
+                           struct object_id *result_oid, int fd, size_t size)
+{
+       unsigned char buf[16384];
+       struct git_hash_ctx ctx;
+       unsigned header_len;
+
+       header_len = format_object_header((char *)buf, sizeof(buf),
+                                         OBJ_BLOB, size);
+       hash_algo->init_fn(&ctx);
+       git_hash_update(&ctx, buf, header_len);
+
+       while (size) {
+               size_t rsize = size < sizeof(buf) ? size : sizeof(buf);
+               ssize_t read_result = read_in_full(fd, buf, rsize);
+
+               if ((read_result < 0) || ((size_t)read_result != rsize))
+                       return -1;
+
+               git_hash_update(&ctx, buf, rsize);
+               size -= read_result;
+       }
+
+       git_hash_final_oid(result_oid, &ctx);
+
+       return 0;
+}
+
 int index_fd(struct index_state *istate, struct object_id *oid,
             int fd, struct stat *st,
             enum object_type type, const char *path, unsigned flags)
@@ -1620,14 +1648,19 @@ int index_fd(struct index_state *istate, struct object_id *oid,
                ret = index_core(istate, oid, fd, xsize_t(st->st_size),
                                 type, path, flags);
        } else {
-               struct odb_transaction *transaction;
-
-               transaction = odb_transaction_begin(the_repository->objects);
-               ret = index_blob_packfile_transaction(the_repository->objects->transaction,
-                                                     oid, fd,
-                                                     xsize_t(st->st_size),
-                                                     path, flags);
-               odb_transaction_commit(transaction);
+               if (flags & INDEX_WRITE_OBJECT) {
+                       struct odb_transaction *transaction;
+
+                       transaction = odb_transaction_begin(the_repository->objects);
+                       ret = index_blob_packfile_transaction(the_repository->objects->transaction,
+                                                             oid, fd,
+                                                             xsize_t(st->st_size),
+                                                             path, flags);
+                       odb_transaction_commit(transaction);
+               } else {
+                       ret = hash_blob_stream(the_repository->hash_algo, oid,
+                                              fd, xsize_t(st->st_size));
+               }
        }
 
        close(fd);
index c824c1a25cf27e1be241ef1d23d4928c37aba803..e1d35170de61a3357abbf85b6ba843fcfe2fe58b 100755 (executable)
@@ -93,6 +93,14 @@ test_expect_success 'diff outside repository' '
        test_cmp expect actual
 '
 
+test_expect_success 'hash object exceeding bigFileThreshold outside repository' '
+       (
+               cd non-repo &&
+               echo foo >foo &&
+               git -c core.bigFileThreshold=1 hash-object --stdin <foo
+       )
+'
+
 test_expect_success 'stripspace outside repository' '
        nongit git stripspace -s </dev/null
 '