Bug 290330 - /bin/sh crash in freejob
Summary: /bin/sh crash in freejob
Status: In Progress
Alias: None
Product: Base System
Classification: Unclassified
Component: bin (show other bugs)
Version: Unspecified
Hardware: Any Any
: --- Affects Only Me
Assignee: freebsd-bugs (Nobody)
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2025-10-17 21:12 UTC by Bryan Drewery
Modified: 2025-11-21 18:08 UTC (History)
1 user (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Bryan Drewery freebsd_committer freebsd_triage 2025-10-17 21:12:40 UTC
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
Comment 1 Bryan Drewery freebsd_committer freebsd_triage 2025-10-17 21:21:13 UTC
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.
Comment 2 Bryan Drewery freebsd_committer freebsd_triage 2025-11-14 22:20:00 UTC
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))
Comment 3 Bryan Drewery freebsd_committer freebsd_triage 2025-11-14 22:21:56 UTC
Above patch is wrong functionally.
Comment 4 commit-hook freebsd_committer freebsd_triage 2025-11-17 18:33:58 UTC
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(-)
Comment 5 commit-hook freebsd_committer freebsd_triage 2025-11-19 20:33:20 UTC
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(-)