diff --git a/src/java.base/share/classes/java/security/MessageDigestSpi.java b/src/java.base/share/classes/java/security/MessageDigestSpi.java index 17bd34e507c2c42b1b8ce166b307f3c3c8321357..c99abf79ecdbdedc475318ea39ed519bfc6a910d 100644 --- a/src/java.base/share/classes/java/security/MessageDigestSpi.java +++ b/src/java.base/share/classes/java/security/MessageDigestSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2023, 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 @@ -204,7 +204,15 @@ public abstract class MessageDigestSpi { */ public Object clone() throws CloneNotSupportedException { if (this instanceof Cloneable) { - return super.clone(); + MessageDigestSpi o = (MessageDigestSpi)super.clone(); + if (o.tempArray != null) { + // New byte arrays are allocated when the ByteBuffer argument + // to engineUpdate is not backed by a byte array. + // Here, the newly allocated byte array must also be cloned + // to prevent threads from sharing the same memory. + o.tempArray = tempArray.clone(); + } + return o; } else { throw new CloneNotSupportedException(); } diff --git a/test/jdk/java/security/MessageDigest/TestCloneable.java b/test/jdk/java/security/MessageDigest/TestCloneable.java index 915f0c0996e4948c4ae8f20bc28b08e48dc10b55..3a4feb82ff6da68ccf7f77bf4838f60bc170f83c 100644 --- a/test/jdk/java/security/MessageDigest/TestCloneable.java +++ b/test/jdk/java/security/MessageDigest/TestCloneable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2023, 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 @@ -23,12 +23,16 @@ /* * @test - * @bug 8246077 + * @bug 8246077 8300416 * @summary Make sure that digest spi and the resulting digest impl are - * consistent in the impl of Cloneable interface + * consistent in the impl of Cloneable interface, and that clones do not + * share memory. * @run testng TestCloneable */ +import java.nio.ByteBuffer; import java.security.*; +import java.util.Arrays; +import java.util.Random; import java.util.Objects; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -53,7 +57,7 @@ public class TestCloneable { @Test(dataProvider = "testData") public void test(String algo, String provName) throws NoSuchProviderException, NoSuchAlgorithmException, - CloneNotSupportedException { + CloneNotSupportedException, InterruptedException { System.out.print("Testing " + algo + " impl from " + provName); Provider p = Security.getProvider(provName); Provider.Service s = p.getService("MessageDigest", algo); @@ -71,6 +75,52 @@ public class TestCloneable { System.out.println(": NOT Cloneable"); Assert.assertThrows(CNSE, ()->md.clone()); } + + System.out.print("Testing " + algo + " impl from " + provName); + final var d1 = MessageDigest.getInstance(algo, provName); + final var buffer = ByteBuffer.allocateDirect(1024); + final var r = new Random(1024); + + fillBuffer(r, buffer); + d1.update(buffer); // this statement triggers tempArray allocation + final var d2 = (MessageDigest) d1.clone(); + assert Arrays.equals(d1.digest(), d2.digest()); + + final var t1 = updateThread(d1); + final var t2 = updateThread(d2); + t1.join(); + t2.join(); + + System.out.println(": Shared data check"); + // Random is producing the same sequence of bytes for each thread, + // and thus each MessageDigest should be equal. When the memory is + // shared, they inevitably overwrite each other's tempArray and + // you get different results. + if (!Arrays.equals(d1.digest(), d2.digest())) { + throw new AssertionError("digests differ"); + } + System.out.println("Test Passed"); } + + private static void fillBuffer(final Random r, final ByteBuffer buffer) { + final byte[] bytes = new byte[buffer.capacity()]; + r.nextBytes(bytes); + buffer.clear(); + buffer.put(bytes); + buffer.flip(); + } + + public static Thread updateThread(final MessageDigest d) { + final var t = new Thread(() -> { + final var r = new Random(1024); + final ByteBuffer buffer = ByteBuffer.allocateDirect(1024); + for (int i = 0; i < 1024; i++) { + fillBuffer(r, buffer); + d.update(buffer); + } + }); + t.start(); + return t; + } }