Files
git/tag.c
Patrick Steinhardt 7ec85185b1 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>
2025-11-04 07:32:25 -08:00

234 lines
5.6 KiB
C

#define USE_THE_REPOSITORY_VARIABLE
#define DISABLE_SIGN_COMPARE_WARNINGS
#include "git-compat-util.h"
#include "environment.h"
#include "tag.h"
#include "object-name.h"
#include "odb.h"
#include "commit.h"
#include "tree.h"
#include "blob.h"
#include "alloc.h"
#include "gpg-interface.h"
#include "hex.h"
#include "packfile.h"
const char *tag_type = "tag";
static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags)
{
struct signature_check sigc;
struct strbuf payload = STRBUF_INIT;
struct strbuf signature = STRBUF_INIT;
int ret;
memset(&sigc, 0, sizeof(sigc));
if (!parse_signature(buf, size, &payload, &signature)) {
if (flags & GPG_VERIFY_VERBOSE)
write_in_full(1, buf, size);
return error("no signature found");
}
sigc.payload_type = SIGNATURE_PAYLOAD_TAG;
sigc.payload = strbuf_detach(&payload, &sigc.payload_len);
ret = check_signature(&sigc, signature.buf, signature.len);
if (!(flags & GPG_VERIFY_OMIT_STATUS))
print_signature_buffer(&sigc, flags);
signature_check_clear(&sigc);
strbuf_release(&payload);
strbuf_release(&signature);
return ret;
}
int gpg_verify_tag(const struct object_id *oid, const char *name_to_report,
unsigned flags)
{
enum object_type type;
char *buf;
unsigned long size;
int ret;
type = odb_read_object_info(the_repository->objects, oid, NULL);
if (type != OBJ_TAG)
return error("%s: cannot verify a non-tag object of type %s.",
name_to_report ?
name_to_report :
repo_find_unique_abbrev(the_repository, oid, DEFAULT_ABBREV),
type_name(type));
buf = odb_read_object(the_repository->objects, oid, &type, &size);
if (!buf)
return error("%s: unable to read file.",
name_to_report ?
name_to_report :
repo_find_unique_abbrev(the_repository, oid, DEFAULT_ABBREV));
ret = run_gpg_verify(buf, size, flags);
free(buf);
return ret;
}
struct object *deref_tag(struct repository *r, struct object *o, const char *warn, int warnlen)
{
struct object_id *last_oid = NULL;
while (o && o->type == OBJ_TAG)
if (((struct tag *)o)->tagged) {
last_oid = &((struct tag *)o)->tagged->oid;
o = parse_object(r, last_oid);
} else {
last_oid = NULL;
o = NULL;
}
if (!o && warn) {
if (last_oid && is_promisor_object(r, last_oid))
return NULL;
if (!warnlen)
warnlen = strlen(warn);
error("missing object referenced by '%.*s'", warnlen, warn);
}
return o;
}
struct tag *lookup_tag(struct repository *r, const struct object_id *oid)
{
struct object *obj = lookup_object(r, oid);
if (!obj)
return create_object(r, oid, alloc_tag_node(r));
return object_as_type(obj, OBJ_TAG, 0);
}
static timestamp_t parse_tag_date(const char *buf, const char *tail)
{
const char *dateptr;
while (buf < tail && *buf++ != '>')
/* nada */;
if (buf >= tail)
return 0;
dateptr = buf;
while (buf < tail && *buf++ != '\n')
/* nada */;
if (buf >= tail)
return 0;
/* dateptr < buf && buf[-1] == '\n', so parsing will stop at buf-1 */
return parse_timestamp(dateptr, NULL, 10);
}
void release_tag_memory(struct tag *t)
{
free(t->tag);
t->tagged = NULL;
t->object.parsed = 0;
t->date = 0;
}
int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, unsigned long size)
{
struct object_id oid;
char type[20];
const char *bufptr = data;
const char *tail = bufptr + size;
const char *nl;
if (item->object.parsed)
return 0;
if (item->tag) {
/*
* Presumably left over from a previous failed parse;
* clear it out in preparation for re-parsing (we'll probably
* hit the same error, which lets us tell our current caller
* about the problem).
*/
FREE_AND_NULL(item->tag);
}
if (size < the_hash_algo->hexsz + 24)
return -1;
if (memcmp("object ", bufptr, 7) || parse_oid_hex(bufptr + 7, &oid, &bufptr) || *bufptr++ != '\n')
return -1;
if (!starts_with(bufptr, "type "))
return -1;
bufptr += 5;
nl = memchr(bufptr, '\n', tail - bufptr);
if (!nl || sizeof(type) <= (nl - bufptr))
return -1;
memcpy(type, bufptr, nl - bufptr);
type[nl - bufptr] = '\0';
bufptr = nl + 1;
if (!strcmp(type, blob_type)) {
item->tagged = (struct object *)lookup_blob(r, &oid);
} else if (!strcmp(type, tree_type)) {
item->tagged = (struct object *)lookup_tree(r, &oid);
} else if (!strcmp(type, commit_type)) {
item->tagged = (struct object *)lookup_commit(r, &oid);
} else if (!strcmp(type, tag_type)) {
item->tagged = (struct object *)lookup_tag(r, &oid);
} else {
return error("unknown tag type '%s' in %s",
type, oid_to_hex(&item->object.oid));
}
if (!item->tagged)
return error("bad tag pointer to %s in %s",
oid_to_hex(&oid),
oid_to_hex(&item->object.oid));
if (bufptr + 4 < tail && starts_with(bufptr, "tag "))
; /* good */
else
return -1;
bufptr += 4;
nl = memchr(bufptr, '\n', tail - bufptr);
if (!nl)
return -1;
item->tag = xmemdupz(bufptr, nl - bufptr);
bufptr = nl + 1;
if (bufptr + 7 < tail && starts_with(bufptr, "tagger "))
item->date = parse_tag_date(bufptr, tail);
else
item->date = 0;
item->object.parsed = 1;
return 0;
}
int parse_tag(struct tag *item)
{
enum object_type type;
void *data;
unsigned long size;
int ret;
if (item->object.parsed)
return 0;
data = odb_read_object(the_repository->objects, &item->object.oid,
&type, &size);
if (!data)
return error("Could not read %s",
oid_to_hex(&item->object.oid));
if (type != OBJ_TAG) {
free(data);
return error("Object %s not a tag",
oid_to_hex(&item->object.oid));
}
ret = parse_tag_buffer(the_repository, item, data, size);
free(data);
return ret;
}
struct object_id *get_tagged_oid(struct tag *tag)
{
if (!tag->tagged)
die("bad tag");
return &tag->tagged->oid;
}