Skip to content

Commit 0ec4708

Browse files
committed
8361748: Enforce limits on the size of an XBM image
Backport-of: c71be802b530034169d17325478dba6e2f1c3238
1 parent 7a2b349 commit 0ec4708

File tree

7 files changed

+212
-88
lines changed

7 files changed

+212
-88
lines changed
Lines changed: 117 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1995, 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1995, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -23,13 +23,22 @@
2323
* questions.
2424
*/
2525

26-
/*-
26+
/*
2727
* Reads xbitmap format images into a DIBitmap structure.
2828
*/
2929
package sun.awt.image;
3030

31-
import java.io.*;
32-
import java.awt.image.*;
31+
import java.awt.image.ImageConsumer;
32+
import java.awt.image.IndexColorModel;
33+
import java.io.BufferedInputStream;
34+
import java.io.BufferedReader;
35+
import java.io.IOException;
36+
import java.io.InputStream;
37+
import java.io.InputStreamReader;
38+
import java.util.regex.Matcher;
39+
import java.util.regex.Pattern;
40+
41+
import static java.lang.Math.multiplyExact;
3342

3443
/**
3544
* Parse files of the form:
@@ -50,6 +59,8 @@ public class XbmImageDecoder extends ImageDecoder {
5059
ImageConsumer.COMPLETESCANLINES |
5160
ImageConsumer.SINGLEPASS |
5261
ImageConsumer.SINGLEFRAME);
62+
private static final int MAX_XBM_SIZE = 16384;
63+
private static final int HEADER_SCAN_LIMIT = 100;
5364

5465
public XbmImageDecoder(InputStreamImageSource src, InputStream is) {
5566
super(src, is);
@@ -72,107 +83,125 @@ private static void error(String s1) throws ImageFormatException {
7283
* produce an image from the stream.
7384
*/
7485
public void produceImage() throws IOException, ImageFormatException {
75-
char[] nm = new char[80];
76-
int c;
77-
int i = 0;
78-
int state = 0;
7986
int H = 0;
8087
int W = 0;
8188
int x = 0;
8289
int y = 0;
83-
boolean start = true;
90+
int n = 0;
91+
int state = 0;
8492
byte[] raster = null;
8593
IndexColorModel model = null;
86-
while (!aborted && (c = input.read()) != -1) {
87-
if ('a' <= c && c <= 'z' ||
88-
'A' <= c && c <= 'Z' ||
89-
'0' <= c && c <= '9' || c == '#' || c == '_') {
90-
if (i < 78)
91-
nm[i++] = (char) c;
92-
} else if (i > 0) {
93-
int nc = i;
94-
i = 0;
95-
if (start) {
96-
if (nc != 7 ||
97-
nm[0] != '#' ||
98-
nm[1] != 'd' ||
99-
nm[2] != 'e' ||
100-
nm[3] != 'f' ||
101-
nm[4] != 'i' ||
102-
nm[5] != 'n' ||
103-
nm[6] != 'e')
104-
{
105-
error("Not an XBM file");
94+
95+
String matchRegex = "(0[xX])?[0-9a-fA-F]+[\\s+]?[,|};]";
96+
String replaceRegex = "(0[xX])|,|[\\s+]|[};]";
97+
98+
String line;
99+
int lineNum = 0;
100+
101+
try (BufferedReader br = new BufferedReader(new InputStreamReader(input))) {
102+
// loop to process XBM header - width, height and create raster
103+
while (!aborted && (line = br.readLine()) != null
104+
&& lineNum <= HEADER_SCAN_LIMIT) {
105+
lineNum++;
106+
// process #define stmts
107+
if (line.trim().startsWith("#define")) {
108+
String[] token = line.split("\\s+");
109+
if (token.length != 3) {
110+
error("Error while parsing define statement");
111+
}
112+
try {
113+
if (!token[2].isBlank() && state == 0) {
114+
W = Integer.parseInt(token[2]);
115+
state = 1; // after width is set
116+
} else if (!token[2].isBlank() && state == 1) {
117+
H = Integer.parseInt(token[2]);
118+
state = 2; // after height is set
119+
}
120+
} catch (NumberFormatException nfe) {
121+
// parseInt() can throw NFE
122+
error("Error while parsing width or height.");
106123
}
107-
start = false;
108124
}
109-
if (nm[nc - 1] == 'h')
110-
state = 1; /* expecting width */
111-
else if (nm[nc - 1] == 't' && nc > 1 && nm[nc - 2] == 'h')
112-
state = 2; /* expecting height */
113-
else if (nc > 2 && state < 0 && nm[0] == '0' && nm[1] == 'x') {
114-
int n = 0;
115-
for (int p = 2; p < nc; p++) {
116-
c = nm[p];
117-
if ('0' <= c && c <= '9')
118-
c = c - '0';
119-
else if ('A' <= c && c <= 'Z')
120-
c = c - 'A' + 10;
121-
else if ('a' <= c && c <= 'z')
122-
c = c - 'a' + 10;
123-
else
124-
c = 0;
125-
n = n * 16 + c;
125+
126+
if (state == 2) {
127+
if (W <= 0 || H <= 0) {
128+
error("Invalid values for width or height.");
126129
}
127-
for (int mask = 1; mask <= 0x80; mask <<= 1) {
128-
if (x < W) {
129-
if ((n & mask) != 0)
130-
raster[x] = 1;
131-
else
132-
raster[x] = 0;
133-
}
134-
x++;
130+
if (multiplyExact(W, H) > MAX_XBM_SIZE) {
131+
error("Large XBM file size."
132+
+ " Maximum allowed size: " + MAX_XBM_SIZE);
135133
}
136-
if (x >= W) {
137-
if (setPixels(0, y, W, 1, model, raster, 0, W) <= 0) {
138-
return;
134+
model = new IndexColorModel(8, 2, XbmColormap,
135+
0, false, 0);
136+
setDimensions(W, H);
137+
setColorModel(model);
138+
setHints(XbmHints);
139+
headerComplete();
140+
raster = new byte[W];
141+
state = 3;
142+
break;
143+
}
144+
}
145+
146+
if (state != 3) {
147+
error("Width or Height of XBM file not defined");
148+
}
149+
150+
// loop to process image data
151+
while (!aborted && (line = br.readLine()) != null) {
152+
lineNum++;
153+
154+
if (line.contains("[]")) {
155+
Matcher matcher = Pattern.compile(matchRegex).matcher(line);
156+
while (matcher.find()) {
157+
if (y >= H) {
158+
error("Scan size of XBM file exceeds"
159+
+ " the defined width x height");
160+
}
161+
162+
int startIndex = matcher.start();
163+
int endIndex = matcher.end();
164+
String hexByte = line.substring(startIndex, endIndex);
165+
166+
if (!(hexByte.startsWith("0x")
167+
|| hexByte.startsWith("0X"))) {
168+
error("Invalid hexadecimal number at Ln#:" + lineNum
169+
+ " Col#:" + (startIndex + 1));
139170
}
140-
x = 0;
141-
if (y++ >= H) {
142-
break;
171+
hexByte = hexByte.replaceAll(replaceRegex, "");
172+
if (hexByte.length() != 2) {
173+
error("Invalid hexadecimal number at Ln#:" + lineNum
174+
+ " Col#:" + (startIndex + 1));
143175
}
144-
}
145-
} else {
146-
int n = 0;
147-
for (int p = 0; p < nc; p++)
148-
if ('0' <= (c = nm[p]) && c <= '9')
149-
n = n * 10 + c - '0';
150-
else {
151-
n = -1;
152-
break;
176+
177+
try {
178+
n = Integer.parseInt(hexByte, 16);
179+
} catch (NumberFormatException nfe) {
180+
error("Error parsing hexadecimal at Ln#:" + lineNum
181+
+ " Col#:" + (startIndex + 1));
182+
}
183+
for (int mask = 1; mask <= 0x80; mask <<= 1) {
184+
if (x < W) {
185+
if ((n & mask) != 0)
186+
raster[x] = 1;
187+
else
188+
raster[x] = 0;
189+
}
190+
x++;
153191
}
154-
if (n > 0 && state > 0) {
155-
if (state == 1)
156-
W = n;
157-
else
158-
H = n;
159-
if (W == 0 || H == 0)
160-
state = 0;
161-
else {
162-
model = new IndexColorModel(8, 2, XbmColormap,
163-
0, false, 0);
164-
setDimensions(W, H);
165-
setColorModel(model);
166-
setHints(XbmHints);
167-
headerComplete();
168-
raster = new byte[W];
169-
state = -1;
192+
193+
if (x >= W) {
194+
int result = setPixels(0, y, W, 1, model, raster, 0, W);
195+
if (result <= 0) {
196+
error("Unexpected error occurred during setPixel()");
197+
}
198+
x = 0;
199+
y++;
170200
}
171201
}
172202
}
173203
}
204+
imageComplete(ImageConsumer.STATICIMAGEDONE, true);
174205
}
175-
input.close();
176-
imageComplete(ImageConsumer.STATICIMAGEDONE, true);
177206
}
178207
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8361748
27+
* @summary Tests XBM image size limits and if XBMImageDecoder.produceImage()
28+
* throws appropriate error when parsing invalid XBM image data.
29+
* @run main XBMDecoderTest
30+
*/
31+
32+
import java.io.ByteArrayOutputStream;
33+
import java.io.File;
34+
import java.io.FileInputStream;
35+
import java.io.PrintStream;
36+
import javax.swing.ImageIcon;
37+
38+
public class XBMDecoderTest {
39+
40+
public static void main(String[] args) throws Exception {
41+
String dir = System.getProperty("test.src");
42+
PrintStream originalErr = System.err;
43+
boolean validCase;
44+
45+
File currentDir = new File(dir);
46+
File[] files = currentDir.listFiles((File d, String s)
47+
-> s.endsWith(".xbm"));
48+
49+
for (File file : files) {
50+
String fileName = file.getName();
51+
validCase = fileName.startsWith("valid");
52+
53+
System.out.println("--- Testing " + fileName + " ---");
54+
try (FileInputStream fis = new FileInputStream(file);
55+
ByteArrayOutputStream errContent = new ByteArrayOutputStream()) {
56+
System.setErr(new PrintStream(errContent));
57+
58+
ImageIcon icon = new ImageIcon(fis.readAllBytes());
59+
boolean isErrEmpty = errContent.toString().isEmpty();
60+
if (!isErrEmpty) {
61+
System.out.println("Expected ImageFormatException occurred.");
62+
System.out.print(errContent);
63+
}
64+
65+
if (validCase && !isErrEmpty) {
66+
throw new RuntimeException("Test failed: Error stream not empty");
67+
} else if (!validCase && isErrEmpty) {
68+
throw new RuntimeException("Test failed: ImageFormatException"
69+
+ " expected but not thrown");
70+
}
71+
System.out.println("PASSED\n");
72+
} finally {
73+
System.setErr(originalErr);
74+
}
75+
}
76+
}
77+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#define k_ht 3
2+
h` k[] = { 01x0, 42222222222236319330::
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#define k_wt 16
2+
#define k_ht 1
3+
k[] = { 0x10, 1234567890};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#define k_wt 16
2+
#define k_ht 0
3+
k[] = { 0x10, 0x12};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#define test_width 16
2+
#define test_height 3
3+
#define ht_x 1
4+
#define ht_y 2
5+
static unsigned char test_bits[] = {
6+
0x13, 0x11, 0x15, 0x00, 0xAB, 0xcd };
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#define test_width 16
2+
#define test_height 2
3+
static unsigned char test_bits[] = { 0x13, 0x11,
4+
0xAB, 0xff };

0 commit comments

Comments
 (0)