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 <noreply@anthropic.com>
This commit is contained in:
parent
9b652d93e5
commit
00f81c5aa8
3 changed files with 127 additions and 3 deletions
|
|
@ -219,6 +219,30 @@
|
||||||
"72301fd5"
|
"72301fd5"
|
||||||
],
|
],
|
||||||
"record": ".wolf/bugs/bug-1978.md"
|
"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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
92
.wolf/bugs/bug-1979.md
Normal file
92
.wolf/bugs/bug-1979.md
Normal file
|
|
@ -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.
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
#include <unity/unity.h>
|
#include <unity/unity.h>
|
||||||
#include "helper.h"
|
#include "helper.h"
|
||||||
|
#include "resobj.h"
|
||||||
#include "xstring.h"
|
#include "xstring.h"
|
||||||
|
|
||||||
/* Pull resource.c into this TU so static helpers are visible for white-box testing.
|
/* 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_init(&uncomitted, 1, sizeof(float));
|
||||||
xvec_idval_init(&wanted, 1);
|
xvec_idval_init(&wanted, 1);
|
||||||
xvec_idval_store(&wanted, 0, 4.0f);
|
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. */
|
/* EXTERNAL: F=4 >= uncomit(0) + wanted(4) -> grant, despite inuse=3. */
|
||||||
rc = sched_check_shared_satisfy(&core, &uncomitted, &wanted, &unmeet);
|
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. */
|
* must still apply -- 4 < 3+0+4 -> deny. Overwrite the flag in place. */
|
||||||
r->flags = XCE_RESF_SHARED;
|
r->flags = XCE_RESF_SHARED;
|
||||||
|
|
||||||
xvec_init(&uncomitted, 1, sizeof(float));
|
|
||||||
xvec_idval_init(&wanted, 1);
|
xvec_idval_init(&wanted, 1);
|
||||||
xvec_idval_store(&wanted, 0, 4.0f);
|
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);
|
rc = sched_check_shared_satisfy(&core, &uncomitted, &wanted, &unmeet);
|
||||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||||
|
|
@ -818,7 +821,12 @@ static void test_unsatisfy_external_skips_inuse_shared_leg(void)
|
||||||
xvec_fini(&uncomitted);
|
xvec_fini(&uncomitted);
|
||||||
zarray_fini(&core.sharedInst);
|
zarray_fini(&core.sharedInst);
|
||||||
zarray_fini(&core.sharedInUse);
|
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);
|
destroy(core.resmgr);
|
||||||
|
xstring_table_fini();
|
||||||
}
|
}
|
||||||
|
|
||||||
#define TEST(x) \
|
#define TEST(x) \
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue