diff mbox series

[1/2] tag: factor out get_tagged_oid()

Message ID 1385c954-d9ef-7ef6-6185-0dad885531ec@web.de (mailing list archive)
State New, archived
Headers show
Series [1/2] tag: factor out get_tagged_oid() | expand

Commit Message

René Scharfe Sept. 5, 2019, 7:55 p.m. UTC
Add a function for accessing the ID of the object referenced by a tag
safely, i.e. without causing a segfault when encountering a broken tag
where ->tagged is NULL.

Signed-off-by: René Scharfe <l.s.r@web.de>
---
 pack-bitmap.c | 4 +---
 revision.c    | 4 +---
 tag.c         | 7 +++++++
 tag.h         | 1 +
 4 files changed, 10 insertions(+), 6 deletions(-)

--
2.23.0

Comments

Jeff King Sept. 6, 2019, 7:13 a.m. UTC | #1
On Thu, Sep 05, 2019 at 09:55:55PM +0200, René Scharfe wrote:

> Add a function for accessing the ID of the object referenced by a tag
> safely, i.e. without causing a segfault when encountering a broken tag
> where ->tagged is NULL.

This approach seems to pretty reasonable. As somebody who's been
thinking about this, I'd be curious to hear your thoughts on:

  https://public-inbox.org/git/20190906065606.GC5122@sigill.intra.peff.net/

which _in theory_ means tag->tagged would never be NULL (we'd catch it
at the parsing stage and consider that an error). But we'd still
potentially want to protect ourselves as you do here for code paths
which don't necessarily check the parse result.

-Peff
René Scharfe Sept. 6, 2019, 3:05 p.m. UTC | #2
Am 06.09.19 um 09:13 schrieb Jeff King:
> On Thu, Sep 05, 2019 at 09:55:55PM +0200, René Scharfe wrote:
>
>> Add a function for accessing the ID of the object referenced by a tag
>> safely, i.e. without causing a segfault when encountering a broken tag
>> where ->tagged is NULL.
>
> This approach seems to pretty reasonable. As somebody who's been
> thinking about this, I'd be curious to hear your thoughts on:
>
>   https://public-inbox.org/git/20190906065606.GC5122@sigill.intra.peff.net/
>
> which _in theory_ means tag->tagged would never be NULL (we'd catch it
> at the parsing stage and consider that an error). But we'd still
> potentially want to protect ourselves as you do here for code paths
> which don't necessarily check the parse result.

A tag referencing an unknown object sounds strange to me.  I imagine we
might get such a thing when the referenced object is lost (broken repo)
or purpose-built from an attacker.  Could such a tag still be used for
anything?  Are there other possible causes?  I suspect the answer to
both questions is "no", and then it makes sense to reject it as early
as possible.

But I may be missing something.  In particular I'm confused by these
patches from February 2008, which seem to suggest that such tags should
not be reported in all cases, but sometimes just silently ignored:

   9684afd967 revision.c: handle tag->tagged == NULL
   cc36934791 process_tag: handle tag->tagged == NULL
   24e8a3c946 deref_tag: handle tag->tagged = NULL

So is there perhaps a use case for them after all?

Leaving that aside: The parsed flag means we saw and checked the object
already.  That is true also for broken objects.  Clearing the flag can
cause the same error to be reported multiple times.  How about setting
it at the start as before, but returning -1 from parse_tag_buffer() if
.parsed == 1 && .tagged == NULL?

René
René Scharfe Sept. 6, 2019, 3:25 p.m. UTC | #3
Am 06.09.19 um 17:05 schrieb René Scharfe:
> A tag referencing an unknown object sounds strange to me.  I imagine we
> might get such a thing when the referenced object is lost (broken repo)
> or purpose-built from an attacker.  Could such a tag still be used for
> anything?  Are there other possible causes?
Forward compatibility perhaps, i.e. supporting tags (by ignoring them)
that point to a new type of object introduced by a future version.

René
Jeff King Sept. 6, 2019, 5:51 p.m. UTC | #4
On Fri, Sep 06, 2019 at 05:05:11PM +0200, René Scharfe wrote:

> > This approach seems to pretty reasonable. As somebody who's been
> > thinking about this, I'd be curious to hear your thoughts on:
> >
> >   https://public-inbox.org/git/20190906065606.GC5122@sigill.intra.peff.net/
> >
> > which _in theory_ means tag->tagged would never be NULL (we'd catch it
> > at the parsing stage and consider that an error). But we'd still
> > potentially want to protect ourselves as you do here for code paths
> > which don't necessarily check the parse result.
> 
> A tag referencing an unknown object sounds strange to me.  I imagine we
> might get such a thing when the referenced object is lost (broken repo)
> or purpose-built from an attacker.  Could such a tag still be used for
> anything?  Are there other possible causes?  I suspect the answer to
> both questions is "no", and then it makes sense to reject it as early
> as possible.

