git: 6c24c795487d - stable/15 - sh: Fix a double free in a rare scenario with pipes
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sun, 01 Feb 2026 14:29:23 UTC
The branch stable/15 has been updated by jilles:
URL: https://cgit.FreeBSD.org/src/commit/?id=6c24c795487d29defef87bf586eea748274b7758
commit 6c24c795487d29defef87bf586eea748274b7758
Author: Jilles Tjoelker <jilles@FreeBSD.org>
AuthorDate: 2025-11-15 16:43:03 +0000
Commit: Jilles Tjoelker <jilles@FreeBSD.org>
CommitDate: 2026-02-01 14:26:41 +0000
sh: Fix a double free in a rare scenario with pipes
The command
sh -c 'sleep 3 | sleep 2 & sleep 3 & kill %1; wait %1'
crashes (with appropriate sanitization such as putting
MALLOC_CONF=abort:true,junk:true in the environment or compiling with
-fsanitize=address).
What happens here is that waitcmdloop() calls dowait() with a NULL job
pointer, instructing dowait() to freejob() if it's a non-interactive
shell and $! was not and cannot be referenced for it. However,
waitcmdloop() then uses fields possibly freed by freejob() and calls
freejob() again.
This only occurs if the job being waited for is identified via % syntax
($! has never been referenced for it), it is a pipeline with two or more
elements and another background job has been started before the wait
command. That seems special enough for a bug to remain. Test scripts
written by Jilles would almost always use $! and not % syntax.
We can instead make waitcmdloop() pass its job pointer to dowait(),
fixing up things for that (waitcmdloop() will have to call deljob() if
it does not call freejob()).
The crash from
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=290330#c2 appears to
be the same bug.
PR: 290330
Reported by: bdrewery
Reviewed by: bdrewery
Differential Revision: https://reviews.freebsd.org/D53773
(cherry picked from commit 75a6c38e4d5c651b7398bf2bea5baa41a0939e92)
---
bin/sh/jobs.c | 3 ++-
bin/sh/tests/builtins/Makefile | 1 +
bin/sh/tests/builtins/wait11.0 | 6 ++++++
3 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/bin/sh/jobs.c b/bin/sh/jobs.c
index 1328ae50edef..0aaff5e1e140 100644
--- a/bin/sh/jobs.c
+++ b/bin/sh/jobs.c
@@ -573,6 +573,7 @@ waitcmdloop(struct job *job)
freejob(job);
else {
job->remembered = 0;
+ deljob(job);
if (job == bgjob)
bgjob = NULL;
}
@@ -599,7 +600,7 @@ waitcmdloop(struct job *job)
break;
}
}
- } while (dowait(DOWAIT_BLOCK | DOWAIT_SIG, (struct job *)NULL) != -1);
+ } while (dowait(DOWAIT_BLOCK | DOWAIT_SIG, job) != -1);
sig = pendingsig_waitcmd;
pendingsig_waitcmd = 0;
diff --git a/bin/sh/tests/builtins/Makefile b/bin/sh/tests/builtins/Makefile
index b3e353024969..0246009cce81 100644
--- a/bin/sh/tests/builtins/Makefile
+++ b/bin/sh/tests/builtins/Makefile
@@ -189,5 +189,6 @@ ${PACKAGE}FILES+= wait7.0
${PACKAGE}FILES+= wait8.0
${PACKAGE}FILES+= wait9.127
${PACKAGE}FILES+= wait10.0
+${PACKAGE}FILES+= wait11.0
.include <bsd.test.mk>
diff --git a/bin/sh/tests/builtins/wait11.0 b/bin/sh/tests/builtins/wait11.0
new file mode 100644
index 000000000000..d5fab26fb677
--- /dev/null
+++ b/bin/sh/tests/builtins/wait11.0
@@ -0,0 +1,6 @@
+sleep 3 | sleep 2 &
+sleep 3 &
+kill %1
+wait %1
+r=$?
+[ "$r" -gt 128 ] && [ "$(kill -l "$r")" = TERM ]