Skip to content

Commit 0252742

Browse files
committed
added system properties to allow setting of max read length and max construction depth.
1 parent 0976c80 commit 0252742

6 files changed

Lines changed: 178 additions & 6 deletions

File tree

core/src/main/java/org/bouncycastle/asn1/ASN1InputStream.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.io.IOException;
77
import java.io.InputStream;
88

9+
import org.bouncycastle.util.Properties;
910
import org.bouncycastle.util.io.Streams;
1011

1112
/**
@@ -18,9 +19,13 @@ public class ASN1InputStream
1819
extends FilterInputStream
1920
implements BERTags
2021
{
22+
static final String MAX_CONS_DEPTH = "org.bouncycastle.asn1.max_cons_depth";
23+
2124
private final int limit;
2225
private final boolean lazyEvaluate;
2326
private final byte[][] tmpBuffers;
27+
private final int level;
28+
private final int maxLevel;
2429

2530
public ASN1InputStream(InputStream is)
2631
{
@@ -92,9 +97,21 @@ private ASN1InputStream(InputStream input, int limit, boolean lazyEvaluate, byte
9297
this.limit = limit;
9398
this.lazyEvaluate = lazyEvaluate;
9499
this.tmpBuffers = tmpBuffers;
100+
this.level = 0;
101+
this.maxLevel = Properties.asInteger(MAX_CONS_DEPTH, 32);
102+
}
103+
104+
private ASN1InputStream(InputStream input, int limit, boolean lazyEvaluate, byte[][] tmpBuffers, int level, int maxLevel)
105+
{
106+
super(input);
107+
this.limit = limit;
108+
this.lazyEvaluate = lazyEvaluate;
109+
this.tmpBuffers = tmpBuffers;
110+
this.level = level;
111+
this.maxLevel = maxLevel;
95112
}
96113

97-
int getLimit()
114+
protected int getLimit()
98115
{
99116
return limit;
100117
}
@@ -329,7 +346,12 @@ ASN1EncodableVector readVector(DefiniteLengthInputStream defIn) throws IOExcepti
329346
return new ASN1EncodableVector(0);
330347
}
331348

332-
return new ASN1InputStream(defIn, remaining, lazyEvaluate, tmpBuffers).readVector();
349+
if (this.level == this.maxLevel)
350+
{
351+
throw new IOException("maximum nested construction level reached - increase " + MAX_CONS_DEPTH + " (currently " + maxLevel + ")");
352+
}
353+
354+
return new ASN1InputStream(defIn, remaining, lazyEvaluate, tmpBuffers, level + 1, maxLevel).readVector();
333355
}
334356

335357
static int readTagNumber(InputStream s, int tag)

core/src/main/java/org/bouncycastle/asn1/ASN1StreamParser.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import java.io.IOException;
55
import java.io.InputStream;
66

7+
import org.bouncycastle.util.Properties;
8+
79
/**
810
* A parser for ASN.1 streams which also returns, where possible, parsers for the objects it encounters.
911
*/
@@ -12,6 +14,8 @@ public class ASN1StreamParser
1214
private final InputStream _in;
1315
private final int _limit;
1416
private final byte[][] tmpBuffers;
17+
private final int level;
18+
private final int maxLevel;
1519

1620
public ASN1StreamParser(InputStream in)
1721
{
@@ -33,6 +37,17 @@ public ASN1StreamParser(InputStream in, int limit)
3337
this._in = in;
3438
this._limit = limit;
3539
this.tmpBuffers = tmpBuffers;
40+
this.level = 0;
41+
this.maxLevel = Properties.asInteger(ASN1InputStream.MAX_CONS_DEPTH, 32);
42+
}
43+
44+
private ASN1StreamParser(InputStream in, int limit, byte[][] tmpBuffers, int level, int maxLevel)
45+
{
46+
this._in = in;
47+
this._limit = limit;
48+
this.tmpBuffers = tmpBuffers;
49+
this.level = level;
50+
this.maxLevel = maxLevel;
3651
}
3752

