From 668dc5c889d7dffc0832e338640ee1249d71bdbe Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Tue, 20 Oct 2026 01:10:36 +0200 Subject: [PATCH] fix(watcher): never hand qsort a NULL array in the SVN observation core A status document with no content candidates (only clean entries, externals or property changes) left the candidate array NULL, and fingerprint_candidates sorted it anyway. glibc declares qsort's base nonnull even for a zero count, so the trapping UBSan leg aborts there; macOS and recovering UBSan only print or ignore it. Sort only when there are at least two candidates. parse_clean_props_and_externals_are_not_local_changes reaches this path: RED on the Linux arm64 GCC leg built with -fno-sanitize-recover=undefined before the guard, GREEN after. Refs #1127 Signed-off-by: Martin Vogel --- src/watcher/svn_state.c | 8 ++++++-- tests/test_svn_state.c | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/watcher/svn_state.c b/src/watcher/svn_state.c index cd3eef391..a1da84f5e 100644 --- a/src/watcher/svn_state.c +++ b/src/watcher/svn_state.c @@ -1173,8 +1173,12 @@ static int walk_candidates(svn_walk_t *walk, candidate_list_t *candidates) { } static int fingerprint_candidates(parser_t *parser, cbm_svn_observation_t *observation) { - qsort(parser->candidates.paths, parser->candidates.count, sizeof(*parser->candidates.paths), - candidate_compare); + /* With no candidates the array is NULL, and glibc declares qsort's base + * nonnull even for a zero count. */ + if (parser->candidates.count > 1) { + qsort(parser->candidates.paths, parser->candidates.count, sizeof(*parser->candidates.paths), + candidate_compare); + } svn_walk_t walk = {0}; walk.root = parser->root; int result = 0; diff --git a/tests/test_svn_state.c b/tests/test_svn_state.c index eca0d9d36..ff6a9f17c 100644 --- a/tests/test_svn_state.c +++ b/tests/test_svn_state.c @@ -198,6 +198,10 @@ TEST(parse_rejects_declarations_and_truncated_documents) { PASS(); } +/* No entry here needs its content read, so the candidate list stays empty: + * this also guards against sorting a NULL array. That guard binds only on the + * trapping UBSan leg (-fno-sanitize-recover=undefined); macOS and recovering + * UBSan pass without it. */ TEST(parse_clean_props_and_externals_are_not_local_changes) { wc_fixture_t fixture; ASSERT_TRUE(wc_fixture_init(&fixture, "cbm_svn_clean"));