Skip to content

Commit 7f0859f

Browse files
1 parent 71abc62 commit 7f0859f

3 files changed

Lines changed: 503 additions & 1 deletion

File tree

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
https://www.openwall.com/lists/musl/2026/04/10/3
2+
3+
>From 228da39e38c1cae13cbe637e771412c1984dba5d Mon Sep 17 00:00:00 2001
4+
From: Rich Felker <dalias@aerifal.cx>
5+
Date: Thu, 9 Apr 2026 22:51:30 -0400
6+
Subject: [PATCH 1/3] qsort: fix leonardo heap corruption from bug in
7+
doubleword ctz primitive
8+
9+
the pntz function, implementing a "count trailing zeros" variant for a
10+
bit vector consisting of two size_t words, erroneously returned zero
11+
rather than the number of bits in the low word when the first bit set
12+
was the low bit of the high word.
13+
14+
as a result, a loop in the trinkle function which should have a
15+
guaranteed small bound on the number of iterations, could run
16+
unboundedly, thereby overflowing a stack-based working-space array
17+
which was sized for the bound.
18+
19+
CVE-2026-40200 has been assigned for this issue.
20+
---
21+
src/stdlib/qsort.c | 8 ++++----
22+
1 file changed, 4 insertions(+), 4 deletions(-)
23+
24+
diff --git a/src/stdlib/qsort.c b/src/stdlib/qsort.c
25+
index ab79dc6f..13219ab3 100644
26+
--- a/src/stdlib/qsort.c
27+
+++ b/src/stdlib/qsort.c
28+
@@ -34,11 +34,11 @@
29+
30+
typedef int (*cmpfun)(const void *, const void *, void *);
31+
32+
+/* returns index of first bit set, excluding the low bit assumed to always
33+
+ * be set, starting from low bit of p[0] up through high bit of p[1] */
34+
static inline int pntz(size_t p[2]) {
35+
- int r = ntz(p[0] - 1);
36+
- if(r != 0 || (r = 8*sizeof(size_t) + ntz(p[1])) != 8*sizeof(size_t)) {
37+
- return r;
38+
- }
39+
+ if (p[0] != 1) return ntz(p[0] - 1);
40+
+ if (p[1]) return 8*sizeof(size_t) + ntz(p[1]);
41+
return 0;
42+
}
43+
44+
--
45+
2.21.0
46+
47+
48+
>From b3291b9a9f77f1f993d2b4f8c68a26cf09221ae7 Mon Sep 17 00:00:00 2001
49+
From: Rich Felker <dalias@aerifal.cx>
50+
Date: Thu, 9 Apr 2026 23:40:53 -0400
51+
Subject: [PATCH 2/3] qsort: hard-preclude oob array writes independent of any
52+
invariants
53+
54+
while the root cause of CVE-2026-40200 was a faulty ctz primitive, the
55+
fallout of the bug would have been limited to erroneous sorting or
56+
infinite loop if not for the stores to a stack-based array that
57+
depended on trusting invariants in order not to go out of bounds.
58+
59+
increase the size of the array to a power of two so that we can mask
60+
indices into it to force them into range. in the absence of any
61+
further bug, the masking is a no-op, but it does not have any
62+
measurable performance cost, and it makes spatial memory safety
63+
trivial to prove (and for readers not familiar with the algorithms to
64+
trust).
65+
---
66+
src/stdlib/qsort.c | 20 +++++++++++++-------
67+
1 file changed, 13 insertions(+), 7 deletions(-)
68+
69+
diff --git a/src/stdlib/qsort.c b/src/stdlib/qsort.c
70+
index 13219ab3..e4bce9f7 100644
71+
--- a/src/stdlib/qsort.c
72+
+++ b/src/stdlib/qsort.c
73+
@@ -89,10 +89,16 @@ static inline void shr(size_t p[2], int n)
74+
p[1] >>= n;
75+
}
76+
77+
+/* power-of-two length for working array so that we can mask indices and
78+
+ * not depend on any invariant of the algorithm for spatial memory safety.
79+
+ * the original size was just 14*sizeof(size_t)+1 */
80+
+#define AR_LEN (16 * sizeof(size_t))
81+
+#define AR_MASK (AR_LEN - 1)
82+
+
83+
static void sift(unsigned char *head, size_t width, cmpfun cmp, void *arg, int pshift, size_t lp[])
84+
{
85+
unsigned char *rt, *lf;
86+
- unsigned char *ar[14 * sizeof(size_t) + 1];
87+
+ unsigned char *ar[AR_LEN];
88+
int i = 1;
89+
90+
ar[0] = head;
91+
@@ -104,16 +110,16 @@ static void sift(unsigned char *head, size_t width, cmpfun cmp, void *arg, int p
92+
break;
93+
}
94+
if(cmp(lf, rt, arg) >= 0) {
95+
- ar[i++] = lf;
96+
+ ar[i++ & AR_MASK] = lf;
97+
head = lf;
98+
pshift -= 1;
99+
} else {
100+
- ar[i++] = rt;
101+
+ ar[i++ & AR_MASK] = rt;
102+
head = rt;
103+
pshift -= 2;
104+
}
105+
}
106+
- cycle(width, ar, i);
107+
+ cycle(width, ar, i & AR_MASK);
108+
}
109+
110+
static void trinkle(unsigned char *head, size_t width, cmpfun cmp, void *arg, size_t pp[2], int pshift, int trusty, size_t lp[])
111+
@@ -121,7 +127,7 @@ static void trinkle(unsigned char *head, size_t width, cmpfun cmp, void *arg, si
112+
unsigned char *stepson,
113+
*rt, *lf;
114+
size_t p[2];
115+
- unsigned char *ar[14 * sizeof(size_t) + 1];
116+
+ unsigned char *ar[AR_LEN];
117+
int i = 1;
118+
int trail;
119+
120+
@@ -142,7 +148,7 @@ static void trinkle(unsigned char *head, size_t width, cmpfun cmp, void *arg, si
121+
}
122+
}
123+
124+
- ar[i++] = stepson;
125+
+ ar[i++ & AR_MASK] = stepson;
126+
head = stepson;
127+
trail = pntz(p);
128+
shr(p, trail);
129+
@@ -150,7 +156,7 @@ static void trinkle(unsigned char *head, size_t width, cmpfun cmp, void *arg, si
130+
trusty = 0;
131+
}
132+
if(!trusty) {
133+
- cycle(width, ar, i);
134+
+ cycle(width, ar, i & AR_MASK);
135+
sift(head, width, cmp, arg, pshift, lp);
136+
}
137+
}
138+
--
139+
2.21.0
140+
141+
142+
>From 5122f9f3c99fee366167c5de98b31546312921ab Mon Sep 17 00:00:00 2001
143+
From: Luca Kellermann <mailto.luca.kellermann@gmail.com>
144+
Date: Fri, 10 Apr 2026 03:03:22 +0200
145+
Subject: [PATCH 3/3] qsort: fix shift UB in shl and shr
146+
147+
if shl() or shr() are called with n==8*sizeof(size_t), n is adjusted
148+
to 0. the shift by (sizeof(size_t) * 8 - n) that then follows will
149+
consequently shift by the width of size_t, which is UB and in practice
150+
produces an incorrect result.
151+
152+
return early in this case. the bitvector p was already shifted by the
153+
required amount.
154+
---
155+
src/stdlib/qsort.c | 2 ++
156+
1 file changed, 2 insertions(+)
157+
158+
diff --git a/src/stdlib/qsort.c b/src/stdlib/qsort.c
159+
index e4bce9f7..28607450 100644
160+
--- a/src/stdlib/qsort.c
161+
+++ b/src/stdlib/qsort.c
162+
@@ -71,6 +71,7 @@ static inline void shl(size_t p[2], int n)
163+
n -= 8 * sizeof(size_t);
164+
p[1] = p[0];
165+
p[0] = 0;
166+
+ if (!n) return;
167+
}
168+
p[1] <<= n;
169+
p[1] |= p[0] >> (sizeof(size_t) * 8 - n);
170+
@@ -83,6 +84,7 @@ static inline void shr(size_t p[2], int n)
171+
n -= 8 * sizeof(size_t);
172+
p[0] = p[1];
173+
p[1] = 0;
174+
+ if (!n) return;
175+
}
176+
p[0] >>= n;
177+
p[0] |= p[1] << (sizeof(size_t) * 8 - n);
178+
--
179+
2.21.0
180+
181+

0 commit comments

Comments
 (0)