No repro for this. I had a core but lost it. At least saved a backtrace: around commit bb0955ee4db8 #0 atomic_load_u (a=0x8, mo=atomic_memory_order_relaxed) at /usr/src/contrib/jemalloc/include/jemalloc/internal/atomic.h:91 #1 rtree_leaf_elm_read (elm=0x0, dependent=true, tsdn=<optimized out>, rtree=<optimized out>) at /usr/src/contrib/jemalloc/include/jemalloc/internal/rtree.h:247 #2 rtree_metadata_read (tsdn=tsdn@entry=0x3902972120a0, rtree=<optimized out>, rtree_ctx=rtree_ctx@entry=0x390297212250, key=key@entry=62680752717848) at /usr/src/contrib/jemalloc/include/jemalloc/internal/rtree.h:446 #3 0x00000fff04ce256d in emap_alloc_ctx_lookup (tsdn=0x3902972120a0, ptr=0x390200000018, emap=<optimized out>, alloc_ctx=<optimized out>) at /usr/src/contrib/jemalloc/include/jemalloc/internal/emap.h:238 #4 ifree (tsd=0x3902972120a0, ptr=0x390200000018, tcache=0x3902972123f8, slow_path=false) at jemalloc_jemalloc.c:2881 #5 __je_free_default (ptr=0x390200000018) at jemalloc_jemalloc.c:3018 #6 0x00000fff04ce2a74 in __free (ptr=0x3902972120a0) at jemalloc_jemalloc.c:3166 #7 0x00000ff6e1af01e1 in ckfree (p=0x3902972120a0) at /usr/src/bin/sh/memalloc.c:88 #8 0x00000ff6e1aed53a in freejob (jp=jp@entry=0x39029767a000) at /usr/src/bin/sh/jobs.c:518 #9 0x00000ff6e1aecb96 in waitforjob (jp=jp@entry=0x39029767a000, signaled=signaled@entry=0x0) at /usr/src/bin/sh/jobs.c:1110 #10 0x00000ff6e1ae3002 in evalpipe (n=<optimized out>) at /usr/src/bin/sh/eval.c:610 #11 evaltree (n=<optimized out>, flags=flags@entry=2) at /usr/src/bin/sh/eval.c:277 #12 0x00000ff6e1ae2ed9 in evaltree (n=0x3902976166e0, flags=flags@entry=0) at /usr/src/bin/sh/eval.c:233 #13 0x00000ff6e1ae2c87 in evaltree (n=0x3902976162c8, flags=flags@entry=0) at /usr/src/bin/sh/eval.c:204 #14 0x00000ff6e1ae3220 in evalredir (n=n@entry=0x390297616250, flags=flags@entry=0) at /usr/src/bin/sh/eval.c:472 #15 0x00000ff6e1ae2c57 in evaltree (n=0x390297616250, flags=flags@entry=0) at /usr/src/bin/sh/eval.c:223 #16 0x00000ff6e1ae2c87 in evaltree (n=0x3902976160f8, flags=flags@entry=0) at /usr/src/bin/sh/eval.c:204 #17 0x00000ff6e1ae42d4 in evalcommand (cmd=cmd@entry=0x390297651030, flags=<optimized out>, flags@entry=0, backcmd=backcmd@entry=0x0) at /usr/src/bin/sh/eval.c:1045 #18 0x00000ff6e1ae2983 in evaltree (n=0x390297651030, flags=flags@entry=0) at /usr/src/bin/sh/eval.c:281 #19 0x00000ff6e1aefe97 in cmdloop (top=top@entry=1) at /usr/src/bin/sh/main.c:221 #20 0x00000ff6e1aefc68 in main (argc=<optimized out>, argv=<optimized out>) at /usr/src/bin/sh/main.c:168
Hit this with poudriere's git using `while make -j$(nproc) check SH=/bin/sh; do :; done`. If someone is going to hunt it I recommend changing the loop to exit if a new sh core is found as there are some sporadic failures still in the test suite.
Slightly different but easily reproducible with `make DEBUG_FLAGS=-fsanitize=address` in bin/sh. ~/git/freebsd/main2/bin/sh # obj/sh -c 'set -m; sleep 300 | sleep 200 & sleep 300 & kill %1; wait %1' ================================================================= ==64152==ERROR: AddressSanitizer: heap-use-after-free on address 0x503000001344 at pc 0x000001115488 bp 0x7fffffffdb80 sp 0x7fffffffdb78 READ of size 4 at 0x503000001344 thread T0 #0 0x000001115487 in getjobstatus /root/git/freebsd/main2/bin/sh/jobs.c:343:34 #1 0x000001115487 in waitcmdloop /root/git/freebsd/main2/bin/sh/jobs.c:567:14 #2 0x000001114ebc in waitcmd /root/git/freebsd/main2/bin/sh/jobs.c:546:13 #3 0x0000010fe136 in evalcommand /root/git/freebsd/main2/bin/sh/eval.c:1099:16 #4 0x0000010fb209 in evaltree /root/git/freebsd/main2/bin/sh/eval.c:281:4 #5 0x0000010faa2d in evalstring /root/git/freebsd/main2/bin/sh/eval.c #6 0x00000111b969 in main /root/git/freebsd/main2/bin/sh/main.c:164:3 #7 0x00080132133e in __libc_start1 /usr/src/lib/libc/csu/libc_start1.c:180:7 #8 0x000001050300 in _start /usr/src/lib/csu/amd64/crt1_s.S:80 0x503000001344 is located 20 bytes inside of 32-byte region [0x503000001330,0x503000001350) freed by thread T0 here: #0 0x0000010c0836 in free /usr/src/contrib/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:52:3 #1 0x000001114cc5 in freejob /root/git/freebsd/main2/bin/sh/jobs.c:521:3 #2 0x000001118666 in dowait /root/git/freebsd/main2/bin/sh/jobs.c:1233:8 #3 0x000001114fcd in waitcmdloop /root/git/freebsd/main2/bin/sh/jobs.c:602:11 #4 0x000001114ebc in waitcmd /root/git/freebsd/main2/bin/sh/jobs.c:546:13 #5 0x0000010fe136 in evalcommand /root/git/freebsd/main2/bin/sh/eval.c:1099:16 #6 0x0000010fb209 in evaltree /root/git/freebsd/main2/bin/sh/eval.c:281:4 #7 0x0000010faa2d in evalstring /root/git/freebsd/main2/bin/sh/eval.c #8 0x00000111b969 in main /root/git/freebsd/main2/bin/sh/main.c:164:3 #9 0x00080132133e in __libc_start1 /usr/src/lib/libc/csu/libc_start1.c:180:7 #10 0x000001050300 in _start /usr/src/lib/csu/amd64/crt1_s.S:80 #11 0x000801197007 (<unknown module>) previously allocated by thread T0 here: #0 0x0000010c095f in malloc /usr/src/contrib/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:68:3 #1 0x00000111c7ae in ckmalloc /root/git/freebsd/main2/bin/sh/memalloc.c:63:6 #2 0x0000011169f7 in makejob /root/git/freebsd/main2/bin/sh/jobs.c:800:12 #3 0x0000010fb310 in evalpipe /root/git/freebsd/main2/bin/sh/eval.c:573:7 #4 0x0000010fb310 in evaltree /root/git/freebsd/main2/bin/sh/eval.c:277:4 #5 0x0000010fb283 in evaltree /root/git/freebsd/main2/bin/sh/eval.c:204:4 #6 0x0000010faa2d in evalstring /root/git/freebsd/main2/bin/sh/eval.c #7 0x00000111b969 in main /root/git/freebsd/main2/bin/sh/main.c:164:3 #8 0x00080132133e in __libc_start1 /usr/src/lib/libc/csu/libc_start1.c:180:7 #9 0x000001050300 in _start /usr/src/lib/csu/amd64/crt1_s.S:80 #10 0x000801197007 (<unknown module>) SUMMARY: AddressSanitizer: heap-use-after-free /root/git/freebsd/main2/bin/sh/jobs.c:343:34 in getjobstatus Shadow bytes around the buggy address: 0x503000001080: fa fa 00 00 00 00 fa fa 00 00 00 00 fa fa 00 00 0x503000001100: 00 00 fa fa 00 00 00 00 fa fa 00 00 00 00 fa fa 0x503000001180: fd fd fd fa fa fa 00 00 00 00 fa fa 00 00 00 00 0x503000001200: fa fa 00 00 00 00 fa fa 00 00 00 00 fa fa 00 00 0x503000001280: 00 00 fa fa 00 00 00 00 fa fa 00 00 00 07 fa fa =>0x503000001300: 00 00 00 04 fa fa fd fd[fd]fd fa fa 00 00 00 06 0x503000001380: fa fa 00 00 00 05 fa fa 00 00 00 05 fa fa fa fa 0x503000001400: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x503000001480: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x503000001500: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x503000001580: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==64152==ABORTING This avoids it: diff --git bin/sh/jobs.c bin/sh/jobs.c index 1328ae50edef..bb059b44c466 100644 --- bin/sh/jobs.c +++ bin/sh/jobs.c @@ -562,7 +562,7 @@ waitcmdloop(struct job *job) */ do { - if (job != NULL) { + if (job != NULL && job->used != 0) { if (job->state == JOBDONE) { status = getjobstatus(job); if (WIFEXITED(status))
Above patch is wrong functionally.
A commit in branch main references this bug: URL: https://cgit.FreeBSD.org/src/commit/?id=75a6c38e4d5c651b7398bf2bea5baa41a0939e92 commit 75a6c38e4d5c651b7398bf2bea5baa41a0939e92 Author: Jilles Tjoelker <jilles@FreeBSD.org> AuthorDate: 2025-11-15 16:43:03 +0000 Commit: Jilles Tjoelker <jilles@FreeBSD.org> CommitDate: 2025-11-17 18:32:38 +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 bin/sh/jobs.c | 3 ++- bin/sh/tests/builtins/Makefile | 1 + bin/sh/tests/builtins/wait11.0 (new) | 6 ++++++ 3 files changed, 9 insertions(+), 1 deletion(-)
A commit in branch main references this bug: URL: https://cgit.FreeBSD.org/src/commit/?id=f44ac8cc9c10d7305223a10b8dbd8e234388cc73 commit f44ac8cc9c10d7305223a10b8dbd8e234388cc73 Author: Jilles Tjoelker <jilles@FreeBSD.org> AuthorDate: 2025-11-17 17:42:01 +0000 Commit: Jilles Tjoelker <jilles@FreeBSD.org> CommitDate: 2025-11-19 20:30:39 +0000 sh: Fix job pointer invalidation with trapsasync Calling dotrap() can do almost anything, including reallocating the jobtab array. Convert the job pointer to an index before calling dotrap() and then restore a proper job pointer afterwards. PR: 290330 Reported by: bdrewery Reviewed by: bdrewery Differential Revision: https://reviews.freebsd.org/D53793 bin/sh/jobs.c | 6 +++++- bin/sh/tests/execution/Makefile | 1 + bin/sh/tests/execution/bg14.0 (new) | 9 +++++++++ 3 files changed, 15 insertions(+), 1 deletion(-)