fix(libxce): xce_strerror_r global-buffer-overflow on HA error codes (utils.c)

The E_XCE_* enum (errnox.h) gained NOMASTER/REDIRECT/NOT_READY/STANDBY
(10011-10014) but xce_err_str[] still had only 10 strings + NULL, while the
bounds check used E_XCE_MAX. Formatting any of those codes ran off the array:
NOMASTER -> strncpy(NULL) deref; NOT_READY (10013, returned by ctrlstatus on a
failover-exhausted cluster) -> read 8 bytes past the 88-byte table = the ASAN
global-buffer-overflow at utils.c:335.

Fix: fill the table for all 14 codes; bound xce_strerror_r against the actual
array size + NULL (degrade to 'unknown error', never OOB); add a _Static_assert
tying the table length to E_XCE_MAX so any future enum drift fails the build.
Pre-existing foundation bug surfaced (not caused) by controller-status 6.4.

RED: test_strerror_all_xce_codes SEGV'd at utils.c:335 under ASAN; GREEN: lib
suite Ok:1 Fail:0. New tests in test_error.c cover all in-range + out-of-range codes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-06-20 10:13:21 +00:00
parent 9a0fec667b
commit 1aa0761c12
2 changed files with 80 additions and 13 deletions

View file

@ -35,11 +35,54 @@ static void test_strerror_r(void) {
}
}
#endif
/* Every XCE error code in [XCE_ERRNO_BASE, E_XCE_MAX) must map to a real string
* via xce_strerror_r — no NULL deref, no out-of-bounds read of xce_err_str[].
* Regression for the global-buffer-overflow at utils.c:335: the HA codes
* E_XCE_NOMASTER/REDIRECT/NOT_READY/STANDBY (10011-10014) were added to the
* enum but not to xce_err_str[], so formatting them (e.g. ctrlstatus on a
* failover-exhausted cluster returning E_XCE_NOT_READY) ran off the array.
* Under ASAN this crashes pre-fix. */
static void test_strerror_all_xce_codes(void)
{
char buf[128];
int code;
for (code = XCE_ERRNO_BASE; code < E_XCE_MAX; code++) {
buf[0] = '\0';
int rc = xce_strerror_r(code, buf, sizeof(buf));
TEST_ASSERT_EQUAL_INT(0, rc); /* every in-range code resolves */
TEST_ASSERT_TRUE(buf[0] != '\0'); /* to a non-empty string */
TEST_ASSERT_TRUE(strcmp(buf, "unknown error") != 0); /* a SPECIFIC one */
}
/* the specific HA codes that triggered the crash */
xce_strerror_r(E_XCE_NOT_READY, buf, sizeof(buf));
TEST_ASSERT_TRUE(buf[0] != '\0' && strcmp(buf, "unknown error") != 0);
xce_strerror_r(E_XCE_NOMASTER, buf, sizeof(buf));
TEST_ASSERT_TRUE(buf[0] != '\0' && strcmp(buf, "unknown error") != 0);
}
/* Out-of-range XCE codes (>= E_XCE_MAX) must fall back to "unknown error",
* never index past xce_err_str[] (defense in depth against future enum drift). */
static void test_strerror_out_of_range(void)
{
char buf[128];
TEST_ASSERT_EQUAL_INT(-1, xce_strerror_r(E_XCE_MAX, buf, sizeof(buf)));
TEST_ASSERT_TRUE(strcmp(buf, "unknown error") == 0);
TEST_ASSERT_EQUAL_INT(-1, xce_strerror_r(E_XCE_MAX + 50, buf, sizeof(buf)));
TEST_ASSERT_TRUE(strcmp(buf, "unknown error") == 0);
}
void test_error(void)
{
printf("\n>> error test\n\n");
RUN_TEST(test_error_setget);
RUN_TEST(test_strerror_all_xce_codes);
RUN_TEST(test_strerror_out_of_range);
#if 0
RUN_TEST(test_strerror_r);
#endif