--- old/src/java.base/share/classes/java/util/Base64.java 2019-01-17 12:14:23.643000000 +0530 +++ new/src/java.base/share/classes/java/util/Base64.java 2019-01-17 12:14:23.075000000 +0530 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,6 +30,7 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; +import java.nio.charset.CharacterCodingException; import java.nio.charset.StandardCharsets; import jdk.internal.HotSpotIntrinsicCandidate; @@ -186,6 +187,10 @@ * a method of this class will cause a * {@link java.lang.NullPointerException NullPointerException} to * be thrown. + *

If the encoded byte output of the needed size can not + * be allocated, the encode methods of this class will + * cause an {@link java.lang.OutOfMemoryError OutOfMemoryError} + * to be thrown. * * @see Decoder * @since 1.8 @@ -237,16 +242,27 @@ static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1, true); static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX, true); - private final int outLength(int srclen) { + private final int outLength(int srclen, boolean withOutputParam) { int len = 0; - if (doPadding) { - len = 4 * ((srclen + 2) / 3); - } else { - int n = srclen % 3; - len = 4 * (srclen / 3) + (n == 0 ? 0 : n + 1); + try { + if (doPadding) { + len = Math.multiplyExact(4, (Math.addExact(srclen, 2) / 3)); + } else { + int n = srclen % 3; + len = Math.addExact(Math.multiplyExact(4, (srclen / 3)), (n == 0 ? 0 : n + 1)); + } + if (linemax > 0) { // line separators + len = Math.addExact(len, (len - 1) / linemax * newline.length); + } + } catch (ArithmeticException ex) { + if (!withOutputParam) { + throw new OutOfMemoryError("Encoded size is too large"); + } else { + // let the caller know that encoded bytes can not fit + // in the passed output array param + len = -1; + } } - if (linemax > 0) // line separators - len += (len - 1) / linemax * newline.length; return len; } @@ -261,7 +277,7 @@ * encoded bytes. */ public byte[] encode(byte[] src) { - int len = outLength(src.length); // dst array size + int len = outLength(src.length, false); // dst array size byte[] dst = new byte[len]; int ret = encode0(src, 0, src.length, dst); if (ret != dst.length) @@ -289,8 +305,8 @@ * space for encoding all input bytes. */ public int encode(byte[] src, byte[] dst) { - int len = outLength(src.length); // dst array size - if (dst.length < len) + int len = outLength(src.length, true); // dst array size + if (dst.length < len || len == -1) throw new IllegalArgumentException( "Output byte array is too small for encoding all input bytes"); return encode0(src, 0, src.length, dst); @@ -316,7 +332,14 @@ @SuppressWarnings("deprecation") public String encodeToString(byte[] src) { byte[] encoded = encode(src); - return new String(encoded, 0, 0, encoded.length); + try { + return jdk.internal.access.SharedSecrets.getJavaLangAccess() + .newStringNoRepl(encoded, StandardCharsets.ISO_8859_1); + } catch(CharacterCodingException ex) { + // This exception never occurs, as there are + // no chances of encoded array being malformed + throw new RuntimeException("malformed encoded array", ex); + } } /** @@ -334,7 +357,7 @@ * @return A newly-allocated byte buffer containing the encoded bytes. */ public ByteBuffer encode(ByteBuffer buffer) { - int len = outLength(buffer.remaining()); + int len = outLength(buffer.remaining(), false); byte[] dst = new byte[len]; int ret = 0; if (buffer.hasArray()) { @@ -469,6 +492,10 @@ * a method of this class will cause a * {@link java.lang.NullPointerException NullPointerException} to * be thrown. + *

If the decoded byte output of the needed size can not + * be allocated, the decode methods of this class will + * cause an {@link java.lang.OutOfMemoryError OutOfMemoryError} + * to be thrown. * * @see Encoder * @since 1.8 @@ -531,7 +558,7 @@ * if {@code src} is not in valid Base64 scheme */ public byte[] decode(byte[] src) { - byte[] dst = new byte[outLength(src, 0, src.length)]; + byte[] dst = new byte[outLength(src, 0, src.length, false)]; int ret = decode0(src, 0, src.length, dst); if (ret != dst.length) { dst = Arrays.copyOf(dst, ret); @@ -584,8 +611,8 @@ * does not have enough space for decoding all input bytes. */ public int decode(byte[] src, byte[] dst) { - int len = outLength(src, 0, src.length); - if (dst.length < len) + int len = outLength(src, 0, src.length, true); + if (dst.length < len || len == -1) throw new IllegalArgumentException( "Output byte array is too small for decoding all input bytes"); return decode0(src, 0, src.length, dst); @@ -610,7 +637,7 @@ * @return A newly-allocated byte buffer containing the decoded bytes * * @throws IllegalArgumentException - * if {@code src} is not in valid Base64 scheme. + * if {@code buffer} is not in valid Base64 scheme */ public ByteBuffer decode(ByteBuffer buffer) { int pos0 = buffer.position(); @@ -628,7 +655,7 @@ sp = 0; sl = src.length; } - byte[] dst = new byte[outLength(src, sp, sl)]; + byte[] dst = new byte[outLength(src, sp, sl, false)]; return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst)); } catch (IllegalArgumentException iae) { buffer.position(pos0); @@ -656,7 +683,7 @@ return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME); } - private int outLength(byte[] src, int sp, int sl) { + private int outLength(byte[] src, int sp, int sl, boolean withOutputParam) { int[] base64 = isURL ? fromBase64URL : fromBase64; int paddings = 0; int len = sl - sp; @@ -691,7 +718,19 @@ } if (paddings == 0 && (len & 0x3) != 0) paddings = 4 - (len & 0x3); - return 3 * ((len + 3) / 4) - paddings; + + try { + len = Math.multiplyExact(3, (Math.addExact(len, 3) / 4)) - paddings; + } catch (ArithmeticException ex) { + if (!withOutputParam) { + throw new OutOfMemoryError("Decoded size is too large"); + } else { + // let the caller know that the decoded bytes can not + // fit in the passed output array param + len = -1; + } + } + return len; } private int decode0(byte[] src, int sp, int sl, byte[] dst) { --- /dev/null 2019-01-06 22:07:53.048000000 +0530 +++ new/test/jdk/java/util/Base64/TestEncodingDecodingLength.java 2019-01-17 12:14:24.431000000 +0530 @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.nio.ByteBuffer; +import java.util.Base64; + +/** + * @test + * @bug 8210583 + * @summary Tests Base64.Encoder.encode/Decoder.decode for the large size + * of resulting bytes which can not be allocated + * @requires os.maxMemory >= 6g + * @run main/othervm -Xms4g -Xmx6g TestEncodingDecodingLength + * + */ + +public class TestEncodingDecodingLength { + + public static void main(String[] args) { + int size = Integer.MAX_VALUE - 2; + byte[] bytes = new byte[size]; + + // Check encoder with max array length + Base64.Encoder encoder = Base64.getEncoder(); + checkOOM("encode(byte[])", () -> encoder.encode(bytes)); + // A separate output array is not required, as it is not used and + // the exception is thrown beforehand while calculating the size + // of the encoded bytes using input array + checkIAE("encode(byte[] byte[])", () -> encoder.encode(bytes, bytes)); + checkOOM("encodeToString(byte[])", () -> encoder.encodeToString(bytes)); + checkOOM("encode(ByteBuffer)", () -> encoder.encode(ByteBuffer.allocate(size))); + + // Check decoder with max array length + Base64.Decoder decoder = Base64.getDecoder(); + checkOOM("decode(byte[])", () -> decoder.decode(bytes)); + // A separate output array is not required, as it is not used and + // the exception is thrown beforehand while calculating the size + // of the decoded bytes using input array + checkIAE("decode(byte[], byte[])", () -> decoder.decode(bytes, bytes)); + checkOOM("decode(ByteBuffer)", () -> decoder.decode(ByteBuffer.allocate(size))); + } + + private static final void checkOOM(String methodName, Runnable r) { + try { + r.run(); + throw new RuntimeException("As expected, OOM is not thrown by" + methodName); + } catch (OutOfMemoryError er) {} + } + + private static final void checkIAE(String methodName, Runnable r) { + try { + r.run(); + throw new RuntimeException("As expected, IAE is not thrown by" + methodName); + } catch (IllegalArgumentException iae) {} + } +} +