Skip to content

Commit 466e89a

Browse files
authored
Merge pull request #111 from alon-sh/rng_mix
Change CRIU RNG to be non-blocking, mixing in SHA1PRNG data
2 parents d7f7dff + 924d6d8 commit 466e89a

File tree

3 files changed

+279
-43
lines changed

3 files changed

+279
-43
lines changed

closed/src/java.base/share/classes/openj9/internal/criu/CRIUSECProvider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ public CRIUSECProvider() {
5757
/**
5858
* Resets the security digests.
5959
*/
60-
public static void resetDigests() {
60+
public static void resetCRIUSEC() {
61+
NativePRNG.clearRNGState();
6162
DigestBase.resetDigests();
6263
}
6364
}

closed/src/java.base/share/classes/openj9/internal/criu/NativePRNG.java

Lines changed: 77 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ public final class NativePRNG extends SecureRandomSpi {
7373
// name of the pure random file (also used for setSeed())
7474
private static final String NAME_RANDOM = "/dev/random";
7575

76+
// name of the pseudo random file
77+
private static final String NAME_URANDOM = "/dev/urandom";
78+
7679
// singleton instance or null if not available
7780
private static final RandomIO INSTANCE = initIO();
7881

@@ -85,7 +88,7 @@ private static RandomIO initIO() {
8588
@Override
8689
public RandomIO run() {
8790
File seedFile = new File(NAME_RANDOM);
88-
File nextFile = new File(NAME_RANDOM);
91+
File nextFile = new File(NAME_URANDOM);
8992

9093
if (debug != null) {
9194
debug.println("NativePRNG." +
@@ -132,9 +135,7 @@ protected void engineSetSeed(byte[] seed) {
132135
// get pseudo random bytes
133136
@Override
134137
protected void engineNextBytes(byte[] bytes) {
135-
int len = bytes.length;
136-
byte[] b = INSTANCE.implGenerateSeed(len);
137-
System.arraycopy(b, 0, bytes, 0, len);
138+
INSTANCE.implNextBytes(bytes);
138139
}
139140

140141
// get true random bytes
@@ -143,12 +144,16 @@ protected byte[] engineGenerateSeed(int numBytes) {
143144
return INSTANCE.implGenerateSeed(numBytes);
144145
}
145146

147+
static void clearRNGState() {
148+
INSTANCE.clearRNGState();
149+
}
150+
146151
/**
147152
* Nested class doing the actual work. Singleton, see INSTANCE above.
148153
*/
149154
private static final class RandomIO {
150155

151-
// Holder for the seedFile. Used if we ever add seed material.
156+
// holder for the seedFile, used if we ever add seed material
152157
private File seedFile;
153158

154159
// In/OutputStream for "seed" and "next".
@@ -158,11 +163,12 @@ private static final class RandomIO {
158163
// flag indicating if we have tried to open seedOut yet
159164
private boolean seedOutInitialized;
160165

161-
// mutex lock for generateSeed()
162-
private final Object LOCK_GET_SEED = new Object();
166+
// SHA1PRNG instance for mixing
167+
// initialized lazily on demand to avoid problems during startup
168+
private volatile SHA1PRNG mixRandom;
163169

164-
// mutex lock for setSeed()
165-
private final Object LOCK_SET_SEED = new Object();
170+
// mutex lock for accessing the seed file stream
171+
private final Object LOCK_GET_SEED = new Object();
166172

167173
// constructor, called only once from initIO()
168174
private RandomIO(File seedFile, File nextFile) throws IOException {
@@ -183,22 +189,32 @@ private RandomIO(File seedFile, File nextFile) throws IOException {
183189
this.nextIn = nextStream;
184190
}
185191

192+
// get the SHA1PRNG for mixing
193+
// initialize if not yet created
194+
private SHA1PRNG getMixRandom() {
195+
SHA1PRNG prngObj = mixRandom;
196+
if (prngObj == null) {
197+
synchronized (LOCK_GET_SEED) {
198+
prngObj = mixRandom;
199+
if (prngObj == null) {
200+
try {
201+
prngObj = SHA1PRNG.seedFrom(seedIn);
202+
} catch (IOException e) {
203+
throw new ProviderException("init failed", e);
204+
}
205+
mixRandom = prngObj;
206+
}
207+
}
208+
}
209+
return prngObj;
210+
}
186211
// Read data.length bytes from in.
187212
// These are not normal files, so we need to loop the read.
188213
// Just keep trying as long as we are making progress.
189214
private static void readFully(InputStream in, byte[] data)
190215
throws IOException {
191216
int len = data.length;
192-
int ofs = 0;
193-
while (len > 0) {
194-
int k = in.read(data, ofs, len);
195-
if (k <= 0) {
196-
throw new EOFException("File(s) closed?");
197-
}
198-
ofs += k;
199-
len -= k;
200-
}
201-
if (len > 0) {
217+
if (in.readNBytes(data, 0, len) < len) {
202218
throw new IOException("Could not read from file(s)");
203219
}
204220
}
@@ -219,30 +235,49 @@ private byte[] implGenerateSeed(int numBytes) {
219235
// supply random bytes to the OS
220236
// write to "seed" if possible
221237
// always add the seed to our mixing random
222-
private void implSetSeed(byte[] seed) {
223-
synchronized (LOCK_SET_SEED) {
224-
if (seedOutInitialized == false) {
225-
seedOutInitialized = true;
226-
seedOut = AccessController.doPrivileged(
227-
new PrivilegedAction<>() {
228-
@Override
229-
public OutputStream run() {
230-
try {
231-
return new FileOutputStream(seedFile, true);
232-
} catch (Exception e) {
233-
return null;
234-
}
235-
}
236-
});
237-
}
238-
if (seedOut != null) {
239-
try {
240-
seedOut.write(seed);
241-
} catch (IOException e) {
242-
// Ignored. On Mac OS X, /dev/urandom can be opened
243-
// for write, but actual write is not permitted.
244-
}
238+
private synchronized void implSetSeed(byte[] seed) {
239+
if (seedOutInitialized == false) {
240+
seedOutInitialized = true;
241+
seedOut = AccessController.doPrivileged(
242+
new PrivilegedAction<>() {
243+
@Override
244+
public OutputStream run() {
245+
try {
246+
return new FileOutputStream(seedFile, true);
247+
} catch (IOException e) {
248+
return null;
249+
}
250+
}
251+
});
252+
}
253+
if (seedOut != null) {
254+
try {
255+
seedOut.write(seed);
256+
} catch (IOException e) {
257+
// ignored, on Mac OS X, /dev/urandom can be opened
258+
// for write, but actual write is not permitted
259+
}
260+
}
261+
getMixRandom().engineSetSeed(seed);
262+
}
263+
264+
private synchronized void implNextBytes(byte[] data) {
265+
getMixRandom().engineNextBytes(data);
266+
try {
267+
// read random data from non blocking source
268+
byte[] rawData = new byte[data.length];
269+
readFully(nextIn, rawData);
270+
for (int i = 0; i < data.length; i++) {
271+
data[i] ^= rawData[i];
245272
}
273+
} catch (IOException e) {
274+
throw new ProviderException("nextBytes() failed", e);
275+
}
276+
}
277+
278+
private void clearRNGState() {
279+
if (mixRandom != null) {
280+
mixRandom.clearState();
246281
}
247282
}
248283
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/*[INCLUDE-IF CRIU_SUPPORT]*/
2+
/*
3+
* Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.
4+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5+
*
6+
* This code is free software; you can redistribute it and/or modify it
7+
* under the terms of the GNU General Public License version 2 only, as
8+
* published by the Free Software Foundation. Oracle designates this
9+
* particular file as subject to the "Classpath" exception as provided
10+
* by Oracle in the LICENSE file that accompanied this code.
11+
*
12+
* This code is distributed in the hope that it will be useful, but WITHOUT
13+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15+
* version 2 for more details (a copy is included in the LICENSE file that
16+
* accompanied this code).
17+
*
18+
* You should have received a copy of the GNU General Public License version
19+
* 2 along with this work; if not, write to the Free Software Foundation,
20+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21+
*
22+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23+
* or visit www.oracle.com if you need additional information or have any
24+
* questions.
25+
*/
26+
27+
/*
28+
* ===========================================================================
29+
* (c) Copyright IBM Corp. 2022, 2022 All Rights Reserved
30+
* ===========================================================================
31+
*/
32+
33+
package openj9.internal.criu;
34+
35+
import java.io.InputStream;
36+
import java.io.IOException;
37+
import java.security.MessageDigest;
38+
import java.security.NoSuchAlgorithmException;
39+
import java.security.NoSuchProviderException;
40+
import java.util.Arrays;
41+
42+
/**
43+
* <p>This class provides a crytpographically strong pseudo-random number
44+
* generator based on the SHA-1 hash algorithm.
45+
*
46+
* <p>Seed must be provided externally.
47+
*
48+
* <p>Also note that when a random object is deserialized,
49+
* <a href="#engineNextBytes(byte[])">engineNextBytes</a> invoked on the
50+
* restored random object will yield the exact same (random) bytes as the
51+
* original object. If this behaviour is not desired, the restored random
52+
* object should be seeded, using
53+
* <a href="#engineSetSeed(byte[])">engineSetSeed</a>.
54+
*
55+
* @author Benjamin Renaud
56+
* @author Josh Bloch
57+
* @author Gadi Guy
58+
*/
59+
60+
public final class SHA1PRNG implements java.io.Serializable {
61+
62+
private static final long serialVersionUID = 3581829991155417889L;
63+
64+
// SHA-1 Digest yields 160-bit hashes which require 20 bytes of space.
65+
private static final int DIGEST_SIZE = 20;
66+
private transient MessageDigest digest;
67+
private byte[] state;
68+
private byte[] remainder;
69+
private int remCount;
70+
71+
// This class is a modified version of the SHA1PRNG SecureRandom implementation
72+
// that is found at sun.security.provider.SecureRandom.
73+
// It was modified to be used by CRIUSEC NativePRNG as a mixing data source.
74+
// Auto-seeding was removed, it is always seeded by NativePRNG from a
75+
// blocking entropy source.
76+
77+
private SHA1PRNG(byte[] seed) {
78+
init(seed);
79+
}
80+
81+
static SHA1PRNG seedFrom(InputStream in) throws IOException {
82+
byte[] seed = new byte[DIGEST_SIZE];
83+
if (in.readNBytes(seed, 0, DIGEST_SIZE) != DIGEST_SIZE) {
84+
throw new IOException("Could not read seed");
85+
}
86+
return new SHA1PRNG(seed);
87+
}
88+
89+
/**
90+
* This call, used by the constructor, instantiates the SHA digest
91+
* and sets the seed.
92+
*/
93+
private void init(byte[] seed) {
94+
if (seed == null) {
95+
throw new InternalError("internal error: no seed available.");
96+
}
97+
98+
try {
99+
digest = MessageDigest.getInstance("SHA-1", "CRIUSEC");
100+
} catch (NoSuchProviderException | NoSuchAlgorithmException e) {
101+
throw new InternalError("internal error: SHA-1 not available.", e);
102+
}
103+
104+
engineSetSeed(seed);
105+
}
106+
107+
108+
/**
109+
* Reseeds this random object. The given seed supplements, rather than
110+
* replaces, the existing seed. Thus, repeated calls are guaranteed
111+
* never to reduce randomness.
112+
*
113+
* @param seed the seed.
114+
*/
115+
public synchronized void engineSetSeed(byte[] seed) {
116+
if (state != null) {
117+
digest.update(state);
118+
for (int i = 0; i < state.length; i++) {
119+
state[i] = 0;
120+
}
121+
}
122+
state = digest.digest(seed);
123+
remCount = 0;
124+
}
125+
126+
private static void updateState(byte[] state, byte[] output) {
127+
int carry = 1;
128+
boolean collision = true;
129+
130+
// state(n + 1) = (state(n) + output(n) + 1) % 2^160;
131+
for (int i = 0; i < state.length; i++) {
132+
// Add two bytes.
133+
int stateCalc = (state[i] & 0xFF) + (output[i] & 0xFF) + carry;
134+
// Result is lower 8 bits.
135+
byte newState = (byte)stateCalc;
136+
// Store result. Check for state collision.
137+
collision &= (state[i] == newState);
138+
state[i] = newState;
139+
// High 8 bits are carry. Store for next iteration.
140+
carry = stateCalc >>> 8;
141+
}
142+
143+
// Make sure at least one bit changes.
144+
if (collision) {
145+
state[0]++;
146+
}
147+
}
148+
149+
150+
/**
151+
* Generates a user-specified number of random bytes.
152+
*
153+
* @param result the array to be filled in with random bytes.
154+
*/
155+
public synchronized void engineNextBytes(byte[] result) {
156+
int index = 0;
157+
byte[] output = remainder;
158+
159+
// Use remainder from last time.
160+
int r = remCount;
161+
if (r > 0) {
162+
// Compute how many bytes to be copied.
163+
int todo = Math.min(result.length - index, DIGEST_SIZE - r);
164+
// Copy the bytes, zero the buffer.
165+
for (int i = 0; i < todo; i++) {
166+
result[i] = output[r];
167+
output[r++] = 0;
168+
}
169+
remCount += todo;
170+
index += todo;
171+
}
172+
173+
// If we need more bytes, make them.
174+
while (index < result.length) {
175+
// Step the state.
176+
digest.update(state);
177+
output = digest.digest();
178+
updateState(state, output);
179+
180+
// Compute how many bytes to be copied.
181+
int todo = Math.min(result.length - index, DIGEST_SIZE);
182+
// Copy the bytes, zero the buffer.
183+
for (int i = 0; i < todo; i++) {
184+
result[index++] = output[i];
185+
output[i] = 0;
186+
}
187+
remCount += todo;
188+
}
189+
190+
// Store remainder for next time.
191+
remainder = output;
192+
remCount %= DIGEST_SIZE;
193+
}
194+
195+
void clearState() {
196+
Arrays.fill(state, (byte) 0x00);
197+
Arrays.fill(remainder, (byte) 0x00);
198+
remCount = 0;
199+
}
200+
}

0 commit comments

Comments
 (0)