{"thread":{"id":"51725","subject":"[PATCH] fix segv with corrupt tag object","startedAt":"2019-08-24T23:18:14Z","lastAt":"2019-08-30T16:29:08Z","messageCount":8,"participants":["Stefan Sperling","René Scharfe","Junio C Hamano","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"381083","messageId":"20190824230944.GA14132@jessup.stsp.name","threadId":"51725","inReplyTo":null,"subject":"[PATCH] fix segv with corrupt tag object","fromName":"Stefan Sperling","fromEmail":"stsp@stsp.name","sentAt":"2019-08-24T23:09:44Z","receivedAt":"2019-08-24T23:18:14Z","isPatch":true,"sender":{"key":"stsp@elego.de","avatar":"https://avatars.githubusercontent.com/u/9281333?v=4"},"body":"A tag object which lacks newlines won't be parsed correctly.\nGit fails to detect this error and crashes due to a NULL deref:\n\n$ git archive 1.0.0\nSegmentation fault (core dumped)\n$ git checkout 1.0.0\nSegmentation fault (core dumped)\n$\n\nSee the attached tarball for a reproduction repository.\nAlso mirrored at https://stsp.name/git-checkout-tag-segv-repo.tgz\n\nWith the patch below:\n\n$ git checkout 1.0.0\nfatal: reference is not a tree: 1.0.0\n$ git archive 1.0.0\nfatal: not a tree object: a99665eea5ee50171b5b7249880aa2ae35e35823\n$\n\ndiff --git a/tree.c b/tree.c\nindex 4720945e6a..92d8bd57a3 100644\n--- a/tree.c\n+++ b/tree.c\n@@ -252,9 +252,11 @@ struct tree *parse_tree_indirect(const struct object_id *oid)\n \t\t\treturn (struct tree *) obj;\n \t\telse if (obj->type == OBJ_COMMIT)\n \t\t\tobj = &(get_commit_tree(((struct commit *)obj))->object);\n-\t\telse if (obj->type == OBJ_TAG)\n+\t\telse if (obj->type == OBJ_TAG) {\n \t\t\tobj = ((struct tag *) obj)->tagged;\n-\t\telse\n+\t\t\tif (!obj)\n+\t\t\t\treturn NULL;\n+\t\t} else\n \t\t\treturn NULL;\n \t\tif (!obj->parsed)\n \t\t\tparse_object(the_repository, &obj->oid);\n\n"},{"id":"381091","messageId":"bcc29199-a4ac-6bdc-6715-9807737253d8@web.de","threadId":"51725","inReplyTo":"20190824230944.GA14132@jessup.stsp.name","subject":"Re: [PATCH] fix segv with corrupt tag object","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2019-08-25T07:52:56Z","receivedAt":"2019-08-25T08:06:44Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 25.08.19 um 01:09 schrieb Stefan Sperling:\n> A tag object which lacks newlines won't be parsed correctly.\n> Git fails to detect this error and crashes due to a NULL deref:\n>\n> $ git archive 1.0.0\n> Segmentation fault (core dumped)\n> $ git checkout 1.0.0\n> Segmentation fault (core dumped)\n> $\n\nGood find.\n\n>\n> See the attached tarball for a reproduction repository.\n> Also mirrored at https://stsp.name/git-checkout-tag-segv-repo.tgz\n>\n> With the patch below:\n>\n> $ git checkout 1.0.0\n> fatal: reference is not a tree: 1.0.0\n> $ git archive 1.0.0\n> fatal: not a tree object: a99665eea5ee50171b5b7249880aa2ae35e35823\n> $\n\nSign-off?\n\n>\n> diff --git a/tree.c b/tree.c\n> index 4720945e6a..92d8bd57a3 100644\n> --- a/tree.c\n> +++ b/tree.c\n> @@ -252,9 +252,11 @@ struct tree *parse_tree_indirect(const struct object_id *oid)\n>  \t\t\treturn (struct tree *) obj;\n>  \t\telse if (obj->type == OBJ_COMMIT)\n>  \t\t\tobj = &(get_commit_tree(((struct commit *)obj))->object);\n> -\t\telse if (obj->type == OBJ_TAG)\n> +\t\telse if (obj->type == OBJ_TAG) {\n>  \t\t\tobj = ((struct tag *) obj)->tagged;\n> -\t\telse\n> +\t\t\tif (!obj)\n> +\t\t\t\treturn NULL;\n> +\t\t} else\n\nOK.\n\nThere seem to be some more placed the use ->tagged without\nchecking (found with \"git grep -wW tagged\"):\n\n  builtin/describe.c::describe_commit()\n  builtin/fast-export.c::handle_tag()\n  builtin/log.c::cmd_show()\n  builtin/replace.c::check_one_mergetag()\n  fsck.c::fsck_walk_tag() -- I'm not sure about that one\n  log-tree.c::show_one_mergetag()\n  packfile.c::add_promisor_object()\n  ref-filter.c::populate_value()\n  ref-filter.c::match_points_at()\n  walker.c::process_tag()\n\nUgh!  Do you perhaps want to have a go at them as well?\n\n>  \t\t\treturn NULL;\n>  \t\tif (!obj->parsed)\n>  \t\t\tparse_object(the_repository, &obj->oid);\n>\n\n\nHmm, I find it a bit sad that this function is almost a duplicate of\nsha1-name.c::repo_peel_to_type(), which already checks for ->tagged\nbeing NULL.\n\nRené\n"},{"id":"381170","messageId":"20190826115715.GB71935@jessup.stsp.name","threadId":"51725","inReplyTo":"bcc29199-a4ac-6bdc-6715-9807737253d8@web.de","subject":"Re: [PATCH] fix segv with corrupt tag object","fromName":"Stefan Sperling","fromEmail":"stsp@stsp.name","sentAt":"2019-08-26T11:57:15Z","receivedAt":"2019-08-26T11:57:19Z","isPatch":true,"sender":{"key":"stsp@elego.de","avatar":"https://avatars.githubusercontent.com/u/9281333?v=4"},"body":"On Sun, Aug 25, 2019 at 09:52:56AM +0200, René Scharfe wrote:\n> Am 25.08.19 um 01:09 schrieb Stefan Sperling:\n> > A tag object which lacks newlines won't be parsed correctly.\n> > Git fails to detect this error and crashes due to a NULL deref:\n> >\n> > $ git archive 1.0.0\n> > Segmentation fault (core dumped)\n> > $ git checkout 1.0.0\n> > Segmentation fault (core dumped)\n> > $\n> \n> Good find.\n> \n> >\n> > See the attached tarball for a reproduction repository.\n> > Also mirrored at https://stsp.name/git-checkout-tag-segv-repo.tgz\n> >\n> > With the patch below:\n> >\n> > $ git checkout 1.0.0\n> > fatal: reference is not a tree: 1.0.0\n> > $ git archive 1.0.0\n> > fatal: not a tree object: a99665eea5ee50171b5b7249880aa2ae35e35823\n> > $\n> \n> Sign-off?\n\nAdded in new patch below.\n\n> > diff --git a/tree.c b/tree.c\n> > index 4720945e6a..92d8bd57a3 100644\n> > --- a/tree.c\n> > +++ b/tree.c\n> > @@ -252,9 +252,11 @@ struct tree *parse_tree_indirect(const struct object_id *oid)\n> >  \t\t\treturn (struct tree *) obj;\n> >  \t\telse if (obj->type == OBJ_COMMIT)\n> >  \t\t\tobj = &(get_commit_tree(((struct commit *)obj))->object);\n> > -\t\telse if (obj->type == OBJ_TAG)\n> > +\t\telse if (obj->type == OBJ_TAG) {\n> >  \t\t\tobj = ((struct tag *) obj)->tagged;\n> > -\t\telse\n> > +\t\t\tif (!obj)\n> > +\t\t\t\treturn NULL;\n> > +\t\t} else\n> \n> OK.\n> \n> There seem to be some more placed the use ->tagged without\n> checking (found with \"git grep -wW tagged\"):\n> \n>   builtin/describe.c::describe_commit()\n>   builtin/fast-export.c::handle_tag()\n>   builtin/log.c::cmd_show()\n>   builtin/replace.c::check_one_mergetag()\n>   fsck.c::fsck_walk_tag() -- I'm not sure about that one\n>   log-tree.c::show_one_mergetag()\n>   packfile.c::add_promisor_object()\n>   ref-filter.c::populate_value()\n>   ref-filter.c::match_points_at()\n>   walker.c::process_tag()\n> \n> Ugh!  Do you perhaps want to have a go at them as well?\n\nI think fixing all those places (and future occurrences) would be\nthe wrong approach. Having an incompletely parsed object run\naround in the program is a bad idea in the first place.\n\nThe root cause of this bug seems to be that the valid assumption\nthat obj->parsed implies a successfully parsed object is broken by\nparse_tag_buffer() because this function sets the 'parsed' flag even\nif errors occur during parsing.\n\nSo I think the proper fix would be something like the new patch below.\n\n> >  \t\t\treturn NULL;\n> >  \t\tif (!obj->parsed)\n> >  \t\t\tparse_object(the_repository, &obj->oid);\n> >\n> \n> \n> Hmm, I find it a bit sad that this function is almost a duplicate of\n> sha1-name.c::repo_peel_to_type(), which already checks for ->tagged\n> being NULL.\n\nI'll leave this for someone else to mop up.\nWith the patch below checking ->tagged for NULL becomes redundant.\nCorrect code should be checking for parse errors and/or ->parsed instead.\n\nRegards,\nStefan\n\nFrom b1928cf610f44a2453c1b68b915e6de071c0c01d Mon Sep 17 00:00:00 2001\nFrom: Stefan Sperling <stsp@stsp.name>\nDate: Mon, 26 Aug 2019 13:08:20 +0200\nSubject: [PATCH] do not mark invalid tag objects as 'parsed'\n\nPrevents segfaults due to use of incompletely parsed tag objects,\nas observed e.g. when 'git checkout' is used with a corrupt tag\nobject which lacks newline characters.\n\nAlways error out for tags which don't have a known object type and hence\ncannot be resolved. Callers of parse_tag_buffer() will crash trying to\ndereference a NULL tag->tagged pointer.\n\nSigned-off-by: Stefan Sperling <stsp@stsp.name>\n---\n tag.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/tag.c b/tag.c\nindex 5db870edb9..74d0cee34e 100644\n--- a/tag.c\n+++ b/tag.c\n@@ -141,7 +141,6 @@ int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, u\n \n \tif (item->object.parsed)\n \t\treturn 0;\n-\titem->object.parsed = 1;\n \n \tif (size < the_hash_algo->hexsz + 24)\n \t\treturn -1;\n@@ -167,8 +166,8 @@ int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, u\n \t} else if (!strcmp(type, tag_type)) {\n \t\titem->tagged = (struct object *)lookup_tag(r, &oid);\n \t} else {\n-\t\terror(\"Unknown type %s\", type);\n \t\titem->tagged = NULL;\n+\t\treturn error(\"Unknown type %s\", type);\n \t}\n \n \tif (bufptr + 4 < tail && starts_with(bufptr, \"tag \"))\n@@ -187,6 +186,7 @@ int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, u\n \telse\n \t\titem->date = 0;\n \n+\titem->object.parsed = 1;\n \treturn 0;\n }\n \n-- \n2.22.0\n\n"},{"id":"381219","messageId":"xmqqo90bhmi3.fsf@gitster-ct.c.googlers.com","threadId":"51725","inReplyTo":"20190826115715.GB71935@jessup.stsp.name","subject":"Re: [PATCH] fix segv with corrupt tag object","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-26T17:20:20Z","receivedAt":"2019-08-26T17:20:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Sperling <stsp@stsp.name> writes:\n\n> The root cause of this bug seems to be that the valid assumption\n> that obj->parsed implies a successfully parsed object is broken by\n> parse_tag_buffer() because this function sets the 'parsed' flag even\n> if errors occur during parsing.\n\nI am mildly negative about that approach.  obj->parsed is about\n\"we've done all we need to do to attempt parsing this object\" (so\nthat next person who gets hold of the object knows that fact---one\nof the reasons why may be that the caller who wants to ensure that\nthe fields are ready to be accessed does not have to spend extra\ncycles, but that is not the only one).  Those that want to look at\nvarious fields in the object (e.g. the tagged object of a tag, the\ntagger identity of a tag, etc.) should be prepared to see and react\nto NULL in there so that they can gracefully handle \"slightly\"\ncorrupt objects.\n\n"},{"id":"381221","messageId":"20190826180237.GN14213@ted.stsp.name","threadId":"51725","inReplyTo":"xmqqo90bhmi3.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH] fix segv with corrupt tag object","fromName":"Stefan Sperling","fromEmail":"stsp@stsp.name","sentAt":"2019-08-26T18:02:37Z","receivedAt":"2019-08-26T18:02:46Z","isPatch":true,"sender":{"key":"stsp@elego.de","avatar":"https://avatars.githubusercontent.com/u/9281333?v=4"},"body":"On Mon, Aug 26, 2019 at 10:20:20AM -0700, Junio C Hamano wrote:\n> Stefan Sperling <stsp@stsp.name> writes:\n> \n> > The root cause of this bug seems to be that the valid assumption\n> > that obj->parsed implies a successfully parsed object is broken by\n> > parse_tag_buffer() because this function sets the 'parsed' flag even\n> > if errors occur during parsing.\n> \n> I am mildly negative about that approach.  obj->parsed is about\n> \"we've done all we need to do to attempt parsing this object\" (so\n> that next person who gets hold of the object knows that fact---one\n> of the reasons why may be that the caller who wants to ensure that\n> the fields are ready to be accessed does not have to spend extra\n> cycles, but that is not the only one).  Those that want to look at\n> various fields in the object (e.g. the tagged object of a tag, the\n> tagger identity of a tag, etc.) should be prepared to see and react\n> to NULL in there so that they can gracefully handle \"slightly\"\n> corrupt objects.\n> \n\nI will respectfully agree to disagree :-)\nIf an object is corrupt the repository is broken and should be fixed.\nNow, if this code was running in a tool which intends to fix up such\nproblems, sure, let it handle corrupt objects. But I don't see the point\nof complicating code all over the place just to have the main tool's\nintended functionality partly working in face of corruption.\n\nThat said, since you state that the 'parsed' flag already carries a\ndifferent meaning than I was assuming it did, my patch is wrong and\nshould be rewritten by someone else who can fully make sense of the\nexisting internals.\n"},{"id":"381226","messageId":"20190826181824.GA22960@sigill.intra.peff.net","threadId":"51725","inReplyTo":"xmqqo90bhmi3.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH] fix segv with corrupt tag object","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2019-08-26T18:18:24Z","receivedAt":"2019-08-26T18:18:27Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Aug 26, 2019 at 10:20:20AM -0700, Junio C Hamano wrote:\n\n> Stefan Sperling <stsp@stsp.name> writes:\n> \n> > The root cause of this bug seems to be that the valid assumption\n> > that obj->parsed implies a successfully parsed object is broken by\n> > parse_tag_buffer() because this function sets the 'parsed' flag even\n> > if errors occur during parsing.\n> \n> I am mildly negative about that approach.  obj->parsed is about\n> \"we've done all we need to do to attempt parsing this object\" (so\n> that next person who gets hold of the object knows that fact---one\n> of the reasons why may be that the caller who wants to ensure that\n> the fields are ready to be accessed does not have to spend extra\n> cycles, but that is not the only one).  Those that want to look at\n> various fields in the object (e.g. the tagged object of a tag, the\n> tagger identity of a tag, etc.) should be prepared to see and react\n> to NULL in there so that they can gracefully handle \"slightly\"\n> corrupt objects.\n\nIt seems like the right place to notice \"we did not parse correctly\" is\nan error return from parse_tag_buffer(). We're not calling it ourselves\nin this instance, but it looks like it does get propagated from\nparse_object(), which would yield NULL.\n\nI wonder if some earlier caller in checkout/archive is ignoring a parse\nfailure, and continuing to work with the object anyway.\n\nAvoiding setting the parse flag is a cheap way to make sure that the\nlater calls re-attempt the parse and notice the error themselves. That\nwastes some work in the case of a bogus tag, but callers who want to\nview the corrupted state aren't really any worse off.\n\nThat said, the error condition touched by Stefan's updated patch is not\nsufficient to guarantee that tag->tagged is non-NULL (whether we detect\nthe error case by return code or by lack of \"parsed\" flag). The code\ndoes this:\n\n          if (!strcmp(type, blob_type)) {\n                  item->tagged = (struct object *)lookup_blob(r, &oid);\n          } else if (!strcmp(type, tree_type)) {\n                  item->tagged = (struct object *)lookup_tree(r, &oid);\n          } else if (!strcmp(type, commit_type)) {\n                  item->tagged = (struct object *)lookup_commit(r, &oid);\n          } else if (!strcmp(type, tag_type)) {\n                  item->tagged = (struct object *)lookup_tag(r, &oid);\n          } else {\n                  error(\"Unknown type %s\", type);\n                  item->tagged = NULL;\n          }\n\nAny of those lookup_* functions may also return. It's relatively rare,\nsince we don't actually confirm the type against the object database at\nthat time. But it can happen if the same program already saw that\nparticular oid as another type. This is tricky to trigger for\ncheckout/archive because they generally parse the tag first (but not\nimpossible; e.g., some config like mailmap.blob may read objects early).\nBut anything using the revision parser is happy to read multiple\nobjects.\n\nIf we want to cover all cases, probably something like:\n\n  if (!item->tagged)\n\tret = -1;\n\nwould be simplest.\n\n-Peff\n"},{"id":"381536","messageId":"c6601cca-7de0-ba82-2e18-916a2e9048d3@web.de","threadId":"51725","inReplyTo":"xmqqo90bhmi3.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH] fix segv with corrupt tag object","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2019-08-29T19:06:22Z","receivedAt":"2019-08-29T19:06:36Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 26.08.19 um 19:20 schrieb Junio C Hamano:\n> Stefan Sperling <stsp@stsp.name> writes:\n>\n>> The root cause of this bug seems to be that the valid assumption\n>> that obj->parsed implies a successfully parsed object is broken by\n>> parse_tag_buffer() because this function sets the 'parsed' flag even\n>> if errors occur during parsing.\n>\n> I am mildly negative about that approach.  obj->parsed is about\n> \"we've done all we need to do to attempt parsing this object\" (so\n> that next person who gets hold of the object knows that fact---one\n> of the reasons why may be that the caller who wants to ensure that\n> the fields are ready to be accessed does not have to spend extra\n> cycles, but that is not the only one).  Those that want to look at\n> various fields in the object (e.g. the tagged object of a tag, the\n> tagger identity of a tag, etc.) should be prepared to see and react\n> to NULL in there so that they can gracefully handle \"slightly\"\n> corrupt objects.\n\nNot sure how this could happen under normal circumstances, but how\nabout this here?\n\n-- >8 --\nSubject: [PATCH] tree: simplify parse_tree_indirect()\n\nReduce code duplication by turning parse_tree_indirect() into a wrapper\nof repo_peel_to_type().  This avoids a segfault when handling a broken\ntag where ->tagged is NULL.  The new version also checks the return\nvalue of parse_object() that was ignored by the old one.\n\nInitial-patch-by: Stefan Sperling <stsp@stsp.name>\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n tree.c | 18 +++---------------\n 1 file changed, 3 insertions(+), 15 deletions(-)\n\ndiff --git a/tree.c b/tree.c\nindex 4720945e6a..1466bcc6a8 100644\n--- a/tree.c\n+++ b/tree.c\n@@ -244,19 +244,7 @@ void free_tree_buffer(struct tree *tree)\n\n struct tree *parse_tree_indirect(const struct object_id *oid)\n {\n-\tstruct object *obj = parse_object(the_repository, oid);\n-\tdo {\n-\t\tif (!obj)\n-\t\t\treturn NULL;\n-\t\tif (obj->type == OBJ_TREE)\n-\t\t\treturn (struct tree *) obj;\n-\t\telse if (obj->type == OBJ_COMMIT)\n-\t\t\tobj = &(get_commit_tree(((struct commit *)obj))->object);\n-\t\telse if (obj->type == OBJ_TAG)\n-\t\t\tobj = ((struct tag *) obj)->tagged;\n-\t\telse\n-\t\t\treturn NULL;\n-\t\tif (!obj->parsed)\n-\t\t\tparse_object(the_repository, &obj->oid);\n-\t} while (1);\n+\tstruct repository *r = the_repository;\n+\tstruct object *obj = parse_object(r, oid);\n+\treturn (struct tree *)repo_peel_to_type(r, NULL, 0, obj, OBJ_TREE);\n }\n--\n2.23.0\n"},{"id":"381590","messageId":"xmqqh85yzkfk.fsf@gitster-ct.c.googlers.com","threadId":"51725","inReplyTo":"c6601cca-7de0-ba82-2e18-916a2e9048d3@web.de","subject":"Re: [PATCH] fix segv with corrupt tag object","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-30T16:29:03Z","receivedAt":"2019-08-30T16:29:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"René Scharfe <l.s.r@web.de> writes:\n\n> Subject: [PATCH] tree: simplify parse_tree_indirect()\n>\n> Reduce code duplication by turning parse_tree_indirect() into a wrapper\n> of repo_peel_to_type().  This avoids a segfault when handling a broken\n> tag where ->tagged is NULL.  The new version also checks the return\n> value of parse_object() that was ignored by the old one.\n>\n> Initial-patch-by: Stefan Sperling <stsp@stsp.name>\n> Signed-off-by: René Scharfe <l.s.r@web.de>\n> ---\n>  tree.c | 18 +++---------------\n>  1 file changed, 3 insertions(+), 15 deletions(-)\n>\n> diff --git a/tree.c b/tree.c\n> index 4720945e6a..1466bcc6a8 100644\n> --- a/tree.c\n> +++ b/tree.c\n> @@ -244,19 +244,7 @@ void free_tree_buffer(struct tree *tree)\n>\n>  struct tree *parse_tree_indirect(const struct object_id *oid)\n>  {\n> -\tstruct object *obj = parse_object(the_repository, oid);\n> -\tdo {\n> -\t\tif (!obj)\n> -\t\t\treturn NULL;\n> -\t\tif (obj->type == OBJ_TREE)\n> -\t\t\treturn (struct tree *) obj;\n> -\t\telse if (obj->type == OBJ_COMMIT)\n> -\t\t\tobj = &(get_commit_tree(((struct commit *)obj))->object);\n> -\t\telse if (obj->type == OBJ_TAG)\n> -\t\t\tobj = ((struct tag *) obj)->tagged;\n> -\t\telse\n> -\t\t\treturn NULL;\n> -\t\tif (!obj->parsed)\n> -\t\t\tparse_object(the_repository, &obj->oid);\n> -\t} while (1);\n> +\tstruct repository *r = the_repository;\n> +\tstruct object *obj = parse_object(r, oid);\n> +\treturn (struct tree *)repo_peel_to_type(r, NULL, 0, obj, OBJ_TREE);\n>  }\n\nLooks quite sensible to me; it is too simple that it makes me\nworried that I might be missing something huge.\n\n"}]}