{"thread":{"id":"58269","subject":"[bug] Segfault in git commit when a hook has a broken shebang","startedAt":"2022-08-05T05:53:20Z","lastAt":"2022-08-05T16:13:04Z","messageCount":5,"participants":["Ilya K","Đoàn Trần Công Danh","Ævar Arnfjörð Bjarmason","Emily Shaffer"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"460695","messageId":"30015309-00f1-9b44-023c-001ee3f242e4@0upti.me","threadId":"58269","inReplyTo":null,"subject":"[bug] Segfault in git commit when a hook has a broken shebang","fromName":"Ilya K","fromEmail":"me@0upti.me","sentAt":"2022-08-05T05:45:02Z","receivedAt":"2022-08-05T05:53:20Z","isPatch":false,"sender":{"key":"me@0upti.me","avatar":null},"body":"Hello! I ran into a weird bug just now that is probably easier to show \nthan explain:\n❯ git init\nInitialized empty Git repository in /home/k900/test/.git/\n❯ echo '#!/usr/bin/oops' > .git/hooks/pre-commit\n❯ chmod +x .git/hooks/pre-commit\n❯ touch oops\n❯ git add oops\n❯ git commit -a\nfatal: cannot run .git/hooks/pre-commit: No such file or directory\n[1]    24580 segmentation fault (core dumped)  git commit -a\nThis happens consistently with git 2.37.x, and I don't think it happened \nwith git 2.36 or earlier.\n\nI'm not sure if this has been reported already, but a quick search of \nthe list archives didn't get me anywhere, so apologies if this is a \nduplicate.\nThanks!\n\n"},{"id":"460696","messageId":"YuzML2lVGDBIK1uF@danh.dev","threadId":"58269","inReplyTo":"30015309-00f1-9b44-023c-001ee3f242e4@0upti.me","subject":"Re: [bug] Segfault in git commit when a hook has a broken shebang","fromName":"Đoàn Trần Công Danh","fromEmail":"congdanhqx@gmail.com","sentAt":"2022-08-05T07:52:15Z","receivedAt":"2022-08-05T07:52:22Z","isPatch":false,"sender":{"key":"congdanhqx@gmail.com","avatar":"https://avatars.githubusercontent.com/u/42673067?v=4"},"body":"On 2022-08-05 08:45:02+0300, Ilya K <me@0upti.me> wrote:\n> Hello! I ran into a weird bug just now that is probably easier to show than\n> explain:\n> ❯ git init\n> Initialized empty Git repository in /home/k900/test/.git/\n> ❯ echo '#!/usr/bin/oops' > .git/hooks/pre-commit\n> ❯ chmod +x .git/hooks/pre-commit\n> ❯ touch oops\n> ❯ git add oops\n> ❯ git commit -a\n> fatal: cannot run .git/hooks/pre-commit: No such file or directory\n> [1]    24580 segmentation fault (core dumped)  git commit -a\n> This happens consistently with git 2.37.x, and I don't think it happened\n> with git 2.36 or earlier.\n\nThis seems to be a side-effect of a082345372, (hook API: fix v2.36.0\nregression: hooks should be connected to a TTY, 2022-06-07)\n\nSince it makes hooks run in \"ungroup\" manner, hence run-command will\npass NULL as first argument to notify_start_failure.\n\nThis patch seems to fix the crash, however, I think we should remove\nthat clause entirely.\n\n---- 8< -------\n\n hook.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/hook.c b/hook.c\nindex d113ee7faa..dc54a2ac67 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -84,8 +84,8 @@ static int notify_start_failure(struct strbuf *out,\n \n \thook_cb->rc |= 1;\n \n-\tstrbuf_addf(out, _(\"Couldn't start hook '%s'\\n\"),\n-\t\t    hook_path);\n+\tfprintf(stderr, _(\"Couldn't start hook '%s'\\n\"),\n+\t\thook_path);\n \n \treturn 1;\n }\n----- >8 --------------\n\n-- \nDanh\n"},{"id":"460699","messageId":"220805.86pmhfru8a.gmgdl@evledraar.gmail.com","threadId":"58269","inReplyTo":"YuzML2lVGDBIK1uF@danh.dev","subject":"Re: [bug] Segfault in git commit when a hook has a broken shebang","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-08-05T08:04:46Z","receivedAt":"2022-08-05T08:05:23Z","isPatch":false,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Aug 05 2022, Đoàn Trần Công Danh wrote:\n\n> On 2022-08-05 08:45:02+0300, Ilya K <me@0upti.me> wrote:\n>> Hello! I ran into a weird bug just now that is probably easier to show than\n>> explain:\n>> ❯ git init\n>> Initialized empty Git repository in /home/k900/test/.git/\n>> ❯ echo '#!/usr/bin/oops' > .git/hooks/pre-commit\n>> ❯ chmod +x .git/hooks/pre-commit\n>> ❯ touch oops\n>> ❯ git add oops\n>> ❯ git commit -a\n>> fatal: cannot run .git/hooks/pre-commit: No such file or directory\n>> [1]    24580 segmentation fault (core dumped)  git commit -a\n>> This happens consistently with git 2.37.x, and I don't think it happened\n>> with git 2.36 or earlier.\n>\n> This seems to be a side-effect of a082345372, (hook API: fix v2.36.0\n> regression: hooks should be connected to a TTY, 2022-06-07)\n>\n> Since it makes hooks run in \"ungroup\" manner, hence run-command will\n> pass NULL as first argument to notify_start_failure.\n>\n> This patch seems to fix the crash, however, I think we should remove\n> that clause entirely.\n\nYes, thanks. I'm already working on a patch for this with a test, will\nsend it in shortly...\n\n"},{"id":"460701","messageId":"220805.86les3rt7u.gmgdl@evledraar.gmail.com","threadId":"58269","inReplyTo":"220805.86pmhfru8a.gmgdl@evledraar.gmail.com","subject":"Re: [bug] Segfault in git commit when a hook has a broken shebang","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-08-05T08:24:43Z","receivedAt":"2022-08-05T08:27:07Z","isPatch":false,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Aug 05 2022, Ævar Arnfjörð Bjarmason wrote:\n\n> On Fri, Aug 05 2022, Đoàn Trần Công Danh wrote:\n>\n>> On 2022-08-05 08:45:02+0300, Ilya K <me@0upti.me> wrote:\n>>> Hello! I ran into a weird bug just now that is probably easier to show than\n>>> explain:\n>>> ❯ git init\n>>> Initialized empty Git repository in /home/k900/test/.git/\n>>> ❯ echo '#!/usr/bin/oops' > .git/hooks/pre-commit\n>>> ❯ chmod +x .git/hooks/pre-commit\n>>> ❯ touch oops\n>>> ❯ git add oops\n>>> ❯ git commit -a\n>>> fatal: cannot run .git/hooks/pre-commit: No such file or directory\n>>> [1]    24580 segmentation fault (core dumped)  git commit -a\n>>> This happens consistently with git 2.37.x, and I don't think it happened\n>>> with git 2.36 or earlier.\n>>\n>> This seems to be a side-effect of a082345372, (hook API: fix v2.36.0\n>> regression: hooks should be connected to a TTY, 2022-06-07)\n>>\n>> Since it makes hooks run in \"ungroup\" manner, hence run-command will\n>> pass NULL as first argument to notify_start_failure.\n>>\n>> This patch seems to fix the crash, however, I think we should remove\n>> that clause entirely.\n>\n> Yes, thanks. I'm already working on a patch for this with a test, will\n> send it in shortly...\n\nFWIW this is the fix I (in parallel to yours) came up with. Currently\nCI-ing it (I vaguely expect that the particularl of the test will break\non Windows).\n\n(I see I forgot the \"Reported-by\", will add that to the final\nversion...)\n\n-- >8 --\nSubject: [PATCH] hook API: don't segfault on strbuf_addf() to NULL \"out\"\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n\nFix a logic error in a082345372e (hook API: fix v2.36.0 regression:\nhooks should be connected to a TTY, 2022-06-07). When it started using\nthe \"ungroup\" API added in fd3aaf53f71 (run-command: add an \"ungroup\"\noption to run_process_parallel(), 2022-06-07) it should have made the\nsame sort of change that fd3aaf53f71 itself made in\n\"t/helper/test-run-command.c\".\n\nThe correct way to emit this \"Couldn't start\" output with \"ungroup\"\nwould be:\n\n\tfprintf(stderr, _(\"Couldn't start hook '%s'\\n\"), hook_path);\n\nBut we should instead remove the emitting of this output. As the added\ntest shows we already emit output when we can't run the child. The\n\"cannot run\" output here is emitted by run-command.c's\nchild_err_spew().\n\nSo the addition of the \"Couldn't start hook\" output here in\n96e7225b310 (hook: add 'run' subcommand, 2021-12-22) was always\nredundant. For the pre-commit hook we'll now emit exactly the same\noutput as we did before f443246b9f2 (commit: convert\n{pre-commit,prepare-commit-msg} hook to hook.h, 2021-12-22) (and\nlikewise for others).\n\nWe could at this point add this to the pick_next_hook() callbacks in\nhook.c:\n\n\tassert(!out);\n\tassert(!*pp_task_cb);\n\nAnd this to notify_start_failure() and notify_hook_finished() (in the\nlatter case the parameter is called \"pp_task_cp\"):\n\n\tassert(!out);\n\tassert(!pp_task_cb);\n\nBut let's leave any such instrumentation for some eventual cleanup of\nthe \"ungroup\" API.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n hook.c          |  7 -------\n t/t1800-hook.sh | 15 +++++++++++++++\n 2 files changed, 15 insertions(+), 7 deletions(-)\n\ndiff --git a/hook.c b/hook.c\nindex d113ee7faae..a493939a4fc 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -62,9 +62,6 @@ static int pick_next_hook(struct child_process *cp,\n \tstrvec_push(&cp->args, hook_path);\n \tstrvec_pushv(&cp->args, hook_cb->options->args.v);\n \n-\t/* Provide context for errors if necessary */\n-\t*pp_task_cb = (char *)hook_path;\n-\n \t/*\n \t * This pick_next_hook() will be called again, we're only\n \t * running one hook, so indicate that no more work will be\n@@ -80,13 +77,9 @@ static int notify_start_failure(struct strbuf *out,\n \t\t\t\tvoid *pp_task_cp)\n {\n \tstruct hook_cb_data *hook_cb = pp_cb;\n-\tconst char *hook_path = pp_task_cp;\n \n \thook_cb->rc |= 1;\n \n-\tstrbuf_addf(out, _(\"Couldn't start hook '%s'\\n\"),\n-\t\t    hook_path);\n-\n \treturn 1;\n }\n \ndiff --git a/t/t1800-hook.sh b/t/t1800-hook.sh\nindex 210f4298872..75f72ffbf27 100755\n--- a/t/t1800-hook.sh\n+++ b/t/t1800-hook.sh\n@@ -151,4 +151,19 @@ test_expect_success TTY 'git commit: stdout and stderr are connected to a TTY' '\n \ttest_hook_tty commit -m\"B.new\"\n '\n \n+test_expect_success 'git hook run a hook with a bad shebang' '\n+\ttest_when_finished \"rm -rf bad-hooks\" &&\n+\tmkdir bad-hooks &&\n+\twrite_script bad-hooks/test-hook \"/bad/path/no/spaces\" </dev/null &&\n+\tcat >expect <<-\\EOF &&\n+\tfatal: cannot run bad-hooks/test-hook: ...\n+\tEOF\n+\ttest_expect_code 1 git \\\n+\t\t-c core.hooksPath=bad-hooks \\\n+\t\thook run test-hook >out 2>actual.raw &&\n+\ttest_must_be_empty out &&\n+\tsed -e \"s/test-hook: .*/test-hook: .../\" <actual.raw >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n"},{"id":"460719","messageId":"CAJoAoZmQdXhSU+y2HdDFpanS=8W5HEmiShKfBDdEMbC60aXPoQ@mail.gmail.com","threadId":"58269","inReplyTo":"220805.86les3rt7u.gmgdl@evledraar.gmail.com","subject":"Re: [bug] Segfault in git commit when a hook has a broken shebang","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2022-08-05T16:12:49Z","receivedAt":"2022-08-05T16:13:04Z","isPatch":false,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Fri, Aug 5, 2022 at 1:27 AM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n> -- >8 --\n> Subject: [PATCH] hook API: don't segfault on strbuf_addf() to NULL \"out\"\n> MIME-Version: 1.0\n> Content-Type: text/plain; charset=UTF-8\n> Content-Transfer-Encoding: 8bit\n>\n> Fix a logic error in a082345372e (hook API: fix v2.36.0 regression:\n> hooks should be connected to a TTY, 2022-06-07). When it started using\n> the \"ungroup\" API added in fd3aaf53f71 (run-command: add an \"ungroup\"\n> option to run_process_parallel(), 2022-06-07) it should have made the\n> same sort of change that fd3aaf53f71 itself made in\n> \"t/helper/test-run-command.c\".\n>\n> The correct way to emit this \"Couldn't start\" output with \"ungroup\"\n> would be:\n>\n>         fprintf(stderr, _(\"Couldn't start hook '%s'\\n\"), hook_path);\n>\n> But we should instead remove the emitting of this output. As the added\n> test shows we already emit output when we can't run the child. The\n> \"cannot run\" output here is emitted by run-command.c's\n> child_err_spew().\n\nNice, thanks. And child_err_spew() gives argv[0] in most cases, so\nit's the same context as we'd get from this line anyway.\n\n>\n> So the addition of the \"Couldn't start hook\" output here in\n> 96e7225b310 (hook: add 'run' subcommand, 2021-12-22) was always\n> redundant. For the pre-commit hook we'll now emit exactly the same\n> output as we did before f443246b9f2 (commit: convert\n> {pre-commit,prepare-commit-msg} hook to hook.h, 2021-12-22) (and\n> likewise for others).\n>\n> We could at this point add this to the pick_next_hook() callbacks in\n> hook.c:\n>\n>         assert(!out);\n>         assert(!*pp_task_cb);\n>\n> And this to notify_start_failure() and notify_hook_finished() (in the\n> latter case the parameter is called \"pp_task_cp\"):\n>\n>         assert(!out);\n>         assert(!pp_task_cb);\n>\n> But let's leave any such instrumentation for some eventual cleanup of\n> the \"ungroup\" API.\n>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nReviewed-by: Emily Shaffer <emilyshaffer@google.com>\n> ---\n>  hook.c          |  7 -------\n>  t/t1800-hook.sh | 15 +++++++++++++++\n>  2 files changed, 15 insertions(+), 7 deletions(-)\n>\n> diff --git a/hook.c b/hook.c\n> index d113ee7faae..a493939a4fc 100644\n> --- a/hook.c\n> +++ b/hook.c\n> @@ -62,9 +62,6 @@ static int pick_next_hook(struct child_process *cp,\n>         strvec_push(&cp->args, hook_path);\n>         strvec_pushv(&cp->args, hook_cb->options->args.v);\n>\n> -       /* Provide context for errors if necessary */\n> -       *pp_task_cb = (char *)hook_path;\n> -\n>         /*\n>          * This pick_next_hook() will be called again, we're only\n>          * running one hook, so indicate that no more work will be\n> @@ -80,13 +77,9 @@ static int notify_start_failure(struct strbuf *out,\n>                                 void *pp_task_cp)\n>  {\n>         struct hook_cb_data *hook_cb = pp_cb;\n> -       const char *hook_path = pp_task_cp;\n>\n>         hook_cb->rc |= 1;\n>\n> -       strbuf_addf(out, _(\"Couldn't start hook '%s'\\n\"),\n> -                   hook_path);\n> -\n>         return 1;\n>  }\n>\n> diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh\n> index 210f4298872..75f72ffbf27 100755\n> --- a/t/t1800-hook.sh\n> +++ b/t/t1800-hook.sh\n> @@ -151,4 +151,19 @@ test_expect_success TTY 'git commit: stdout and stderr are connected to a TTY' '\n>         test_hook_tty commit -m\"B.new\"\n>  '\n>\n> +test_expect_success 'git hook run a hook with a bad shebang' '\n> +       test_when_finished \"rm -rf bad-hooks\" &&\n> +       mkdir bad-hooks &&\n> +       write_script bad-hooks/test-hook \"/bad/path/no/spaces\" </dev/null &&\n> +       cat >expect <<-\\EOF &&\n> +       fatal: cannot run bad-hooks/test-hook: ...\n> +       EOF\n> +       test_expect_code 1 git \\\n> +               -c core.hooksPath=bad-hooks \\\n> +               hook run test-hook >out 2>actual.raw &&\n> +       test_must_be_empty out &&\n> +       sed -e \"s/test-hook: .*/test-hook: .../\" <actual.raw >actual &&\n> +       test_cmp expect actual\n> +'\n> +\n>  test_done\n> --\n"}]}