Skip to content

Commit 936c711

Browse files
Ekaterina VergizovaRealCLanger
authored andcommitted
8282252: Improve BigInteger/Decimal validation
Reviewed-by: mbalao Backport-of: e863e256f9e7465a97d3fb2a109299987e990b7c
1 parent 2957bb9 commit 936c711

File tree

5 files changed

+377
-56
lines changed

5 files changed

+377
-56
lines changed

src/java.base/share/classes/java/math/BigDecimal.java

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131

3232
import static java.math.BigInteger.LONG_MASK;
3333
import java.io.IOException;
34+
import java.io.InvalidObjectException;
35+
import java.io.ObjectInputStream;
36+
import java.io.ObjectStreamException;
37+
import java.io.StreamCorruptedException;
3438
import java.util.Arrays;
3539
import java.util.Objects;
3640

@@ -1058,6 +1062,15 @@ public BigDecimal(double val, MathContext mc) {
10581062
this.precision = prec;
10591063
}
10601064

1065+
/**
1066+
* Accept no subclasses.
1067+
*/
1068+
private static BigInteger toStrictBigInteger(BigInteger val) {
1069+
return (val.getClass() == BigInteger.class) ?
1070+
val :
1071+
new BigInteger(val.toByteArray().clone());
1072+
}
1073+
10611074
/**
10621075
* Translates a {@code BigInteger} into a {@code BigDecimal}.
10631076
* The scale of the {@code BigDecimal} is zero.
@@ -1067,8 +1080,8 @@ public BigDecimal(double val, MathContext mc) {
10671080
*/
10681081
public BigDecimal(BigInteger val) {
10691082
scale = 0;
1070-
intVal = val;
1071-
intCompact = compactValFor(val);
1083+
intVal = toStrictBigInteger(val);
1084+
intCompact = compactValFor(intVal);
10721085
}
10731086

10741087
/**
@@ -1082,7 +1095,7 @@ public BigDecimal(BigInteger val) {
10821095
* @since 1.5
10831096
*/
10841097
public BigDecimal(BigInteger val, MathContext mc) {
1085-
this(val,0,mc);
1098+
this(toStrictBigInteger(val), 0, mc);
10861099
}
10871100

10881101
/**
@@ -1096,8 +1109,8 @@ public BigDecimal(BigInteger val, MathContext mc) {
10961109
*/
10971110
public BigDecimal(BigInteger unscaledVal, int scale) {
10981111
// Negative scales are now allowed
1099-
this.intVal = unscaledVal;
1100-
this.intCompact = compactValFor(unscaledVal);
1112+
this.intVal = toStrictBigInteger(unscaledVal);
1113+
this.intCompact = compactValFor(this.intVal);
11011114
this.scale = scale;
11021115
}
11031116