I don't think there's really a valid case. Keep in mind that this isn't
checking for the object on-disk at all. It's only checking:

  - were we able to parse the tag at all (i.e., is it syntactically
    valid)

  - does it have a type we know about

  - were we able to create an in-memory struct, meaning we did not see
    the same oid with a different type elsewhere in the same process

The first and middle imply a corruption or malicious attack. The second
could mean forward-compatibility is a concern, but I think this is the
tip of the iceberg. You really need a Git that understands the object
types you're working with, or basically everything is going to fail.

So I'm not too concerned with tightening this code. The question to me
is whether to do it in parse_tag(), or when accessing tag->tagged, or
both.

> But I may be missing something.  In particular I'm confused by these
> patches from February 2008, which seem to suggest that such tags should
> not be reported in all cases, but sometimes just silently ignored:
> 
>    9684afd967 revision.c: handle tag->tagged == NULL
>    cc36934791 process_tag: handle tag->tagged == NULL
>    24e8a3c946 deref_tag: handle tag->tagged = NULL
> 
> So is there perhaps a use case for them after all?

There's not much discussion on the list from that thread, but searching
for messages from Martin around that the main goal was protecting
against corruptions. So I think these NULL checks were attempting to do
the same thing we're doing here, just in a less centralized way.

> Leaving that aside: The parsed flag means we saw and checked the object
> already.  That is true also for broken objects.  Clearing the flag can
> cause the same error to be reported multiple times.  How about setting
> it at the start as before, but returning -1 from parse_tag_buffer() if
> .parsed == 1 && .tagged == NULL?

That wouldn't cover all of the other reasons that we might have seen a
parse failure (so it fixes "tagged == NULL", but means that a subsequent
parse_tag() will silently return success).

I think the multiple error messages may be OK given that this is
explicitly covering a corrupt situation. But the more robust way is
adding an extra bit to say "I parsed this already, and it was corrupt"
to cause parse_tag() to return an error reliably.

-Peff
diff mbox series

Patch

diff --git a/pack-bitmap.c b/pack-bitmap.c
index ed2befaac6..30842e1e74 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -709,9 +709,7 @@  struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs)
 			else
 				object_list_insert(object, &wants);

-			if (!tag->tagged)
-				die("bad tag");
-			object = parse_object_or_die(&tag->tagged->oid, NULL);
+			object = parse_object_or_die(get_tagged_oid(tag), NULL);
 		}

 		if (object->flags & UNINTERESTING)
diff --git a/revision.c b/revision.c
index 07412297f0..ee1b1552b9 100644
--- a/revision.c
+++ b/revision.c
@@ -404,9 +404,7 @@  static struct commit *handle_commit(struct rev_info *revs,
 		struct tag *tag = (struct tag *) object;
 		if (revs->tag_objects && !(flags & UNINTERESTING))
 			add_pending_object(revs, object, tag->tag);
-		if (!tag->tagged)
-			die("bad tag");
-		object = parse_object(revs->repo, &tag->tagged->oid);
+		object = parse_object(revs->repo, get_tagged_oid(tag));
 		if (!object) {
 			if (revs->ignore_missing_links || (flags & UNINTERESTING))
 				return NULL;
diff --git a/tag.c b/tag.c
index 5db870edb9..bfa0e31435 100644
--- a/tag.c
+++ b/tag.c
@@ -212,3 +212,10 @@  int parse_tag(struct tag *item)
 	free(data);
 	return ret;
 }
+
+struct object_id *get_tagged_oid(struct tag *tag)
+{
+	if (!tag->tagged)
+		die("bad tag");
+	return &tag->tagged->oid;
+}
diff --git a/tag.h b/tag.h
index 03265fbfe2..3ce8e72192 100644
--- a/tag.h
+++ b/tag.h
@@ -19,5 +19,6 @@  struct object *deref_tag(struct repository *r, struct object *, const char *, in
 struct object *deref_tag_noverify(struct object *);
 int gpg_verify_tag(const struct object_id *oid,
 		   const char *name_to_report, unsigned flags);
+struct object_id *get_tagged_oid(struct tag *tag);

 #endif /* TAG_H */