3853
public ASN1Encodable readObject() throws IOException
@@ -48,6 +63,11 @@ public ASN1Encodable readObject() throws IOException
4863

4964
ASN1Encodable implParseObject(int tagHdr) throws IOException
5065
{
66+
if (this.level == this.maxLevel)
67+
{
68+
throw new IOException("maximum nested construction level reached - increase " + ASN1InputStream.MAX_CONS_DEPTH + " (currently " + maxLevel + ")");
69+
}
70+
5171
//
5272
// turn off looking for "00" while we resolve the tag
5373
//
@@ -73,7 +93,7 @@ ASN1Encodable implParseObject(int tagHdr) throws IOException
7393
}
7494

7595
IndefiniteLengthInputStream indIn = new IndefiniteLengthInputStream(_in, _limit);
76-
ASN1StreamParser sp = new ASN1StreamParser(indIn, _limit, tmpBuffers);
96+
ASN1StreamParser sp = new ASN1StreamParser(indIn, _limit, tmpBuffers, level + 1, maxLevel);
7797

7898
int tagClass = tagHdr & BERTags.PRIVATE;
7999
if (0 != tagClass)
@@ -92,7 +112,7 @@ ASN1Encodable implParseObject(int tagHdr) throws IOException
92112
return parseImplicitPrimitive(tagNo, defIn);
93113
}
94114

95-
ASN1StreamParser sp = new ASN1StreamParser(defIn, defIn.getLimit(), tmpBuffers);
115+
ASN1StreamParser sp = new ASN1StreamParser(defIn, defIn.getLimit(), tmpBuffers, level + 1, maxLevel);
96116

97117
int tagClass = tagHdr & BERTags.PRIVATE;
98118
if (0 != tagClass)

core/src/main/java/org/bouncycastle/asn1/StreamUtil.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@
66
import java.io.InputStream;
77
import java.nio.channels.FileChannel;
88