@@ -1115,6 +1128,7 @@ public BigDecimal(BigInteger unscaledVal, int scale) {
11151128
* @since 1.5
11161129
*/
11171130
public BigDecimal(BigInteger unscaledVal, int scale, MathContext mc) {
1131+
unscaledVal = toStrictBigInteger(unscaledVal);
11181132
long compactVal = compactValFor(unscaledVal);
11191133
int mcp = mc.precision;
11201134
int prec = 0;
@@ -4257,9 +4271,13 @@ private static class UnsafeHolder {
42574271
= unsafe.objectFieldOffset(BigDecimal.class, "intCompact");
42584272
private static final long intValOffset
42594273
= unsafe.objectFieldOffset(BigDecimal.class, "intVal");
4274+
private static final long scaleOffset
4275+
= unsafe.objectFieldOffset(BigDecimal.class, "scale");
42604276

4261-
static void setIntCompact(BigDecimal bd, long val) {
4262-
unsafe.putLong(bd, intCompactOffset, val);
4277+
static void setIntValAndScale(BigDecimal bd, BigInteger intVal, int scale) {
4278+
unsafe.putReference(bd, intValOffset, intVal);
4279+
unsafe.putInt(bd, scaleOffset, scale);
4280+
unsafe.putLong(bd, intCompactOffset, compactValFor(intVal));
42634281
}
42644282

42654283
static void setIntValVolatile(BigDecimal bd, BigInteger val) {
@@ -4278,15 +4296,30 @@ static void setIntValVolatile(BigDecimal bd, BigInteger val) {
42784296
@java.io.Serial
42794297
private void readObject(java.io.ObjectInputStream s)
42804298
throws IOException, ClassNotFoundException {
4281-
// Read in all fields
4282-
s.defaultReadObject();
4283-
// validate possibly bad fields
4284-
if (intVal == null) {
4285-
String message = "BigDecimal: null intVal in stream";
4286-
throw new java.io.StreamCorruptedException(message);
4287-
// [all values of scale are now allowed]
4299+
// prepare to read the fields
4300+
ObjectInputStream.GetField fields = s.readFields();
4301+
BigInteger serialIntVal = (BigInteger) fields.get("intVal", null);
4302+
4303+
// Validate field data
4304+
if (serialIntVal == null) {
4305+
throw new StreamCorruptedException("Null or missing intVal in BigDecimal stream");
42884306
}
4289-
UnsafeHolder.setIntCompact(this, compactValFor(intVal));
4307+
// Validate provenance of serialIntVal object
4308+
serialIntVal = toStrictBigInteger(serialIntVal);
4309+
4310+
// Any integer value is valid for scale
4311+
int serialScale = fields.get("scale", 0);
4312+
4313+
UnsafeHolder.setIntValAndScale(this, serialIntVal, serialScale);
4314+
}
4315+
4316+
/**
4317+
* Serialization without data not supported for this class.
4318+
*/
4319+
@java.io.Serial
4320+
private void readObjectNoData()
4321+
throws ObjectStreamException {
4322+
throw new InvalidObjectException("Deserialized BigDecimal objects need data");
42904323
}
42914324

42924325
/**

src/java.base/share/classes/java/math/BigInteger.java

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@
3030
package java.math;
3131

3232
import java.io.IOException;
33+
import java.io.InvalidObjectException;
3334
import java.io.ObjectInputStream;
3435
import java.io.ObjectOutputStream;
3536
import java.io.ObjectStreamField;
37+
import java.io.ObjectStreamException;
3638
import java.util.Arrays;
3739
import java.util.Objects;
3840
import java.util.Random;
@@ -4706,17 +4708,21 @@ private void readObject(java.io.ObjectInputStream s)
47064708
// prepare to read the alternate persistent fields
47074709
ObjectInputStream.GetField fields = s.readFields();
47084710

4709-
// Read the alternate persistent fields that we care about
4710-
int sign = fields.get("signum", -2);
4711-
byte[] magnitude = (byte[])fields.get("magnitude", null);
4711+
// Read and validate the alternate persistent fields that we
4712+
// care about, signum and magnitude
47124713

4713-
// Validate signum
4714+
// Read and validate signum
4715+
int sign = fields.get("signum", -2);
47144716
if (sign < -1 || sign > 1) {
47154717
String message = "BigInteger: Invalid signum value";
47164718
if (fields.defaulted("signum"))
47174719
message = "BigInteger: Signum not present in stream";
47184720
throw new java.io.StreamCorruptedException(message);
47194721
}
4722+
4723+
// Read and validate magnitude
4724+
byte[] magnitude = (byte[])fields.get("magnitude", null);
4725+
magnitude = magnitude.clone(); // defensive copy
47204726
int[] mag = stripLeadingZeroBytes(magnitude, 0, magnitude.length);
47214727
if ((mag.length == 0) != (sign == 0)) {
47224728
String message = "BigInteger: signum-magnitude mismatch";
@@ -4725,18 +4731,24 @@ private void readObject(java.io.ObjectInputStream s)
47254731
throw new java.io.StreamCorruptedException(message);
47264732
}
47274733

4734+
// Equivalent to checkRange() on mag local without assigning
4735+
// this.mag field
4736+
if (mag.length > MAX_MAG_LENGTH ||
4737+
(mag.length == MAX_MAG_LENGTH && mag[0] < 0)) {
4738+
throw new java.io.StreamCorruptedException("BigInteger: Out of the supported range");
4739+
}
4740+
47284741
// Commit final fields via Unsafe
4729-
UnsafeHolder.putSign(this, sign);
4742+
UnsafeHolder.putSignAndMag(this, sign, mag);
4743+
}
47304744

4731-
// Calculate mag field from magnitude and discard magnitude
4732-
UnsafeHolder.putMag(this, mag);
4733-
if (mag.length >= MAX_MAG_LENGTH) {
4734-
try {
4735-
checkRange();
4736-
} catch (ArithmeticException e) {
4737-
throw new java.io.StreamCorruptedException("BigInteger: Out of the supported range");
4738-
}
4739-
}
4745+
/**
4746+
* Serialization without data not supported for this class.
4747+
*/
4748+
@java.io.Serial
4749+
private void readObjectNoData()
4750+
throws ObjectStreamException {
4751+
throw new InvalidObjectException("Deserialized BigInteger objects need data");
47404752
}
47414753

47424754
// Support for resetting final fields while deserializing
@@ -4748,11 +4760,8 @@ private static class UnsafeHolder {
47484760
private static final long magOffset
47494761
= unsafe.objectFieldOffset(BigInteger.class, "mag");
47504762

4751-
static void putSign(BigInteger bi, int sign) {
4763+
static void putSignAndMag(BigInteger bi, int sign, int[] magnitude) {
47524764
unsafe.putInt(bi, signumOffset, sign);
4753-
}
4754-
4755-
static void putMag(BigInteger bi, int[] magnitude) {
47564765
unsafe.putReference(bi, magOffset, magnitude);
47574766
}
47584767
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright (c) 2022, 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 8282252
27+
* @summary Test constructors of BigDecimal to replace BigInteger subclasses
28+
*/
29+
30+
import java.math.*;
31+
32+
public class ConstructorUnscaledValue {
33+
public static void main(String... args) {
34+
TestBigInteger tbi = new TestBigInteger(BigInteger.ONE);
35+
// Create BigDecimal's using each of the three constructors
36+
// with guards on the class of unscaledValue
37+
BigDecimal[] values = {
38+
new BigDecimal(tbi),
39+
new BigDecimal(tbi, 2),
40+
new BigDecimal(tbi, 3, MathContext.DECIMAL32),
41+
};
42+
43+
for (var bd : values) {
44+
BigInteger unscaledValue = bd.unscaledValue();
45+
if (unscaledValue.getClass() != BigInteger.class) {
46+
throw new RuntimeException("Bad class for unscaledValue");
47+
}
48+
if (!unscaledValue.equals(BigInteger.ONE)) {
49+
throw new RuntimeException("Bad value for unscaledValue");
50+
}
51+
}
52+
}
53+
54+
private static class TestBigInteger extends BigInteger {
55+
public TestBigInteger(BigInteger bi) {
56+
super(bi.toByteArray());
57+
}
58+
59+
@Override
60+
public String toString() {
61+
return java.util.Arrays.toString(toByteArray());
62+
}
63+
}
64+
}

0 commit comments

Comments
 (0)