Skip to content

Commit 7559a1b

Browse files
Patrick Reynoldsgitster
authored andcommitted
unblock and unignore SIGPIPE
Blocked and ignored signals -- but not caught signals -- are inherited across exec. Some callers with sloppy signal-handling behavior can call git with SIGPIPE blocked or ignored, even non-deterministically. When SIGPIPE is blocked or ignored, several git commands can run indefinitely, ignoring EPIPE returns from write() calls, even when the process that called them has gone away. Our specific case involved a pipe of git diff-tree output to a script that reads a limited amount of diff data. In an ideal world, git would never be called with SIGPIPE blocked or ignored. But in the real world, several real potential callers, including Perl, Apache, and Unicorn, sometimes spawn subprocesses with SIGPIPE ignored. It is easier and more productive to harden git against this mistake than to clean it up in every potential parent process. Signed-off-by: Patrick Reynolds <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 96db324 commit 7559a1b

File tree

2 files changed

+44
-0
lines changed

2 files changed

+44
-0
lines changed

git.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,26 @@ static int run_argv(int *argcp, const char ***argv)
592592
return done_alias;
593593
}
594594

595+
/*
596+
* Many parts of Git have subprograms communicate via pipe, expect the
597+
* upstream of a pipe to die with SIGPIPE when the downstream of a
598+
* pipe does not need to read all that is written. Some third-party
599+
* programs that ignore or block SIGPIPE for their own reason forget
600+
* to restore SIGPIPE handling to the default before spawning Git and
601+
* break this carefully orchestrated machinery.
602+
*
603+
* Restore the way SIGPIPE is handled to default, which is what we
604+
* expect.
605+
*/
606+
static void restore_sigpipe_to_default(void)
607+
{
608+
sigset_t unblock;
609+
610+
sigemptyset(&unblock);
611+
sigaddset(&unblock, SIGPIPE);
612+
sigprocmask(SIG_UNBLOCK, &unblock, NULL);
613+
signal(SIGPIPE, SIG_DFL);
614+
}
595615

596616
int main(int argc, char **av)
597617
{
@@ -611,6 +631,8 @@ int main(int argc, char **av)
611631
*/
612632
sanitize_stdfds();
613633

634+
restore_sigpipe_to_default();
635+
614636
git_setup_gettext();
615637

616638
trace_command_performance(argv);

t/t0005-signals.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,26 @@ test_expect_success !MINGW 'signals are propagated using shell convention' '
2727
test_expect_code 143 git sigterm
2828
'
2929

30+
large_git () {
31+
for i in $(test_seq 1 100)
32+
do
33+
git diff --cached --binary || return
34+
done
35+
}
36+
37+
test_expect_success 'create blob' '
38+
test-genrandom foo 16384 >file &&
39+
git add file
40+
'
41+
42+
test_expect_success 'a constipated git dies with SIGPIPE' '
43+
OUT=$( ((large_git; echo $? 1>&3) | :) 3>&1 )
44+
test "$OUT" -eq 141
45+
'
46+
47+
test_expect_success 'a constipated git dies with SIGPIPE even if parent ignores it' '
48+
OUT=$( ((trap "" PIPE; large_git; echo $? 1>&3) | :) 3>&1 )
49+
test "$OUT" -eq 141
50+
'
51+
3052
test_done

0 commit comments

Comments
 (0)