9+
import org.bouncycastle.util.Properties;
10+
911
class StreamUtil
1012
{
13+
static final String MAX_LIMIT = "org.bouncycastle.asn1.max_limit";
14+
1115
/**
1216
* Find out possible longest length, capped by available memory.
1317
*
@@ -46,12 +50,32 @@ else if (in instanceof FileInputStream)
4650
}
4751
}
4852

53+
String limit = Properties.getPropertyValue(MAX_LIMIT);
54+
if (limit != null)
55+
{
56+
switch (limit.charAt(limit.length() - 1))
57+
{
58+
case 'k':
59+
return Integer.parseInt(limit.substring(0, limit.length() - 1)) * 1024;
60+
case 'm':
61+
return Integer.parseInt(limit.substring(0, limit.length() - 1)) * 1024 * 1024;
62+
case 'g':
63+
return Integer.parseInt(limit.substring(0, limit.length() - 1)) * 1024 * 1024 * 1024;
64+
default:
65+
return Integer.parseInt(limit);
66+
}
67+
}
68+
69+
return getMaxMemory();
70+
}
71+
72+
private static int getMaxMemory()
73+
{
4974
long maxMemory = Runtime.getRuntime().maxMemory();
5075
if (maxMemory > Integer.MAX_VALUE)
5176
{
5277
return Integer.MAX_VALUE;
5378
}
54-
5579
return (int)maxMemory;
5680
}
5781
}

core/src/test/java/org/bouncycastle/asn1/test/ASN1SequenceParserTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@
88
import junit.framework.Test;
99
import junit.framework.TestCase;
1010
import junit.framework.TestSuite;
11+
import org.bouncycastle.asn1.ASN1InputStream;
1112
import org.bouncycastle.asn1.ASN1Integer;
1213
import org.bouncycastle.asn1.ASN1Null;
1314
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
15+
import org.bouncycastle.asn1.ASN1Sequence;
1416
import org.bouncycastle.asn1.ASN1SequenceParser;
1517
import org.bouncycastle.asn1.ASN1StreamParser;
1618
import org.bouncycastle.asn1.BERSequenceGenerator;
1719
import org.bouncycastle.asn1.DERSequenceGenerator;
20+
import org.bouncycastle.test.TestResourceFinder;
1821
import org.bouncycastle.util.encoders.Hex;
1922

2023
public class ASN1SequenceParserTest
@@ -329,6 +332,34 @@ public void testBERExplicitTaggedSequenceWriting()
329332
assertTrue("explicit BER tag writing test failed.", Arrays.equals(berExpTagSeqData, bOut.toByteArray()));
330333
}
331334

335+
public void testHeavilyDLNestedSequence()
336+
throws Exception
337+
{
338+
try
339+
{
340+
ASN1Sequence seq = ASN1Sequence.getInstance(new ASN1InputStream(TestResourceFinder.findTestResource("asn1", "nested_seq.der")).readObject());
341+
fail("no exception");
342+
}
343+
catch (IOException e)
344+
{
345+
assertEquals("maximum nested construction level reached - increase org.bouncycastle.asn1.max_cons_depth (currently 32)", e.getMessage());
346+
}
347+
}
348+
349+
public void testHeavilyBERNestedSequence()
350+
throws Exception
351+
{
352+
try
353+
{
354+
ASN1Sequence seq = ASN1Sequence.getInstance(new ASN1InputStream(TestResourceFinder.findTestResource("asn1", "nested_seq_indef.ber")).readObject());
355+
fail("no exception");
356+
}
357+
catch (IOException e)
358+
{
359+
assertEquals("maximum nested construction level reached - increase org.bouncycastle.asn1.max_cons_depth (currently 32)", e.getMessage());
360+
}
361+
}
362+
332363
public void testSequenceWithDERNullReading()
333364
throws Exception
334365
{

core/src/test/java/org/bouncycastle/asn1/test/RegressionTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ public class RegressionTest
5757
new DERPrivateTest(),
5858
new X509AltTest(),
5959
new CertIDTest(),
60-
new IANAObjectIdentifierTest()
60+
new IANAObjectIdentifierTest(),
61+
new StreamLimitTest()
6162
};
6263

6364
public static void main(String[] args)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package org.bouncycastle.asn1.test;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
6+
import org.bouncycastle.asn1.ASN1InputStream;
7+
import org.bouncycastle.util.test.SimpleTest;
8+
9+
public class StreamLimitTest
10+
extends SimpleTest
11+
{
12+
static final String MAX_LIMIT = "org.bouncycastle.asn1.max_limit";
13+
14+
public void performTest()
15+
throws Exception
16+
{
17+
System.setProperty("org.bouncycastle.asn1.max_limit", "1024");
18+
19+
MyASN1InputStream asn1In = new MyASN1InputStream();
20+
21+
isEquals(1024, asn1In.getLimit());
22+
23+
System.setProperty("org.bouncycastle.asn1.max_limit", "1024k");
24+
25+
asn1In = new MyASN1InputStream();
26+
27+
isEquals(1048576, asn1In.getLimit());
28+
29+
System.setProperty("org.bouncycastle.asn1.max_limit", "1024m");
30+
31+
asn1In = new MyASN1InputStream();
32+
33+
isEquals(1073741824, asn1In.getLimit());
34+
35+
System.setProperty("org.bouncycastle.asn1.max_limit", "1g");
36+
37+
asn1In = new MyASN1InputStream();
38+
39+
isEquals(1073741824, asn1In.getLimit());
40+
}
41+
42+
public String getName()
43+
{
44+
return "StreamLimit";
45+
}
46+
47+
public static void main(
48+
String[] args)
49+
{
50+
runTest(new StreamLimitTest());
51+
}
52+
53+
private static class MyASN1InputStream
54+
extends ASN1InputStream
55+
{
56+
MyASN1InputStream()
57+
{
58+
super(new InputStream()
59+
{
60+
@Override
61+
public int read()
62+
throws IOException
63+
{
64+
return 0;
65+
}
66+
});
67+
}
68+
69+
public int getLimit()
70+
{
71+
return super.getLimit();
72+
}
73+
}
74+
}

0 commit comments

Comments
 (0)