From 00f81c5aa8764d84e3e033d3d9d3e9040c842258 Mon Sep 17 00:00:00 2001 From: "lei.chen" Date: Thu, 29 Oct 2026 03:22:31 +0000 Subject: [PATCH] fix(testsuite): declare makeResource properly + symmetric teardown in external-skips-inuse test bug-1979: test.c never included resobj.h, so makeResource() was an implicitly declared function in this TU -- its 64-bit pointer return was truncated to int (0x50b000000118 -> 0x118) and sign-extended back. Dormant since E2 Phase 1 (the test only TEST_ASSERT_NOT_NULL'd it and re-derived the real pointer via key_find); the leak-fix teardown's dereference(made) was its first real use and SEGV'd under ASAN_OPTIONS=detect_leaks=1 (the xce-build.sh all direct-run step). - include resobj.h: makeResource now declared with its true prototype - unmeet: memset-only -- sched_check_shared_satisfy_() xvec_idval_init()s it itself; the pre-init leaked the vector it replaced (2x4B per call) - uncomitted: single xvec_init/xvec_fini pair (was init'd twice, fini'd once) - teardown: collect-then-dereference the refcount-1 resource object, then destroy(resmgr), then xstring_table_fini() -- mirrors xce_sched_core_fini() (core.c:405) Verified: ASAN_OPTIONS=detect_leaks=1 direct run rc=0, 17/17 PASS, zero leak reports; xce-build.sh all green (137/137 suites + manual sched_test + Go tests). Co-Authored-By: Claude Code --- .wolf/buglog.json | 24 +++++++++ .wolf/bugs/bug-1979.md | 92 ++++++++++++++++++++++++++++++++++ src/scheduler/testsuite/test.c | 14 ++++-- 3 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 .wolf/bugs/bug-1979.md diff --git a/.wolf/buglog.json b/.wolf/buglog.json index 118a9a92..87f67b52 100644 --- a/.wolf/buglog.json +++ b/.wolf/buglog.json @@ -219,6 +219,30 @@ "72301fd5" ], "record": ".wolf/bugs/bug-1978.md" + }, + { + "id": "bug-1979", + "status": "open", + "status_updated": "2026-10-29", + "status_note": "newly logged", + "component": "scheduler", + "severity": "crash", + "timestamp": "2026-10-29T03:16:20Z", + "last_seen": "2026-10-29T03:16:20Z", + "occurrences": 1, + "error_message": "sched_test makeResource() implicitly declared in test.c TU -- pointer truncated to int, dereference(made) SEGVs\n\n**Observed:**\n`ASAN_OPTIONS=detect_leaks=1 ./scheduler/testsuite/sched_test` SEGVs in\n`dereference` after all 17 tests PASS:\n\n```\n==659821==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000258\n #0 in dereference ../subprojects/librtti/src/ref.c:31\n #1 in test_unsatisfy_external_skips_inuse_shared_leg ../scheduler/testsuite/test.c:826\n```\n\n**Expected:** the test's teardown `dereference(made)` releases the\nrefcount-1 resource object registered by `makeResource()`.\n\n**Impact:** sched_test cannot run with leak detection enabled (the\n`xce-build.sh all` manual direct-run step); crashes after a clean PASS.\n\n**Found by:** ASAN direct run while closing out the\nfeature/license-resources -> working merge verification.", + "file": "src/scheduler/testsuite/test.c", + "root_cause": "`test.c` never includes `resobj.h` and has no local extern, so\n`makeResource()` is an *implicitly declared* function in this TU\n(gnu17 + this gcc emits only a warning; the build proceeds). An implicit\ndeclaration returns `int`: the real 64-bit pointer `0x50b000000118` is\ntruncated to 32-bit `eax` (`0x118`) and sign-extended back\n(`cltq` visible in the disassembly right after the\n`call makeResource`), so `made == 0x118` -- garbage.\n\nThe bug was introduced with the test itself (E2 Phase 1, 2fcacac2) but\nlay dormant: the test only ever did `TEST_ASSERT_NOT_NULL(made)`\n(`0x118 != NULL` always passes) and `key_find`-re-derived the real\npointer into `r` for the flag overwrite. The merge-verification leak fix\nadded `dereference(made)` in the teardown -- the FIRST real use of the\ntruncated pointer -- which turned the latent defect into a SEGV.", + "fix": "`#include \"resobj.h\"` in src/scheduler/testsuite/test.c (the header that\ndeclares `extern xce_resource_t* makeResource(var, xce_resource_t*)`),\nplus the leak fixes that motivated the teardown:\nsymmetric single `xvec_init`/`xvec_fini` for `uncomitted`, memset-only\n`unmeet` (`sched_check_shared_satisfy_()` xvec_idval_init()s it itself),\nand the collect-then-dereference teardown\n(`dereference(made)` -> `destroy(core.resmgr)` -> `xstring_table_fini()`,\nmirroring `xce_sched_core_fini()`/core.c:405).\n\nVerification:\n- Test: test_unsatisfy_external_skips_inuse_shared_leg\n (src/scheduler/testsuite/test.c); the SEGV itself was the RED state\n (deterministic, both full-run and single-test mode), GREEN after the\n include: rc=0, 17/17 PASS, zero leak reports under\n `ASAN_OPTIONS=detect_leaks=1`, single-test mode too.\n- Suites: `meson test --suite controller` 6/6 OK; full\n `xce-build.sh all` re-run pending at logging time.\n- Live: n/a (unit test only).", + "tags": [ + "testsuite", + "implicit-declaration", + "asan", + "external-license-resources" + ], + "related_bugs": [], + "fix_commits": [], + "record": ".wolf/bugs/bug-1979.md" } ] } diff --git a/.wolf/bugs/bug-1979.md b/.wolf/bugs/bug-1979.md new file mode 100644 index 00000000..62be8fc8 --- /dev/null +++ b/.wolf/bugs/bug-1979.md @@ -0,0 +1,92 @@ +--- +id: "bug-1979" +status: "open" +status_updated: "2026-10-29" +status_note: "newly logged" +component: "scheduler" +severity: "crash" +timestamp: "2026-10-29T03:16:20Z" +last_seen: "2026-10-29T03:16:20Z" +occurrences: 1 +files: ["src/scheduler/testsuite/test.c"] +tags: ["testsuite", "implicit-declaration", "asan", "external-license-resources"] +related: [] +summary: "sched_test: makeResource() implicitly declared -- pointer truncated, dereference SEGVs" +--- + +# bug-1979: makeResource() implicitly declared in sched_test -- pointer truncated, dereference SEGVs + +## Description +**Observed:** +`ASAN_OPTIONS=detect_leaks=1 ./scheduler/testsuite/sched_test` SEGVs in +`dereference` after all 17 tests PASS: + +``` +==659821==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000258 + #0 in dereference ../subprojects/librtti/src/ref.c:31 + #1 in test_unsatisfy_external_skips_inuse_shared_leg ../scheduler/testsuite/test.c:826 +``` + +**Expected:** the test's teardown `dereference(made)` releases the +refcount-1 resource object registered by `makeResource()`. + +**Impact:** sched_test cannot run with leak detection enabled (the +`xce-build.sh all` manual direct-run step); crashes after a clean PASS. + +**Found by:** ASAN direct run while closing out the +feature/license-resources -> working merge verification. + +## Environment +- OS: Ubuntu 24.04.2 LTS; kernel Linux 6.8.0-147-generic x86_64 +- libc: glibc 2.39 (host ldd) +- Toolchain: gcc (GCC) 15.2.0, meson 1.9.1 +- Build: asan (xce.build) +- Code: working @ 9b652d93-dirty (test.c local leak-fix edit) +- Deployment: n/a (unit test, no cluster) + +## Reproduction +1. `meson compile -C src/xce.build sched_test` +2. `ASAN_OPTIONS=detect_leaks=1 ./src/xce.build/scheduler/testsuite/sched_test` + (full run or `-t test_unsatisfy_external_skips_inuse_shared_leg`) +- Frequency: deterministic +- Evidence: not kept (transient ASAN stderr; repro command above) + +## Root cause +`test.c` never includes `resobj.h` and has no local extern, so +`makeResource()` is an *implicitly declared* function in this TU +(gnu17 + this gcc emits only a warning; the build proceeds). An implicit +declaration returns `int`: the real 64-bit pointer `0x50b000000118` is +truncated to 32-bit `eax` (`0x118`) and sign-extended back +(`cltq` visible in the disassembly right after the +`call makeResource`), so `made == 0x118` -- garbage. + +The bug was introduced with the test itself (E2 Phase 1, 2fcacac2) but +lay dormant: the test only ever did `TEST_ASSERT_NOT_NULL(made)` +(`0x118 != NULL` always passes) and `key_find`-re-derived the real +pointer into `r` for the flag overwrite. The merge-verification leak fix +added `dereference(made)` in the teardown -- the FIRST real use of the +truncated pointer -- which turned the latent defect into a SEGV. + +## Fix +`#include "resobj.h"` in src/scheduler/testsuite/test.c (the header that +declares `extern xce_resource_t* makeResource(var, xce_resource_t*)`), +plus the leak fixes that motivated the teardown: +symmetric single `xvec_init`/`xvec_fini` for `uncomitted`, memset-only +`unmeet` (`sched_check_shared_satisfy_()` xvec_idval_init()s it itself), +and the collect-then-dereference teardown +(`dereference(made)` -> `destroy(core.resmgr)` -> `xstring_table_fini()`, +mirroring `xce_sched_core_fini()`/core.c:405). + +## Verification +- Test: test_unsatisfy_external_skips_inuse_shared_leg + (src/scheduler/testsuite/test.c); the SEGV itself was the RED state + (deterministic, both full-run and single-test mode), GREEN after the + include: rc=0, 17/17 PASS, zero leak reports under + `ASAN_OPTIONS=detect_leaks=1`, single-test mode too. +- Suites: `meson test --suite controller` 6/6 OK; full + `xce-build.sh all` re-run pending at logging time. +- Live: n/a (unit test only). + +## References +- Related: bug-1429 (the XCE_TESTING reaper convention this teardown + complements), E2 Phase 1 (2fcacac2) where the test was introduced. diff --git a/src/scheduler/testsuite/test.c b/src/scheduler/testsuite/test.c index 9e83f29c..a19dafe5 100644 --- a/src/scheduler/testsuite/test.c +++ b/src/scheduler/testsuite/test.c @@ -6,6 +6,7 @@ #include #include #include "helper.h" +#include "resobj.h" #include "xstring.h" /* Pull resource.c into this TU so static helpers are visible for white-box testing. @@ -792,7 +793,10 @@ static void test_unsatisfy_external_skips_inuse_shared_leg(void) xvec_init(&uncomitted, 1, sizeof(float)); xvec_idval_init(&wanted, 1); xvec_idval_store(&wanted, 0, 4.0f); - xvec_idval_init(&unmeet, 1); + /* sched_check_shared_satisfy_() xvec_idval_init()s unmeet itself + * (resource.c) -- memset only, like the bug-1892 tests; a pre-init + * here would leak the vector it replaces. */ + memset(&unmeet, 0, sizeof(unmeet)); /* EXTERNAL: F=4 >= uncomit(0) + wanted(4) -> grant, despite inuse=3. */ rc = sched_check_shared_satisfy(&core, &uncomitted, &wanted, &unmeet); @@ -805,10 +809,9 @@ static void test_unsatisfy_external_skips_inuse_shared_leg(void) * must still apply -- 4 < 3+0+4 -> deny. Overwrite the flag in place. */ r->flags = XCE_RESF_SHARED; - xvec_init(&uncomitted, 1, sizeof(float)); xvec_idval_init(&wanted, 1); xvec_idval_store(&wanted, 0, 4.0f); - xvec_idval_init(&unmeet, 1); + memset(&unmeet, 0, sizeof(unmeet)); rc = sched_check_shared_satisfy(&core, &uncomitted, &wanted, &unmeet); TEST_ASSERT_EQUAL_INT(0, rc); @@ -818,7 +821,12 @@ static void test_unsatisfy_external_skips_inuse_shared_leg(void) xvec_fini(&uncomitted); zarray_fini(&core.sharedInst); zarray_fini(&core.sharedInUse); + /* core.c's xce_sched_core_fini() convention: collect-then-dereference + * -- destroy(resmgr) frees the table; each registered resource object + * (refcount 1) still needs its own dereference to run resobj_destroy. */ + dereference(made); destroy(core.resmgr); + xstring_table_fini(); } #define TEST(x) \