commit 6912561

Michael Forney  ·  2026-04-10 19:23:44 +0000 UTC
parent 803b573
musl: Apply recent bug fixes from git
8 files changed,  +698, -1
+31, -0
 1@@ -0,0 +1,31 @@
 2+From df97e1c57780588639d8f6aff7dd8f1db8b63c19 Mon Sep 17 00:00:00 2001
 3+From: Liam Wachter <liam@asymmetric.re>
 4+Date: Fri, 20 Mar 2026 12:19:40 -0400
 5+Subject: [PATCH] dns: fix nameserver OOB read in IPv6-disabled fallback
 6+
 7+In __res_msend_rc(), the IPv6-disabled fallback check uses conf->ns[nns]
 8+inside a loop controlled by i, so it tests a fixed slot instead of
 9+walking configured nameservers. This reads one past the array's size.
10+
11+Use conf->ns[i] so the loop correctly detects whether all configured
12+nameservers are IPv6-only.
13+---
14+ src/network/res_msend.c | 2 +-
15+ 1 file changed, 1 insertion(+), 1 deletion(-)
16+
17+diff --git a/src/network/res_msend.c b/src/network/res_msend.c
18+index fcb52513..51d42ecb 100644
19+--- a/src/network/res_msend.c
20++++ b/src/network/res_msend.c
21+@@ -124,7 +124,7 @@ int __res_msend_rc(int nqueries, const unsigned char *const *queries,
22+ 
23+ 	/* Handle case where system lacks IPv6 support */
24+ 	if (fd < 0 && family == AF_INET6 && errno == EAFNOSUPPORT) {
25+-		for (i=0; i<nns && conf->ns[nns].family == AF_INET6; i++);
26++		for (i=0; i<nns && conf->ns[i].family == AF_INET6; i++);
27+ 		if (i==nns) {
28+ 			pthread_setcancelstate(cs, 0);
29+ 			return -1;
30+-- 
31+2.49.0
32+
+108, -0
  1@@ -0,0 +1,108 @@
  2+From 8515ae00b04224597977c4d0ff06636435daa116 Mon Sep 17 00:00:00 2001
  3+From: Rich Felker <dalias@aerifal.cx>
  4+Date: Sun, 22 Mar 2026 21:32:35 -0400
  5+Subject: [PATCH] fix incorrect access to tzname[] by strptime %Z conversion
  6+ specifier
  7+
  8+there are three issues here:
  9+
 10+1. if tzset has not been called (explicitly or implicitly), the
 11+tzname[] array will contain null pointers, and the dereference to
 12+compare against them has undefined behavior (and will fault).
 13+
 14+2. access to tzname[] was performed without the timezone lock held.
 15+this resulted in a data race if the timezone is concurrently changed
 16+from another thread.
 17+
 18+3. due to unintended signedness of the types, the open-coded isalpha
 19+in the non-matching case was wrong and would continue past null
 20+termination.
 21+
 22+to fix the first two issues, the body of the %Z conversion is moved to
 23+__tz.c where it has access to locking, and null checks are added.
 24+
 25+there is probably an argument to be made that the equivalent of tzset
 26+should happen here, but POSIX does not specify that to happen, so in
 27+the absence of an interpretation adding such an allowance or
 28+requirement, it is not done.
 29+
 30+the third issue is fixed just by using the existing isalpha macro.
 31+---
 32+ src/time/__tz.c      | 19 +++++++++++++++++++
 33+ src/time/strptime.c  | 13 +++----------
 34+ src/time/time_impl.h |  1 +
 35+ 3 files changed, 23 insertions(+), 10 deletions(-)
 36+
 37+diff --git a/src/time/__tz.c b/src/time/__tz.c
 38+index 54ed4cf6..cfce268e 100644
 39+--- a/src/time/__tz.c
 40++++ b/src/time/__tz.c
 41+@@ -436,3 +436,22 @@ const char *__tm_to_tzname(const struct tm *tm)
 42+ 	UNLOCK(lock);
 43+ 	return p;
 44+ }
 45++
 46++int __tzname_to_isdst(const char *restrict *s)
 47++{
 48++	size_t len;
 49++	int isdst = -1;
 50++	LOCK(lock);
 51++	if (tzname[0] && !strncmp(*s, tzname[0], len = strlen(tzname[0]))) {
 52++		isdst = 0;
 53++		*s += len;
 54++	} else if (tzname[1] && !strncmp(*s, tzname[1], len=strlen(tzname[1]))) {
 55++		isdst = 1;
 56++		*s += len;
 57++	} else {
 58++		/* FIXME: is this supposed to be an error? */
 59++		while (isalpha(**s)) ++*s;
 60++	}
 61++	UNLOCK(lock);
 62++	return isdst;
 63++}
 64+diff --git a/src/time/strptime.c b/src/time/strptime.c
 65+index b1147242..40bb37af 100644
 66+--- a/src/time/strptime.c
 67++++ b/src/time/strptime.c
 68+@@ -5,6 +5,7 @@
 69+ #include <stddef.h>
 70+ #include <string.h>
 71+ #include <strings.h>
 72++#include "time_impl.h"
 73+ 
 74+ char *strptime(const char *restrict s, const char *restrict f, struct tm *restrict tm)
 75+ {
 76+@@ -207,16 +208,8 @@ char *strptime(const char *restrict s, const char *restrict f, struct tm *restri
 77+ 			s += 5;
 78+ 			break;
 79+ 		case 'Z':
 80+-			if (!strncmp(s, tzname[0], len = strlen(tzname[0]))) {
 81+-				tm->tm_isdst = 0;
 82+-				s += len;
 83+-			} else if (!strncmp(s, tzname[1], len=strlen(tzname[1]))) {
 84+-				tm->tm_isdst = 1;
 85+-				s += len;
 86+-			} else {
 87+-				/* FIXME: is this supposed to be an error? */
 88+-				while ((*s|32)-'a' <= 'z'-'a') s++;
 89+-			}
 90++			i = __tzname_to_isdst(&s);
 91++			if (i>=0) tm->tm_isdst = i;
 92+ 			break;
 93+ 		case '%':
 94+ 			if (*s++ != '%') return 0;
 95+diff --git a/src/time/time_impl.h b/src/time/time_impl.h
 96+index f26d8005..ffe5050b 100644
 97+--- a/src/time/time_impl.h
 98++++ b/src/time/time_impl.h
 99+@@ -5,6 +5,7 @@ hidden int __month_to_secs(int, int);
100+ hidden long long __year_to_secs(long long, int *);
101+ hidden long long __tm_to_secs(const struct tm *);
102+ hidden const char *__tm_to_tzname(const struct tm *);
103++hidden int __tzname_to_isdst(const char *restrict *);
104+ hidden int __secs_to_tm(long long, struct tm *);
105+ hidden void __secs_to_zone(long long, int, int *, long *, long *, const char **);
106+ hidden const char *__strftime_fmt_1(char (*)[100], size_t *, int, const struct tm *, locale_t, int);
107+-- 
108+2.49.0
109+
+62, -0
 1@@ -0,0 +1,62 @@
 2+From b6bfb0cbc7e56b560b1a928b6136c3ef89015743 Mon Sep 17 00:00:00 2001
 3+From: Szabolcs Nagy <nsz@port70.net>
 4+Date: Mon, 23 Mar 2026 17:33:20 +0000
 5+Subject: [PATCH] regex: reject invalid \digit back reference in BRE
 6+
 7+in BRE \n matches the nth subexpression, but regcomp did not check if
 8+the nth subexpression was complete or not, only that there were more
 9+subexpressions overall than the largest backref.
10+
11+fix regcomp to error if the referenced subexpression is incomplete.
12+the bug could cause an infinite loop in regexec:
13+
14+ regcomp(&re, "\\(^a*\\1\\)*", 0);
15+ regexec(&re, "aa", 0, 0, 0);
16+
17+since BRE has backreferences, any application accepting a BRE from
18+untrusted sources is already vulnerable to an attacker-controlled
19+near-infinite (exponential-time) loop, but this particular case where
20+the loop is actually infinite can and should be avoided.
21+
22+ERE is not affected since the language an ERE describes is actually
23+regular.
24+
25+Reported-by: Simon Resch <simon.resch@code-intelligence.com>
26+---
27+ src/regex/regcomp.c | 6 ++++++
28+ 1 file changed, 6 insertions(+)
29+
30+diff --git a/src/regex/regcomp.c b/src/regex/regcomp.c
31+index fb24556e..b4b81968 100644
32+--- a/src/regex/regcomp.c
33++++ b/src/regex/regcomp.c
34+@@ -409,6 +409,8 @@ typedef struct {
35+ 	int position;
36+ 	/* The highest back reference or -1 if none seen so far. */
37+ 	int max_backref;
38++	/* Bit mask of submatch IDs that can be back referenced. */
39++	int backref_ok;
40+ 	/* Compilation flags. */
41+ 	int cflags;
42+ } tre_parse_ctx_t;
43+@@ -769,6 +771,8 @@ static reg_errcode_t marksub(tre_parse_ctx_t *ctx, tre_ast_node_t *node, int sub
44+ 	node->submatch_id = subid;
45+ 	node->num_submatches++;
46+ 	ctx->n = node;
47++	if (subid < 10)
48++		ctx->backref_ok |= 1<<subid;
49+ 	return REG_OK;
50+ }
51+ 
52+@@ -864,6 +868,8 @@ static reg_errcode_t parse_atom(tre_parse_ctx_t *ctx, const char *s)
53+ 			if (!ere && (unsigned)*s-'1' < 9) {
54+ 				/* back reference */
55+ 				int val = *s - '0';
56++				if (!(ctx->backref_ok & 1<<val))
57++					return REG_ESUBREG;
58+ 				node = tre_ast_new_literal(ctx->mem, BACKREF, val, ctx->position++);
59+ 				ctx->max_backref = MAX(val, ctx->max_backref);
60+ 			} else {
61+-- 
62+2.49.0
63+
+320, -0
  1@@ -0,0 +1,320 @@
  2+From 00ae8f500c3eb95fb2fae7d6bc05db86666897b1 Mon Sep 17 00:00:00 2001
  3+From: Rich Felker <dalias@aerifal.cx>
  4+Date: Mon, 30 Mar 2026 16:00:50 -0400
  5+Subject: [PATCH] fix pathological slowness & incorrect mappings in iconv
  6+ gb18030 decoder
  7+
  8+in order to implement the "UTF" aspect of gb18030 (ability to
  9+represent arbitrary unicode characters not present in the 2-byte
 10+mapping), we have to apply the index obtained from the encoded 4-byte
 11+sequence into the set of unmapped characters. this was done by
 12+scanning repeatedly over the table of mapped characters and counting
 13+off mapped characters below a running index by which to adjust the
 14+running index by on each iteration. this iterative process eventually
 15+leaves us with the value of the Nth unmapped character replacing the
 16+index, but depending on which particular character that is, the number
 17+of iterations needed to find it can be in the tens of thousands, and
 18+each iteration traverses the whole 126x190 table in the inner loop.
 19+this can lead to run times exceeding an entire second per character on
 20+moderate-speed machines.
 21+
 22+on top of that, the transformation logic produced wrong results for
 23+BMP characters above the the surrogate range, as a result of not
 24+correctly accounting for it being excluded, and for characters outside
 25+the BMP, as a result of a misunderstanding of how gb18030 encodes
 26+them.
 27+
 28+this patch replaces the unmapped character lookup with a single linear
 29+search of a list of unmapped ranges. there are only 206 such ranges,
 30+and these are permanently assigned and unchangeable as a consequence
 31+of the character encoding having to be stable, so a simple array of
 32+16-bit start/length values for each range consumes only 824 bytes, a
 33+very reasonable size cost here.
 34+
 35+this new table accounts for the previously-incorrect surrogate
 36+handling, and non-BMP characters are handled correctly by a single
 37+offset, without the need for any unmapped-range search.
 38+
 39+there are still a small number of mappings that are incorrect due to
 40+late changes made in the definition of gb18030, swapping PUA
 41+codepoints with proper Unicode characters. correcting these requires a
 42+postprocessing step that will be added later.
 43+---
 44+ src/locale/gb18030utf.h | 206 ++++++++++++++++++++++++++++++++++++++++
 45+ src/locale/iconv.c      |  33 +++++--
 46+ 2 files changed, 230 insertions(+), 9 deletions(-)
 47+ create mode 100644 src/locale/gb18030utf.h
 48+
 49+diff --git a/src/locale/gb18030utf.h b/src/locale/gb18030utf.h
 50+new file mode 100644
 51+index 00000000..322a2440
 52+--- /dev/null
 53++++ b/src/locale/gb18030utf.h
 54+@@ -0,0 +1,206 @@
 55++{ 0x80, 36 },
 56++{ 0xa5, 2 },
 57++{ 0xa9, 7 },
 58++{ 0xb2, 5 },
 59++{ 0xb8, 31 },
 60++{ 0xd8, 8 },
 61++{ 0xe2, 6 },
 62++{ 0xeb, 1 },
 63++{ 0xee, 4 },
 64++{ 0xf4, 3 },
 65++{ 0xf8, 1 },
 66++{ 0xfb, 1 },
 67++{ 0xfd, 4 },
 68++{ 0x102, 17 },
 69++{ 0x114, 7 },
 70++{ 0x11c, 15 },
 71++{ 0x12c, 24 },
 72++{ 0x145, 3 },
 73++{ 0x149, 4 },
 74++{ 0x14e, 29 },
 75++{ 0x16c, 98 },
 76++{ 0x1cf, 1 },
 77++{ 0x1d1, 1 },
 78++{ 0x1d3, 1 },
 79++{ 0x1d5, 1 },
 80++{ 0x1d7, 1 },
 81++{ 0x1d9, 1 },
 82++{ 0x1db, 1 },
 83++{ 0x1dd, 28 },
 84++{ 0x1fa, 87 },
 85++{ 0x252, 15 },
 86++{ 0x262, 101 },
 87++{ 0x2c8, 1 },
 88++{ 0x2cc, 13 },
 89++{ 0x2da, 183 },
 90++{ 0x3a2, 1 },
 91++{ 0x3aa, 7 },
 92++{ 0x3c2, 1 },
 93++{ 0x3ca, 55 },
 94++{ 0x402, 14 },
 95++{ 0x450, 1 },
 96++{ 0x452, 7102 },
 97++{ 0x2011, 2 },
 98++{ 0x2017, 1 },
 99++{ 0x201a, 2 },
100++{ 0x201e, 7 },
101++{ 0x2027, 9 },
102++{ 0x2031, 1 },
103++{ 0x2034, 1 },
104++{ 0x2036, 5 },
105++{ 0x203c, 112 },
106++{ 0x20ad, 86 },
107++{ 0x2104, 1 },
108++{ 0x2106, 3 },
109++{ 0x210a, 12 },
110++{ 0x2117, 10 },
111++{ 0x2122, 62 },
112++{ 0x216c, 4 },
113++{ 0x217a, 22 },
114++{ 0x2194, 2 },
115++{ 0x219a, 110 },
116++{ 0x2209, 6 },
117++{ 0x2210, 1 },
118++{ 0x2212, 3 },
119++{ 0x2216, 4 },
120++{ 0x221b, 2 },
121++{ 0x2221, 2 },
122++{ 0x2224, 1 },
123++{ 0x2226, 1 },
124++{ 0x222c, 2 },
125++{ 0x222f, 5 },
126++{ 0x2238, 5 },
127++{ 0x223e, 10 },
128++{ 0x2249, 3 },
129++{ 0x224d, 5 },
130++{ 0x2253, 13 },
131++{ 0x2262, 2 },
132++{ 0x2268, 6 },
133++{ 0x2270, 37 },
134++{ 0x2296, 3 },
135++{ 0x229a, 11 },
136++{ 0x22a6, 25 },
137++{ 0x22c0, 82 },
138++{ 0x2313, 333 },
139++{ 0x246a, 10 },
140++{ 0x249c, 100 },
141++{ 0x254c, 4 },
142++{ 0x2574, 13 },
143++{ 0x2590, 3 },
144++{ 0x2596, 10 },
145++{ 0x25a2, 16 },
146++{ 0x25b4, 8 },
147++{ 0x25be, 8 },
148++{ 0x25c8, 3 },
149++{ 0x25cc, 2 },
150++{ 0x25d0, 18 },
151++{ 0x25e6, 31 },
152++{ 0x2607, 2 },
153++{ 0x260a, 54 },
154++{ 0x2641, 1 },
155++{ 0x2643, 2110 },
156++{ 0x2e82, 2 },
157++{ 0x2e85, 3 },
158++{ 0x2e89, 2 },
159++{ 0x2e8d, 10 },
160++{ 0x2e98, 15 },
161++{ 0x2ea8, 2 },
162++{ 0x2eab, 3 },
163++{ 0x2eaf, 4 },
164++{ 0x2eb4, 2 },
165++{ 0x2eb8, 3 },
166++{ 0x2ebc, 14 },
167++{ 0x2ecb, 293 },
168++{ 0x2ffc, 4 },
169++{ 0x3004, 1 },
170++{ 0x3018, 5 },
171++{ 0x301f, 2 },
172++{ 0x302a, 20 },
173++{ 0x303f, 2 },
174++{ 0x3094, 7 },
175++{ 0x309f, 2 },
176++{ 0x30f7, 5 },
177++{ 0x30ff, 6 },
178++{ 0x312a, 246 },
179++{ 0x322a, 7 },
180++{ 0x3232, 113 },
181++{ 0x32a4, 234 },
182++{ 0x3390, 12 },
183++{ 0x339f, 2 },
184++{ 0x33a2, 34 },
185++{ 0x33c5, 9 },
186++{ 0x33cf, 2 },
187++{ 0x33d3, 2 },
188++{ 0x33d6, 113 },
189++{ 0x3448, 43 },
190++{ 0x3474, 298 },
191++{ 0x359f, 111 },
192++{ 0x360f, 11 },
193++{ 0x361b, 765 },
194++{ 0x3919, 85 },
195++{ 0x396f, 96 },
196++{ 0x39d1, 14 },
197++{ 0x39e0, 147 },
198++{ 0x3a74, 218 },
199++{ 0x3b4f, 287 },
200++{ 0x3c6f, 113 },
201++{ 0x3ce1, 885 },
202++{ 0x4057, 264 },
203++{ 0x4160, 471 },
204++{ 0x4338, 116 },
205++{ 0x43ad, 4 },
206++{ 0x43b2, 43 },
207++{ 0x43de, 248 },
208++{ 0x44d7, 373 },
209++{ 0x464d, 20 },
210++{ 0x4662, 193 },
211++{ 0x4724, 5 },
212++{ 0x472a, 82 },
213++{ 0x477d, 16 },
214++{ 0x478e, 441 },
215++{ 0x4948, 50 },
216++{ 0x497b, 2 },
217++{ 0x497e, 4 },
218++{ 0x4984, 1 },
219++{ 0x4987, 20 },
220++{ 0x499c, 3 },
221++{ 0x49a0, 22 },
222++{ 0x49b8, 703 },
223++{ 0x4c78, 39 },
224++{ 0x4ca4, 111 },
225++{ 0x4d1a, 148 },
226++{ 0x4daf, 81 },
227++{ 0x9fa6, 14426 },
228++{ 0xe76c, 1 },
229++{ 0xe7c8, 1 },
230++{ 0xe7e7, 13 },
231++{ 0xe815, 1 },
232++{ 0xe819, 5 },
233++{ 0xe81f, 7 },
234++{ 0xe827, 4 },
235++{ 0xe82d, 4 },
236++{ 0xe833, 8 },
237++{ 0xe83c, 7 },
238++{ 0xe844, 16 },
239++{ 0xe856, 14 },
240++{ 0xe865, 4295 },
241++{ 0xf92d, 76 },
242++{ 0xf97a, 27 },
243++{ 0xf996, 81 },
244++{ 0xf9e8, 9 },
245++{ 0xf9f2, 26 },
246++{ 0xfa10, 1 },
247++{ 0xfa12, 1 },
248++{ 0xfa15, 3 },
249++{ 0xfa19, 6 },
250++{ 0xfa22, 1 },
251++{ 0xfa25, 2 },
252++{ 0xfa2a, 1030 },
253++{ 0xfe32, 1 },
254++{ 0xfe45, 4 },
255++{ 0xfe53, 1 },
256++{ 0xfe58, 1 },
257++{ 0xfe67, 1 },
258++{ 0xfe6c, 149 },
259++{ 0xff5f, 129 },
260++{ 0xffe6, 26 },
261+diff --git a/src/locale/iconv.c b/src/locale/iconv.c
262+index 52178950..4151411d 100644
263+--- a/src/locale/iconv.c
264++++ b/src/locale/iconv.c
265+@@ -74,6 +74,10 @@ static const unsigned short gb18030[126][190] = {
266+ #include "gb18030.h"
267+ };
268+ 
269++static const unsigned short gb18030utf[][2] = {
270++#include "gb18030utf.h"
271++};
272++
273+ static const unsigned short big5[89][157] = {
274+ #include "big5.h"
275+ };
276+@@ -224,6 +228,8 @@ static unsigned uni_to_jis(unsigned c)
277+ 	}
278+ }
279+ 
280++#define countof(a) (sizeof (a) / sizeof *(a))
281++
282+ size_t iconv(iconv_t cd, char **restrict in, size_t *restrict inb, char **restrict out, size_t *restrict outb)
283+ {
284+ 	size_t x=0;
285+@@ -430,15 +436,24 @@ size_t iconv(iconv_t cd, char **restrict in, size_t *restrict inb, char **restri
286+ 				d = *((unsigned char *)*in + 3);
287+ 				if (d-'0'>9) goto ilseq;
288+ 				c += d-'0';
289+-				c += 128;
290+-				for (d=0; d<=c; ) {
291+-					k = 0;
292+-					for (int i=0; i<126; i++)
293+-						for (int j=0; j<190; j++)
294+-							if (gb18030[i][j]-d <= c-d)
295+-								k++;
296+-					d = c+1;
297+-					c += k;
298++				/* Starting at 90 30 81 30 (189000), mapping is
299++				 * linear without gaps, to U+10000 and up. */
300++				if (c >= 189000) {
301++					c -= 189000;
302++					c += 0x10000;
303++					if (c >= 0x110000) goto ilseq;
304++					break;
305++				}
306++				/* Otherwise we must process an index into set
307++				 * of characters unmapped by 2-byte table. */
308++				for (int i=0; ; i++) {
309++					if (i==countof(gb18030utf))
310++						goto ilseq;
311++					if (c<gb18030utf[i][1]) {
312++						c += gb18030utf[i][0];
313++						break;
314++					}
315++					c -= gb18030utf[i][1];
316+ 				}
317+ 				break;
318+ 			}
319+-- 
320+2.49.0
321+
+44, -0
 1@@ -0,0 +1,44 @@
 2+From 9a9dc9650dacdeb3459ba24aa1448c633eb33241 Mon Sep 17 00:00:00 2001
 3+From: Rich Felker <dalias@aerifal.cx>
 4+Date: Thu, 9 Apr 2026 22:51:30 -0400
 5+Subject: [PATCH] qsort: fix leonardo heap corruption from bug in doubleword
 6+ ctz primitive
 7+
 8+the pntz function, implementing a "count trailing zeros" variant for a
 9+bit vector consisting of two size_t words, erroneously returned zero
10+rather than the number of bits in the low word when the first bit set
11+was the low bit of the high word.
12+
13+as a result, a loop in the trinkle function which should have a
14+guaranteed small bound on the number of iterations, could run
15+unboundedly, thereby overflowing a stack-based working-space array
16+which was sized for the bound.
17+
18+CVE-2026-40200 has been assigned for this issue.
19+---
20+ src/stdlib/qsort.c | 8 ++++----
21+ 1 file changed, 4 insertions(+), 4 deletions(-)
22+
23+diff --git a/src/stdlib/qsort.c b/src/stdlib/qsort.c
24+index ab79dc6f..13219ab3 100644
25+--- a/src/stdlib/qsort.c
26++++ b/src/stdlib/qsort.c
27+@@ -34,11 +34,11 @@
28+ 
29+ typedef int (*cmpfun)(const void *, const void *, void *);
30+ 
31++/* returns index of first bit set, excluding the low bit assumed to always
32++ * be set, starting from low bit of p[0] up through high bit of p[1] */
33+ static inline int pntz(size_t p[2]) {
34+-	int r = ntz(p[0] - 1);
35+-	if(r != 0 || (r = 8*sizeof(size_t) + ntz(p[1])) != 8*sizeof(size_t)) {
36+-		return r;
37+-	}
38++	if (p[0] != 1) return ntz(p[0] - 1);
39++	if (p[1]) return 8*sizeof(size_t) + ntz(p[1]);
40+ 	return 0;
41+ }
42+ 
43+-- 
44+2.49.0
45+
+93, -0
 1@@ -0,0 +1,93 @@
 2+From 867a65f49b59b4127b27cfe40249c7965e7316b7 Mon Sep 17 00:00:00 2001
 3+From: Rich Felker <dalias@aerifal.cx>
 4+Date: Thu, 9 Apr 2026 23:40:53 -0400
 5+Subject: [PATCH] qsort: hard-preclude oob array writes independent of any
 6+ invariants
 7+
 8+while the root cause of CVE-2026-40200 was a faulty ctz primitive, the
 9+fallout of the bug would have been limited to erroneous sorting or
10+infinite loop if not for the stores to a stack-based array that
11+depended on trusting invariants in order not to go out of bounds.
12+
13+increase the size of the array to a power of two so that we can mask
14+indices into it to force them into range. in the absence of any
15+further bug, the masking is a no-op, but it does not have any
16+measurable performance cost, and it makes spatial memory safety
17+trivial to prove (and for readers not familiar with the algorithms to
18+trust).
19+---
20+ src/stdlib/qsort.c | 20 +++++++++++++-------
21+ 1 file changed, 13 insertions(+), 7 deletions(-)
22+
23+diff --git a/src/stdlib/qsort.c b/src/stdlib/qsort.c
24+index 13219ab3..e4bce9f7 100644
25+--- a/src/stdlib/qsort.c
26++++ b/src/stdlib/qsort.c
27+@@ -89,10 +89,16 @@ static inline void shr(size_t p[2], int n)
28+ 	p[1] >>= n;
29+ }
30+ 
31++/* power-of-two length for working array so that we can mask indices and
32++ * not depend on any invariant of the algorithm for spatial memory safety.
33++ * the original size was just 14*sizeof(size_t)+1 */
34++#define AR_LEN  (16 * sizeof(size_t))
35++#define AR_MASK (AR_LEN - 1)
36++
37+ static void sift(unsigned char *head, size_t width, cmpfun cmp, void *arg, int pshift, size_t lp[])
38+ {
39+ 	unsigned char *rt, *lf;
40+-	unsigned char *ar[14 * sizeof(size_t) + 1];
41++	unsigned char *ar[AR_LEN];
42+ 	int i = 1;
43+ 
44+ 	ar[0] = head;
45+@@ -104,16 +110,16 @@ static void sift(unsigned char *head, size_t width, cmpfun cmp, void *arg, int p
46+ 			break;
47+ 		}
48+ 		if(cmp(lf, rt, arg) >= 0) {
49+-			ar[i++] = lf;
50++			ar[i++ & AR_MASK] = lf;
51+ 			head = lf;
52+ 			pshift -= 1;
53+ 		} else {
54+-			ar[i++] = rt;
55++			ar[i++ & AR_MASK] = rt;
56+ 			head = rt;
57+ 			pshift -= 2;
58+ 		}
59+ 	}
60+-	cycle(width, ar, i);
61++	cycle(width, ar, i & AR_MASK);
62+ }
63+ 
64+ static void trinkle(unsigned char *head, size_t width, cmpfun cmp, void *arg, size_t pp[2], int pshift, int trusty, size_t lp[])
65+@@ -121,7 +127,7 @@ static void trinkle(unsigned char *head, size_t width, cmpfun cmp, void *arg, si
66+ 	unsigned char *stepson,
67+ 	              *rt, *lf;
68+ 	size_t p[2];
69+-	unsigned char *ar[14 * sizeof(size_t) + 1];
70++	unsigned char *ar[AR_LEN];
71+ 	int i = 1;
72+ 	int trail;
73+ 
74+@@ -142,7 +148,7 @@ static void trinkle(unsigned char *head, size_t width, cmpfun cmp, void *arg, si
75+ 			}
76+ 		}
77+ 
78+-		ar[i++] = stepson;
79++		ar[i++ & AR_MASK] = stepson;
80+ 		head = stepson;
81+ 		trail = pntz(p);
82+ 		shr(p, trail);
83+@@ -150,7 +156,7 @@ static void trinkle(unsigned char *head, size_t width, cmpfun cmp, void *arg, si
84+ 		trusty = 0;
85+ 	}
86+ 	if(!trusty) {
87+-		cycle(width, ar, i);
88++		cycle(width, ar, i & AR_MASK);
89+ 		sift(head, width, cmp, arg, pshift, lp);
90+ 	}
91+ }
92+-- 
93+2.49.0
94+
+39, -0
 1@@ -0,0 +1,39 @@
 2+From 527aa84bb47dda0da09432d05caca0fd94cee692 Mon Sep 17 00:00:00 2001
 3+From: Luca Kellermann <mailto.luca.kellermann@gmail.com>
 4+Date: Fri, 10 Apr 2026 03:03:22 +0200
 5+Subject: [PATCH] qsort: fix shift UB in shl and shr
 6+
 7+if shl() or shr() are called with n==8*sizeof(size_t), n is adjusted
 8+to 0. the shift by (sizeof(size_t) * 8 - n) that then follows will
 9+consequently shift by the width of size_t, which is UB and in practice
10+produces an incorrect result.
11+
12+return early in this case. the bitvector p was already shifted by the
13+required amount.
14+---
15+ src/stdlib/qsort.c | 2 ++
16+ 1 file changed, 2 insertions(+)
17+
18+diff --git a/src/stdlib/qsort.c b/src/stdlib/qsort.c
19+index e4bce9f7..28607450 100644
20+--- a/src/stdlib/qsort.c
21++++ b/src/stdlib/qsort.c
22+@@ -71,6 +71,7 @@ static inline void shl(size_t p[2], int n)
23+ 		n -= 8 * sizeof(size_t);
24+ 		p[1] = p[0];
25+ 		p[0] = 0;
26++		if (!n) return;
27+ 	}
28+ 	p[1] <<= n;
29+ 	p[1] |= p[0] >> (sizeof(size_t) * 8 - n);
30+@@ -83,6 +84,7 @@ static inline void shr(size_t p[2], int n)
31+ 		n -= 8 * sizeof(size_t);
32+ 		p[0] = p[1];
33+ 		p[1] = 0;
34++		if (!n) return;
35+ 	}
36+ 	p[0] >>= n;
37+ 	p[0] |= p[1] << (sizeof(size_t) * 8 - n);
38+-- 
39+2.49.0
40+
+1, -1
1@@ -1 +1 @@
2-1.2.6
3+1.2.6 r1