object: add flag to `peel_object()` to verify object type

When peeling a tag to a non-tag object we repeatedly call `parse_object()` on the tagged object until we find the first object that isn't a tag. While this feels sensible at first, there is a big catch here: `parse_object()` doesn't actually verify the type of the tagged object. The relevant code path here eventually ends up in `parse_tag_buffer()`. Here, we parse the various fields of the tag, including the "type". Once we've figured out the type and the tagged object ID, we call one of the `lookup_${type}()` functions for whatever type we have found. There is two possible outcomes in the successful case: 1. The object is already part of our cached objects. In that case we double-check whether the type we're trying to look up matches the type that was cached. 2. The object is _not_ part of our cached objects. In that case, we simply create a new object with the expected type, but we don't parse that object. In the first case we might notice type mismatches, but only in the case where our cache has the object with the correct type. In the second case, we'll blindly assume that the type is correct and then go with it. We'll only notice that the type might be wrong when we try to parse the object at a later point. Now arguably, we could change `parse_tag_buffer()` to verify the tagged object's type for us. But that would have the effect that such a tag cannot be parsed at all anymore, and we have a small bunch of tests for exactly this case that assert we still can open such tags. So this change does not feel like something we can retroactively tighten, even though one shouldn't ever hit such corrupted tags. Instead, add a new `flags` field to `peel_object()` that allows the caller to opt in to strict object verification. This will be wired up at a subset of callsites over the next few commits. Note that this change also inlines `deref_tag_noverify()`. There's only been two callsites of that function, the one we're changing and one in our test helpers. The latter callsite can trivially use `deref_tag()` instead, so by inlining the function we avoid having to pass down the flag. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Oct 23, 2025 at 09:16 UTC 7ec85185b197ce1cd28721a6f4415fb9db5cd42f
9 files changed +38 -25
object.c
+17 -3
@@ -209,11 +209,12 @@ struct object *lookup_object_by_type(struct repository *r,
209
210 enum peel_status peel_object(struct repository *r,
211 const struct object_id *name,
212 - struct object_id *oid)
212 + struct object_id *oid,
213 + unsigned flags)
214 {
215 struct object *o = lookup_unknown_object(r, name);
216
216 - if (o->type == OBJ_NONE) {
217 + if (o->type == OBJ_NONE || flags & PEEL_OBJECT_VERIFY_OBJECT_TYPE) {
218 int type = odb_read_object_info(r->objects, name, NULL);
219 if (type < 0 || !object_as_type(o, type, 0))
220 return PEEL_INVALID;
@@ -222,7 +223,20 @@ enum peel_status peel_object(struct repository *r,
223 if (o->type != OBJ_TAG)
224 return PEEL_NON_TAG;
225
225 - o = deref_tag_noverify(r, o);
226 + while (o && o->type == OBJ_TAG) {
227 + o = parse_object(r, &o->oid);
228 + if (o && o->type == OBJ_TAG && ((struct tag *)o)->tagged) {
229 + o = ((struct tag *)o)->tagged;
230 +
231 + if (flags & PEEL_OBJECT_VERIFY_OBJECT_TYPE) {
232 + int type = odb_read_object_info(r->objects, &o->oid, NULL);
233 + if (type < 0 || !object_as_type(o, type, 0))
234 + return PEEL_INVALID;
235 + }
236 + } else {
237 + o = NULL;
238 + }
239 + }
240 if (!o)
241 return PEEL_INVALID;
242
object.h
+14 -1
@@ -287,6 +287,17 @@ enum peel_status {
287 PEEL_BROKEN = -4
288 };
289
290 +enum peel_object_flags {
291 + /*
292 + * Always verify the object type, even in the case where the looked-up
293 + * object already has an object type. This can be useful when the
294 + * stored object type may be invalid. One such case is when looking up
295 + * objects via tags, where we blindly trust the object type declared by
296 + * the tag.
297 + */
298 + PEEL_OBJECT_VERIFY_OBJECT_TYPE = (1 << 0),
299 +};
300 +
301 /*
302 * Peel the named object; i.e., if the object is a tag, resolve the
303 * tag recursively until a non-tag is found. If successful, store the
@@ -295,7 +306,9 @@ enum peel_status {
306 * and leave oid unchanged.
307 */
308 enum peel_status peel_object(struct repository *r,
298 - const struct object_id *name, struct object_id *oid);
309 + const struct object_id *name,
310 + struct object_id *oid,
311 + unsigned flags);
312
313 struct object_list *object_list_insert(struct object *item,
314 struct object_list **list_p);
ref-filter.c
+1 -1
@@ -2581,7 +2581,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
2581 if (need_tagged) {
2582 if (!is_null_oid(&ref->peeled_oid)) {
2583 oidcpy(&oi_deref.oid, &ref->peeled_oid);
2584 - } else if (!peel_object(the_repository, &obj->oid, &oi_deref.oid)) {
2584 + } else if (!peel_object(the_repository, &oi.oid, &oi_deref.oid, 0)) {
2585 /* We managed to peel the object ourselves. */
2586 } else {
2587 die("bad tag");
refs.c
+1 -1
@@ -2333,7 +2333,7 @@ int reference_get_peeled_oid(struct repository *repo,
2333 return 0;
2334 }
2335
2336 - return peel_object(repo, ref->oid, peeled_oid) ? -1 : 0;
2336 + return peel_object(repo, ref->oid, peeled_oid, 0) ? -1 : 0;
2337 }
2338
2339 int refs_update_symref(struct ref_store *refs, const char *ref,
refs/packed-backend.c
+2 -3
@@ -1527,9 +1527,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
1527 i++;
1528 } else {
1529 struct object_id peeled;
1530 - int peel_error = peel_object(refs->base.repo,
1531 - &update->new_oid,
1532 - &peeled);
1530 + int peel_error = peel_object(refs->base.repo, &update->new_oid,
1531 + &peeled, 0);
1532
1533 if (write_packed_entry(out, update->refname,
1534 &update->new_oid,
refs/reftable-backend.c
+2 -2
@@ -1632,7 +1632,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
1632 ref.refname = (char *)u->refname;
1633 ref.update_index = ts;
1634
1635 - peel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled);
1635 + peel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled, 0);
1636 if (!peel_error) {
1637 ref.value_type = REFTABLE_REF_VAL2;
1638 memcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);
@@ -2497,7 +2497,7 @@ static int write_reflog_expiry_table(struct reftable_writer *writer, void *cb_da
2497 ref.refname = (char *)arg->refname;
2498 ref.update_index = ts;
2499
2500 - if (!peel_object(arg->refs->base.repo, &arg->update_oid, &peeled)) {
2500 + if (!peel_object(arg->refs->base.repo, &arg->update_oid, &peeled, 0)) {
2501 ref.value_type = REFTABLE_REF_VAL2;
2502 memcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);
2503 memcpy(ref.value.val2.value, arg->update_oid.hash, GIT_MAX_RAWSZ);
t/helper/test-reach.c
+1 -1
@@ -63,7 +63,7 @@ int cmd__reach(int ac, const char **av)
63 die("failed to resolve %s", buf.buf + 2);
64
65 orig = parse_object(r, &oid);
66 - peeled = deref_tag_noverify(the_repository, orig);
66 + peeled = deref_tag(the_repository, orig, NULL, 0);
67
68 if (!peeled)
69 die("failed to load commit for input %s resulting in oid %s",
tag.c
-12
@@ -94,18 +94,6 @@ struct object *deref_tag(struct repository *r, struct object *o, const char *war
94 return o;
95 }
96
97 -struct object *deref_tag_noverify(struct repository *r, struct object *o)
98 -{
99 - while (o && o->type == OBJ_TAG) {
100 - o = parse_object(r, &o->oid);
101 - if (o && o->type == OBJ_TAG && ((struct tag *)o)->tagged)
102 - o = ((struct tag *)o)->tagged;
103 - else
104 - o = NULL;
105 - }
106 - return o;
107 -}
108 -
97 struct tag *lookup_tag(struct repository *r, const struct object_id *oid)
98 {
99 struct object *obj = lookup_object(r, oid);
tag.h
-1
@@ -16,7 +16,6 @@ int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, u
16 int parse_tag(struct tag *item);
17 void release_tag_memory(struct tag *t);
18 struct object *deref_tag(struct repository *r, struct object *, const char *, int);
19 -struct object *deref_tag_noverify(struct repository *r, struct object *);
19 int gpg_verify_tag(const struct object_id *oid,
20 const char *name_to_report, unsigned flags);
21 struct object_id *get_tagged_oid(struct tag *tag);