Skip to content

Commit 715559e

Browse files
authored
Reject out-of-range code points in UTF32Reader (#293)
1 parent 568f8b2 commit 715559e

5 files changed

Lines changed: 75 additions & 15 deletions

File tree

release-notes/CREDITS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,5 @@ Aizal Khan (@aizu-m)
179179

180180
* Contributed fix #291: Reject overlong UTF-8 sequences in `UTF8Reader`
181181
(7.2.1)
182+
* Contributed fix #293: Reject out-of-range code points in `UTF32Reader`
183+
(7.2.1)

release-notes/VERSION

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Project: woodstox
88

99
#291: Reject overlong UTF-8 sequences in `UTF8Reader`
1010
(contributed by @aizu-m)
11+
#293: Reject out-of-range code points in `UTF32Reader`
12+
(contributed by @aizu-m)
1113

1214
7.2.0 (19-May-2026)
1315

src/main/java/com/ctc/wstx/io/UTF32Reader.java

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ public final class UTF32Reader
4949
protected int mByteCount = 0;
5050

5151
/*
52-
////////////////////////////////////////
52+
///////////////////////////////////////////////////////////
5353
// Life-cycle
54-
////////////////////////////////////////
54+
///////////////////////////////////////////////////////////
5555
*/
5656

5757
public UTF32Reader(ReaderConfig cfg, InputStream in, byte[] buf, int ptr, int len,
@@ -67,9 +67,9 @@ public void setXmlCompliancy(int xmlVersion) {
6767
}
6868

6969
/*
70-
////////////////////////////////////////
70+
///////////////////////////////////////////////////////////
7171
// Public API
72-
////////////////////////////////////////
72+
///////////////////////////////////////////////////////////
7373
*/
7474

7575
@Override
@@ -131,8 +131,17 @@ public int read(char[] cbuf, int start, int len) throws IOException
131131
}
132132
mBytePtr += 4;
133133

134+
// Any code point above the Unicode max is illegal; note that a
135+
// negative value here means the high byte's top bit was set, which
136+
// sign-extends into an out-of-range value rather than a valid char.
137+
if (ch < 0 || ch > XmlConsts.MAX_UNICODE_CHAR) {
138+
reportInvalid(ch, outPtr-start,
139+
"(above "+Integer.toHexString(XmlConsts.MAX_UNICODE_CHAR)+") ");
140+
}
141+
134142
// Does it need to be split to surrogates?
135-
// (also, we can and need to verify illegal chars)
143+
// (also, we still verify remaining illegal chars here, such as
144+
// surrogate code points and 0xFFFE/0xFFFF)
136145
if (ch >= 0x7F) {
137146
if (ch <= 0x9F) {
138147
if (mXml11) { // high-order ctrl char detection...
@@ -142,11 +151,6 @@ public int read(char[] cbuf, int start, int len) throws IOException
142151
ch = CONVERT_NEL_TO;
143152
}
144153
} else if (ch >= 0xD800) {
145-
// Illegal?
146-
if (ch > XmlConsts.MAX_UNICODE_CHAR) {
147-
reportInvalid(ch, outPtr-start,
148-
"(above "+Integer.toHexString(XmlConsts.MAX_UNICODE_CHAR)+") ");
149-
}
150154
if (ch > 0xFFFF) { // need to split into surrogates?
151155
ch -= 0x10000; // to normalize it starting with 0x0
152156
cbuf[outPtr++] = (char) (0xD800 + (ch >> 10));
@@ -177,9 +181,9 @@ public int read(char[] cbuf, int start, int len) throws IOException
177181
}
178182

179183
/*
180-
////////////////////////////////////////
184+
///////////////////////////////////////////////////////////
181185
// Internal methods
182-
////////////////////////////////////////
186+
///////////////////////////////////////////////////////////
183187
*/
184188

185189
private void reportUnexpectedEOF(int gotBytes, int needed)
@@ -255,4 +259,3 @@ private boolean loadMore(int available)
255259
return true;
256260
}
257261
}
258-

src/test/java/wstxtest/fuzz/Fuzz125_32969_UTF32ReadTest.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ public void testIssue125InputStream() throws Exception
3535
streamThrough(sr);
3636
fail("Should not pass");
3737
} catch (WstxIOException e) {
38-
verifyException(e, "Unexpected EOF in the middle of a 4-byte UTF-32 char");
38+
// 02-Jun-2026, [woodstox-core#293]: out-of-range code point (high
39+
// byte top bit set, sign-extends negative) is now rejected up
40+
// front instead of being truncated and slipping through to EOF.
41+
verifyException(e, "Invalid UTF-32 character");
3942
}
4043
sr.close();
4144
}
@@ -51,7 +54,8 @@ public void testIssue125Stax2ByteArray() throws Exception
5154
streamThrough(sr);
5255
fail("Should not pass");
5356
} catch (WstxIOException e) {
54-
verifyException(e, "Unexpected EOF in the middle of a 4-byte UTF-32 char");
57+
// 02-Jun-2026, [woodstox-core#293]: see above
58+
verifyException(e, "Invalid UTF-32 character");
5559
}
5660
sr.close();
5761
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package wstxtest.io;
2+
3+
import java.io.*;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
import com.ctc.wstx.api.ReaderConfig;
8+
import com.ctc.wstx.io.UTF32Reader;
9+
10+
public class TestUTF32Reader extends wstxtest.BaseJUnit4Test
11+
{
12+
@SuppressWarnings("resource")
13+
private UTF32Reader reader(byte[] input, boolean bigEndian) {
14+
ReaderConfig cfg = ReaderConfig.createFullDefaults();
15+
return new UTF32Reader(cfg, new ByteArrayInputStream(input),
16+
new byte[16], 0, 0, false, bigEndian);
17+
}
18+
19+
// Code points above U+10FFFF must be rejected, not truncated to 16 bits.
20+
// The high byte's top bit sign-extends the int negative, which previously
21+
// slipped past the range checks and decoded e.g. 80 00 00 3C to '<'.
22+
@Test
23+
public void testOutOfRangeBigEndian() throws Exception {
24+
try {
25+
int n = reader(new byte[]{(byte)0x80, 0x00, 0x00, 0x3C}, true)
26+
.read(new char[8], 0, 8);
27+
fail("Expected CharConversionException, got "+n+" char(s)");
28+
} catch (CharConversionException expected) { }
29+
}
30+
31+
@Test
32+
public void testOutOfRangeLittleEndian() throws Exception {
33+
try {
34+
int n = reader(new byte[]{0x3C, 0x00, 0x00, (byte)0x80}, false)
35+
.read(new char[8], 0, 8);
36+
fail("Expected CharConversionException, got "+n+" char(s)");
37+
} catch (CharConversionException expected) { }
38+
}
39+
40+
// Legal astral character (U+10000) still decodes to a surrogate pair.
41+
@Test
42+
public void testValidAstral() throws Exception {
43+
char[] cbuf = new char[8];
44+
int n = reader(new byte[]{0x00, 0x01, 0x00, 0x00}, true).read(cbuf, 0, 8);
45+
assertEquals(2, n);
46+
assertEquals('\uD800', cbuf[0]);
47+
assertEquals('\uDC00', cbuf[1]);
48+
}
49+
}

0 commit comments

Comments
 (0)