fix(controller): persist_job_hook_fun OOB read on RUSAGE_UPDATE hook (bug-1502)
The RUSAGE_UPDATE job hook fires with (cluster_t*, xce_job_rusages_t*) as its varargs — the apiserver and scheduler subscribers parse exactly that contract — but persist_job_hook_fun's default case treated the second arg as a job_t* and read j->job.state at offset 56. A xce_job_rusages_t is 56 bytes and shares only jobid@0 with xce_job_t (which is why the jobid read coincidentally worked), so the state read landed one byte past the struct: ASAN heap-buffer-overflow (READ of size 4) in thread T7 whenever a job runs and the worker pushes rusage updates. This was the recurring controller crash (term 3/4/5) blocking job-PENDING verification. Add a dedicated HOOK_DETAIL_JOB_RUSAGE_UPDATE case that reads jobid from the xce_job_rusages_t* and bakes the job's current state from a jobmgr key_find on the vararg cluster_t* (race-free: the sole caller update_job_rusages() holds that job's write lock while firing the AFTER_EVENT hook; re-locking the job inside the hook would deadlock). Regression test: test_job_hook_rusage_writes_wal_entry now passes a real 56-byte xce_job_rusages_t instead of a full job_t (which masked the overflow) — ASAN stack-buffer-overflow under the old code, PASS under the fix. Full controller unit suite: 458 tests, 0 failures. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
ff2d784ca9
commit
2fa9bedcd3
4 changed files with 61 additions and 2 deletions
|
|
@ -14,6 +14,7 @@
|
|||
#include "persist.h"
|
||||
#include "xce/cluster_def.h"
|
||||
#include "xlist.h"
|
||||
#include "controller.capnp.h" /* xce_job_rusages_t (bug-1502 regression test) */
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Test helpers */
|
||||
|
|
@ -276,9 +277,19 @@ static void test_job_hook_rusage_writes_wal_entry(void)
|
|||
|
||||
uint64_t lsn_before = p->current_lsn;
|
||||
|
||||
/* bug-1502 regression: production fires RUSAGE_UPDATE with a real
|
||||
* xce_job_rusages_t* (56 bytes, NO job-state field) as the second hook
|
||||
* arg — NOT a job_t*. The old persist_job_hook_fun default case read
|
||||
* j->job.state at offset 56, one byte past this struct → ASAN
|
||||
* heap-buffer-overflow. Passing the real minimal struct here (instead of
|
||||
* the old &g_stub_job, a full job_t that masked the overflow) reproduces
|
||||
* the production shape exactly. */
|
||||
xce_job_rusages_t rusages = {0};
|
||||
rusages.jobid = 0x1234567890ABCDEFULL;
|
||||
|
||||
int rc = call_job_hook(HOOK_AFTER_EVENT, HOOK_AFTER_EVENT,
|
||||
HOOK_DETAIL_JOB_RUSAGE_UPDATE, p,
|
||||
(cluster_t*)NULL, &g_stub_job);
|
||||
(cluster_t*)NULL, &rusages);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_GREATER_THAN(lsn_before, p->current_lsn);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue