1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server; 18 19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.os.Binder; 24 import android.os.Environment; 25 import android.os.Handler; 26 import android.os.Message; 27 import android.os.SystemProperties; 28 import android.util.AtomicFile; 29 import android.util.Slog; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.internal.util.Preconditions; 33 34 import java.io.File; 35 import java.io.FileInputStream; 36 import java.io.FileNotFoundException; 37 import java.io.FileOutputStream; 38 import java.io.IOException; 39 import java.nio.ByteBuffer; 40 import java.security.MessageDigest; 41 import java.security.NoSuchAlgorithmException; 42 43 /** 44 * A service that loads and periodically saves "randomness" for the 45 * Linux kernel RNG. 46 * 47 * <p>When a Linux system starts up, the entropy pool associated with {@code 48 * /dev/urandom}, {@code /dev/random}, and {@code getrandom()} may be in a 49 * fairly predictable state, depending on the entropy sources available to the 50 * kernel. Applications that depend on randomness may find these APIs returning 51 * predictable data. To counteract this effect, this service maintains a seed 52 * file across shutdowns and startups, and also mixes some device and 53 * boot-specific information into the pool. 54 */ 55 public class EntropyMixer extends Binder { 56 private static final String TAG = "EntropyMixer"; 57 private static final int UPDATE_SEED_MSG = 1; 58 private static final int SEED_UPDATE_PERIOD = 3 * 60 * 60 * 1000; // 3 hrs 59 private static final long START_TIME = System.currentTimeMillis(); 60 private static final long START_NANOTIME = System.nanoTime(); 61 62 /* 63 * The size of the seed file in bytes. This must be at least the size of a 64 * SHA-256 digest (32 bytes). It *should* also be at least the size of the 65 * kernel's entropy pool (/proc/sys/kernel/random/poolsize divided by 8), 66 * which historically was 512 bytes, but changed to 32 bytes in Linux v5.18. 67 * There's actually no real need for more than a 32-byte seed, even with 68 * older kernels; however, we take the conservative approach of staying with 69 * the 512-byte size for now, as the cost is very small. 70 */ 71 @VisibleForTesting 72 static final int SEED_FILE_SIZE = 512; 73 74 @VisibleForTesting 75 static final String DEVICE_SPECIFIC_INFO_HEADER = 76 "Copyright (C) 2009 The Android Open Source Project\n" + 77 "All Your Randomness Are Belong To Us\n"; 78 79 private final AtomicFile seedFile; 80 private final File randomReadDevice; 81 private final File randomWriteDevice; // separate from randomReadDevice only for testing 82 83 /** 84 * Handler that periodically updates the seed file. 85 */ 86 private final Handler mHandler = new Handler(IoThread.getHandler().getLooper()) { 87 // IMPLEMENTATION NOTE: This handler runs on the I/O thread to avoid I/O on the main thread. 88 // The reason we're using our own Handler instead of IoThread.getHandler() is to create our 89 // own ID space for the "what" parameter of messages seen by the handler. 90 @Override 91 public void handleMessage(Message msg) { 92 if (msg.what != UPDATE_SEED_MSG) { 93 Slog.e(TAG, "Will not process invalid message"); 94 return; 95 } 96 updateSeedFile(); 97 scheduleSeedUpdater(); 98 } 99 }; 100 101 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 102 @Override 103 public void onReceive(Context context, Intent intent) { 104 updateSeedFile(); 105 } 106 }; 107 EntropyMixer(Context context)108 public EntropyMixer(Context context) { 109 this(context, new File(getSystemDir(), "entropy.dat"), 110 new File("/dev/urandom"), new File("/dev/urandom")); 111 } 112 113 @VisibleForTesting EntropyMixer(Context context, File seedFile, File randomReadDevice, File randomWriteDevice)114 EntropyMixer(Context context, File seedFile, File randomReadDevice, File randomWriteDevice) { 115 this.seedFile = new AtomicFile(Preconditions.checkNotNull(seedFile)); 116 this.randomReadDevice = Preconditions.checkNotNull(randomReadDevice); 117 this.randomWriteDevice = Preconditions.checkNotNull(randomWriteDevice); 118 119 loadInitialEntropy(); 120 updateSeedFile(); 121 scheduleSeedUpdater(); 122 IntentFilter broadcastFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); 123 broadcastFilter.addAction(Intent.ACTION_POWER_CONNECTED); 124 broadcastFilter.addAction(Intent.ACTION_REBOOT); 125 context.registerReceiver( 126 mBroadcastReceiver, 127 broadcastFilter, 128 null, // do not require broadcaster to hold any permissions 129 mHandler // process received broadcasts on the I/O thread instead of the main thread 130 ); 131 } 132 scheduleSeedUpdater()133 private void scheduleSeedUpdater() { 134 mHandler.removeMessages(UPDATE_SEED_MSG); 135 mHandler.sendEmptyMessageDelayed(UPDATE_SEED_MSG, SEED_UPDATE_PERIOD); 136 } 137 loadInitialEntropy()138 private void loadInitialEntropy() { 139 byte[] seed = readSeedFile(); 140 try (FileOutputStream out = new FileOutputStream(randomWriteDevice)) { 141 if (seed.length != 0) { 142 out.write(seed); 143 Slog.i(TAG, "Loaded existing seed file"); 144 } 145 out.write(getDeviceSpecificInformation()); 146 } catch (IOException e) { 147 Slog.e(TAG, "Error writing to " + randomWriteDevice, e); 148 } 149 } 150 readSeedFile()151 private byte[] readSeedFile() { 152 try { 153 return seedFile.readFully(); 154 } catch (FileNotFoundException e) { 155 return new byte[0]; 156 } catch (IOException e) { 157 Slog.e(TAG, "Error reading " + seedFile.getBaseFile(), e); 158 return new byte[0]; 159 } 160 } 161 162 /** 163 * Update (or create) the seed file. 164 * 165 * <p>Traditionally, the recommended way to update a seed file on Linux was 166 * to simply copy some bytes from /dev/urandom. However, that isn't 167 * actually a good way to do it, because writes to /dev/urandom aren't 168 * guaranteed to immediately affect reads from /dev/urandom. This can cause 169 * the new seed file to contain less entropy than the old one! 170 * 171 * <p>Instead, we generate the new seed by hashing the old seed together 172 * with some bytes from /dev/urandom, following the example of <a 173 * href="https://git.zx2c4.com/seedrng/tree/README.md">SeedRNG</a>. This 174 * ensures that the new seed is at least as entropic as the old seed. 175 */ updateSeedFile()176 private void updateSeedFile() { 177 byte[] oldSeed = readSeedFile(); 178 byte[] newSeed = new byte[SEED_FILE_SIZE]; 179 180 try (FileInputStream in = new FileInputStream(randomReadDevice)) { 181 if (in.read(newSeed) != newSeed.length) { 182 throw new IOException("unexpected EOF"); 183 } 184 } catch (IOException e) { 185 Slog.e(TAG, "Error reading " + randomReadDevice + 186 "; seed file won't be properly updated", e); 187 // Continue on; at least we'll have new timestamps... 188 } 189 190 // newSeed = newSeed[:-32] || 191 // SHA-256(fixed_prefix || real_time || boot_time || 192 // old_seed_len || old_seed || new_seed_len || new_seed) 193 MessageDigest sha256; 194 try { 195 sha256 = MessageDigest.getInstance("SHA-256"); 196 } catch (NoSuchAlgorithmException e) { 197 Slog.wtf(TAG, "SHA-256 algorithm not found; seed file won't be updated", e); 198 return; 199 } 200 // This fixed prefix should be changed if the fields that are hashed change. 201 sha256.update("Android EntropyMixer v1".getBytes()); 202 sha256.update(longToBytes(System.currentTimeMillis())); 203 sha256.update(longToBytes(System.nanoTime())); 204 sha256.update(longToBytes(oldSeed.length)); 205 sha256.update(oldSeed); 206 sha256.update(longToBytes(newSeed.length)); 207 sha256.update(newSeed); 208 byte[] digest = sha256.digest(); 209 System.arraycopy(digest, 0, newSeed, newSeed.length - digest.length, digest.length); 210 211 writeNewSeed(newSeed); 212 if (oldSeed.length == 0) { 213 Slog.i(TAG, "Created seed file"); 214 } else { 215 Slog.i(TAG, "Updated seed file"); 216 } 217 } 218 writeNewSeed(byte[] newSeed)219 private void writeNewSeed(byte[] newSeed) { 220 FileOutputStream out = null; 221 try { 222 out = seedFile.startWrite(); 223 out.write(newSeed); 224 seedFile.finishWrite(out); 225 } catch (IOException e) { 226 Slog.e(TAG, "Error writing " + seedFile.getBaseFile(), e); 227 seedFile.failWrite(out); 228 } 229 } 230 longToBytes(long x)231 private static byte[] longToBytes(long x) { 232 ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); 233 buffer.putLong(x); 234 return buffer.array(); 235 } 236 237 /** 238 * Get some device and boot-specific information to mix into the kernel's 239 * entropy pool. This information probably won't contain much actual 240 * entropy, but that's fine because we don't ask the kernel to credit it. 241 * Writes to {@code /dev/urandom} can only increase or have no effect on the 242 * quality of random numbers, never decrease it. 243 * 244 * <p>The main goal here is just to initialize the entropy pool differently 245 * on devices that might otherwise be identical and have very little other 246 * entropy available. Therefore, we include various system properties that 247 * can vary on a per-device and/or per-build basis. We also include some 248 * timestamps, as these might vary on a per-boot basis and be not easily 249 * observable or guessable by an attacker. 250 */ getDeviceSpecificInformation()251 private byte[] getDeviceSpecificInformation() { 252 StringBuilder b = new StringBuilder(); 253 b.append(DEVICE_SPECIFIC_INFO_HEADER); 254 b.append(START_TIME).append('\n'); 255 b.append(START_NANOTIME).append('\n'); 256 b.append(SystemProperties.get("ro.serialno")).append('\n'); 257 b.append(SystemProperties.get("ro.bootmode")).append('\n'); 258 b.append(SystemProperties.get("ro.baseband")).append('\n'); 259 b.append(SystemProperties.get("ro.carrier")).append('\n'); 260 b.append(SystemProperties.get("ro.bootloader")).append('\n'); 261 b.append(SystemProperties.get("ro.hardware")).append('\n'); 262 b.append(SystemProperties.get("ro.revision")).append('\n'); 263 b.append(SystemProperties.get("ro.build.fingerprint")).append('\n'); 264 b.append(new Object().hashCode()).append('\n'); 265 b.append(System.currentTimeMillis()).append('\n'); 266 b.append(System.nanoTime()).append('\n'); 267 return b.toString().getBytes(); 268 } 269 getSystemDir()270 private static File getSystemDir() { 271 File dataDir = Environment.getDataDirectory(); 272 File systemDir = new File(dataDir, "system"); 273 systemDir.mkdirs(); 274 return systemDir; 275 } 276 } 277