From 94d940ff24ded20bb3273f1ae47896a44603235c Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Tue, 13 Oct 2026 17:50:15 +0200 Subject: [PATCH] fix(doclinks): never qsort an empty rst token vector doclink_rst.c sorted d.toks unconditionally; the vector stays NULL until the first token, and qsort's base is declared nonnull even for zero elements. GCC UBSan on Linux amd64 reported it in rst_section_text_cut and diagrams_rst ("null pointer passed as argument 1, which is declared to never be null", doclink_rst.c:1767) -- the same class as the k8s pass fix. Sort only when there are at least two tokens, as the file already does for its section titles. Proof: doc_links_rst on Linux amd64 (GCC, ASan+UBSan) reports the violation without the change and none with it; doc_links_rst + diagrams on macOS: 27 passed. Signed-off-by: Martin Vogel --- internal/cbm/doclink_rst.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/cbm/doclink_rst.c b/internal/cbm/doclink_rst.c index 9169a38d2..2aee5e75c 100644 --- a/internal/cbm/doclink_rst.c +++ b/internal/cbm/doclink_rst.c @@ -1764,7 +1764,11 @@ void cbm_doclink_rst_scan_file(CBMExtractCtx *ctx) { ctx->result->doc_links.failed = true; return; } - qsort(d.toks, (size_t)d.ntoks, sizeof(*d.toks), rst_tok_cmp); + /* d.toks stays NULL until the first token: qsort's base is nonnull even + * for zero elements (glibc attribute; UBSan on Linux amd64, 2026-10-13). */ + if (d.ntoks > 1) { + qsort(d.toks, (size_t)d.ntoks, sizeof(*d.toks), rst_tok_cmp); + } for (int i = 0; i < d.ntoks; i++) { const rst_tok_t *t = &d.toks[i]; const rst_title_t *sec = rst_section_of(&d, t->line);