1 /* 2 * Copyright (c) 2014, 2021, 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 /* 26 * @test 27 * @key randomness 28 * @summary SharedArchiveConsistency 29 * @requires vm.cds 30 * @library /test/lib 31 * @build sun.hotspot.WhiteBox 32 * @compile test-classes/Hello.java 33 * @run driver jdk.test.lib.helpers.ClassFileInstaller sun.hotspot.WhiteBox 34 * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI SharedArchiveConsistency 35 */ 36 import jdk.test.lib.process.OutputAnalyzer; 37 import jdk.test.lib.Utils; 38 import java.io.File; 39 import java.io.FileInputStream; 40 import java.io.FileOutputStream; 41 import java.io.IOException; 42 import java.nio.ByteBuffer; 43 import java.nio.ByteOrder; 44 import java.nio.channels.FileChannel; 45 import java.nio.file.Files; 46 import java.nio.file.Path; 47 import java.nio.file.Paths; 48 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; 49 import java.nio.file.StandardOpenOption; 50 import static java.nio.file.StandardOpenOption.READ; 51 import static java.nio.file.StandardOpenOption.WRITE; 52 import java.util.ArrayList; 53 import java.util.HashSet; 54 import java.util.List; 55 import java.util.Random; 56 import sun.hotspot.WhiteBox; 57 58 public class SharedArchiveConsistency { 59 public static WhiteBox wb; 60 public static int offset_magic; // CDSFileMapHeaderBase::_magic 61 public static int offset_version; // CDSFileMapHeaderBase::_version 62 public static int offset_jvm_ident; // FileMapHeader::_jvm_ident 63 public static int sp_offset_crc; // CDSFileMapRegion::_crc 64 public static int file_header_size = -1;// total size of header, variant, need calculation 65 public static int CDSFileMapRegion_size; // size of CDSFileMapRegion 66 public static int sp_offset; // offset of CDSFileMapRegion 67 public static int sp_used_offset; // offset of CDSFileMapRegion::_used 68 public static int size_t_size; // size of size_t 69 public static int int_size; // size of int 70 public static long alignment; // MetaspaceShared::core_region_alignment 71 72 // The following should be consistent with the enum in the C++ MetaspaceShared class 73 public static String[] shared_region_name = { 74 "rw", // ReadWrite 75 "ro", // ReadOnly 76 "bm", // relocation bitmaps 77 "first_closed_archive", 78 "last_closed_archive", 79 "first_open_archive", 80 "last_open_archive" 81 }; 82 83 public static int num_regions = shared_region_name.length; 84 public static String[] matchMessages = { 85 "Unable to use shared archive", 86 "An error has occurred while processing the shared archive file.", 87 "Checksum verification failed.", 88 "The shared archive file has been truncated." 89 }; 90 getFileOffsetInfo()91 public static void getFileOffsetInfo() throws Exception { 92 wb = WhiteBox.getWhiteBox(); 93 offset_magic = wb.getOffsetForName("FileMapHeader::_magic"); 94 offset_version = wb.getOffsetForName("FileMapHeader::_version"); 95 offset_jvm_ident = wb.getOffsetForName("FileMapHeader::_jvm_ident"); 96 sp_offset_crc = wb.getOffsetForName("CDSFileMapRegion::_crc"); 97 try { 98 int nonExistOffset = wb.getOffsetForName("FileMapHeader::_non_exist_offset"); 99 System.exit(-1); // should fail 100 } catch (Exception e) { 101 // success 102 } 103 104 sp_offset = wb.getOffsetForName("FileMapHeader::_space[0]") - offset_magic; 105 sp_used_offset = wb.getOffsetForName("CDSFileMapRegion::_used") - sp_offset_crc; 106 size_t_size = wb.getOffsetForName("size_t_size"); 107 CDSFileMapRegion_size = wb.getOffsetForName("CDSFileMapRegion_size"); 108 alignment = wb.metaspaceSharedRegionAlignment(); 109 } 110 getFileHeaderSize(FileChannel fc)111 public static int getFileHeaderSize(FileChannel fc) throws Exception { 112 if (file_header_size != -1) { 113 return file_header_size; 114 } 115 // this is not real header size, it is struct size 116 int_size = wb.getOffsetForName("int_size"); 117 file_header_size = wb.getOffsetForName("file_header_size"); 118 System.out.println("file_header_size = " + file_header_size); 119 file_header_size = (int)align_up_page(file_header_size); 120 System.out.println("file_header_size (aligned to page) = " + file_header_size); 121 return file_header_size; 122 } 123 align_up_page(long l)124 public static long align_up_page(long l) throws Exception { 125 // wb is obtained in getFileOffsetInfo() which is called first in main() else we should call 126 // WhiteBox.getWhiteBox() here first. 127 int pageSize = wb.getVMPageSize(); 128 return (l + pageSize -1) & (~ (pageSize - 1)); 129 } 130 getRandomBetween(long start, long end)131 private static long getRandomBetween(long start, long end) throws Exception { 132 if (start > end) { 133 throw new IllegalArgumentException("start must be less than end"); 134 } 135 Random aRandom = Utils.getRandomInstance(); 136 int d = aRandom.nextInt((int)(end - start)); 137 if (d < 1) { 138 d = 1; 139 } 140 return start + d; 141 } 142 readInt(FileChannel fc, long offset, int nbytes)143 public static long readInt(FileChannel fc, long offset, int nbytes) throws Exception { 144 ByteBuffer bb = ByteBuffer.allocate(nbytes); 145 bb.order(ByteOrder.nativeOrder()); 146 fc.position(offset); 147 fc.read(bb); 148 return (nbytes > 4 ? bb.getLong(0) : bb.getInt(0)); 149 } 150 writeData(FileChannel fc, long offset, ByteBuffer bb)151 public static void writeData(FileChannel fc, long offset, ByteBuffer bb) throws Exception { 152 fc.position(offset); 153 fc.write(bb); 154 } 155 getFileChannel(File jsaFile)156 public static FileChannel getFileChannel(File jsaFile) throws Exception { 157 List<StandardOpenOption> arry = new ArrayList<StandardOpenOption>(); 158 arry.add(READ); 159 arry.add(WRITE); 160 return FileChannel.open(jsaFile.toPath(), new HashSet<StandardOpenOption>(arry)); 161 } 162 modifyJsaContentRandomly(File jsaFile)163 public static void modifyJsaContentRandomly(File jsaFile) throws Exception { 164 FileChannel fc = getFileChannel(jsaFile); 165 // corrupt random area in the data areas 166 long[] used = new long[num_regions]; // record used bytes 167 long start0, start, end, off; 168 int used_offset, path_info_size; 169 170 int bufSize; 171 System.out.printf("%-24s%12s%12s%16s\n", "Space Name", "Used bytes", "Reg Start", "Random Offset"); 172 start0 = getFileHeaderSize(fc); 173 for (int i = 0; i < num_regions; i++) { 174 used[i] = get_region_used_size_aligned(fc, i); 175 start = start0; 176 for (int j = 0; j < i; j++) { 177 start += align_up_page(used[j]); 178 } 179 end = start + used[i]; 180 if (start == end) { 181 continue; // Ignore empty regions 182 } 183 off = getRandomBetween(start, end); 184 System.out.printf("%-24s%12d%12d%16d\n", shared_region_name[i], used[i], start, off); 185 if (end - off < 1024) { 186 bufSize = (int)(end - off + 1); 187 } else { 188 bufSize = 1024; 189 } 190 ByteBuffer bbuf = ByteBuffer.wrap(new byte[bufSize]); 191 writeData(fc, off, bbuf); 192 } 193 if (fc.isOpen()) { 194 fc.close(); 195 } 196 } 197 get_region_used_size_aligned(FileChannel fc, int region)198 static long get_region_used_size_aligned(FileChannel fc, int region) throws Exception { 199 long n = sp_offset + CDSFileMapRegion_size * region + sp_used_offset; 200 long used = readInt(fc, n, size_t_size); 201 used = (used + alignment - 1) & ~(alignment - 1); 202 return used; 203 } 204 modifyJsaContent(int region, File jsaFile)205 public static boolean modifyJsaContent(int region, File jsaFile) throws Exception { 206 FileChannel fc = getFileChannel(jsaFile); 207 byte[] buf = new byte[4096]; 208 ByteBuffer bbuf = ByteBuffer.wrap(buf); 209 210 long total = 0L; 211 long[] used = new long[num_regions]; 212 System.out.printf("%-24s%12s\n", "Space name", "Used bytes"); 213 for (int i = 0; i < num_regions; i++) { 214 used[i] = get_region_used_size_aligned(fc, i); 215 System.out.printf("%-24s%12d\n", shared_region_name[i], used[i]); 216 total += used[i]; 217 } 218 System.out.printf("%-24s%12d\n", "Total: ", total); 219 long header_size = getFileHeaderSize(fc); 220 long region_start_offset = header_size; 221 for (int i=0; i<region; i++) { 222 region_start_offset += used[i]; 223 } 224 if (used[region] == 0) { 225 System.out.println("Region " + shared_region_name[region] + " is empty. Nothing to corrupt."); 226 return false; 227 } 228 System.out.println("Corrupt " + shared_region_name[region] + " section, start = " + region_start_offset 229 + " (header_size + 0x" + Long.toHexString(region_start_offset-header_size) + ")"); 230 long bytes_written = 0L; 231 while (bytes_written < used[region]) { 232 writeData(fc, region_start_offset + bytes_written, bbuf); 233 bbuf.clear(); 234 bytes_written += 4096; 235 } 236 if (fc.isOpen()) { 237 fc.close(); 238 } 239 return true; 240 } 241 modifyJsaHeader(File jsaFile)242 public static void modifyJsaHeader(File jsaFile) throws Exception { 243 FileChannel fc = getFileChannel(jsaFile); 244 // screw up header info 245 byte[] buf = new byte[getFileHeaderSize(fc)]; 246 ByteBuffer bbuf = ByteBuffer.wrap(buf); 247 writeData(fc, 0L, bbuf); 248 if (fc.isOpen()) { 249 fc.close(); 250 } 251 } 252 modifyJvmIdent(File jsaFile)253 public static void modifyJvmIdent(File jsaFile) throws Exception { 254 FileChannel fc = getFileChannel(jsaFile); 255 int headerSize = getFileHeaderSize(fc); 256 System.out.println(" offset_jvm_ident " + offset_jvm_ident); 257 byte[] buf = new byte[256]; 258 ByteBuffer bbuf = ByteBuffer.wrap(buf); 259 writeData(fc, (long)offset_jvm_ident, bbuf); 260 if (fc.isOpen()) { 261 fc.close(); 262 } 263 } 264 modifyHeaderIntField(File jsaFile, long offset, int value)265 public static void modifyHeaderIntField(File jsaFile, long offset, int value) throws Exception { 266 FileChannel fc = getFileChannel(jsaFile); 267 int headerSize = getFileHeaderSize(fc); 268 System.out.println(" offset " + offset); 269 byte[] buf = ByteBuffer.allocate(4).putInt(value).array(); 270 ByteBuffer bbuf = ByteBuffer.wrap(buf); 271 writeData(fc, offset, bbuf); 272 if (fc.isOpen()) { 273 fc.close(); 274 } 275 } 276 277 static int testCount = 0; startNewTestArchive(String testName)278 public static String startNewTestArchive(String testName) { 279 ++ testCount; 280 String newArchiveName = TestCommon.getNewArchiveName(String.format("%02d", testCount) + "-" + testName); 281 TestCommon.setCurrentArchiveName(newArchiveName); 282 return newArchiveName; 283 } 284 copyFile(File orgJsaFile, String testName)285 public static File copyFile(File orgJsaFile, String testName) throws Exception { 286 File newJsaFile = new File(startNewTestArchive(testName)); 287 if (newJsaFile.exists()) { 288 if (!newJsaFile.delete()) { 289 throw new IOException("Could not delete file " + newJsaFile); 290 } 291 } 292 Files.copy(orgJsaFile.toPath(), newJsaFile.toPath(), REPLACE_EXISTING); 293 294 // orgJsaFile is read only, and Files.copy passes on this attribute to newJsaFile. 295 // Since we need to modify newJsaFile later, let's set it to r/w 296 setReadWritePermission(newJsaFile); 297 298 return newJsaFile; 299 } 300 301 // Copy file with bytes deleted or inserted 302 // del -- true, deleted, false, inserted insertOrDeleteBytes(File orgJsaFile, boolean del)303 public static File insertOrDeleteBytes(File orgJsaFile, boolean del) throws Exception { 304 File newJsaFile = new File(startNewTestArchive(del ? "delete-bytes" : "insert-bytes")); 305 try ( 306 FileChannel inputChannel = new FileInputStream(orgJsaFile).getChannel(); 307 FileChannel outputChannel = new FileOutputStream(newJsaFile).getChannel() 308 ) { 309 long size = inputChannel.size(); 310 int init_size = getFileHeaderSize(inputChannel); 311 outputChannel.transferFrom(inputChannel, 0, init_size); 312 int n = (int)getRandomBetween(0, 1024); 313 if (del) { 314 System.out.println("Delete " + n + " bytes at data start section"); 315 inputChannel.position(init_size + n); 316 outputChannel.transferFrom(inputChannel, init_size, size - init_size - n); 317 } else { 318 System.out.println("Insert " + n + " bytes at data start section"); 319 outputChannel.position(init_size); 320 outputChannel.write(ByteBuffer.wrap(new byte[n])); 321 outputChannel.transferFrom(inputChannel, init_size + n , size - init_size); 322 } 323 } 324 325 return newJsaFile; 326 } 327 setReadWritePermission(File file)328 public static void setReadWritePermission(File file) throws Exception { 329 if (!file.canRead()) { 330 if (!file.setReadable(true)) { 331 throw new IOException("Cannot modify file " + file + " as readable"); 332 } 333 } 334 if (!file.canWrite()) { 335 if (!file.setWritable(true)) { 336 throw new IOException("Cannot modify file " + file + " as writable"); 337 } 338 } 339 } 340 testAndCheck(String[] execArgs)341 public static void testAndCheck(String[] execArgs) throws Exception { 342 OutputAnalyzer output = TestCommon.execCommon(execArgs); 343 String stdtxt = output.getOutput(); 344 System.out.println("Note: this test may fail in very rare occasions due to CRC32 checksum collision"); 345 for (String message : matchMessages) { 346 if (stdtxt.contains(message)) { 347 // match any to return 348 return; 349 } 350 } 351 TestCommon.checkExec(output); 352 } 353 354 // dump with hello.jsa, then 355 // read the jsa file 356 // 1) run normal 357 // 2) modify header 358 // 3) keep header correct but modify content in each region specified by shared_region_name[] 359 // 4) update both header and content, test 360 // 5) delete bytes in data begining 361 // 6) insert bytes in data begining 362 // 7) randomly corrupt data in each region specified by shared_region_name[] main(String... args)363 public static void main(String... args) throws Exception { 364 // must call to get offset info first!!! 365 getFileOffsetInfo(); 366 Path currentRelativePath = Paths.get(""); 367 String currentDir = currentRelativePath.toAbsolutePath().toString(); 368 System.out.println("Current relative path is: " + currentDir); 369 // get jar file 370 String jarFile = JarBuilder.getOrCreateHelloJar(); 371 372 // dump (appcds.jsa created) 373 TestCommon.testDump(jarFile, null); 374 375 // test, should pass 376 System.out.println("1. Normal, should pass but may fail\n"); 377 378 String[] execArgs = {"-Xlog:cds=debug", "-cp", jarFile, "Hello"}; 379 // tests that corrupt contents of the archive need to run with 380 // VerifySharedSpaces enabled to detect inconsistencies 381 String[] verifyExecArgs = {"-Xlog:cds", "-XX:+VerifySharedSpaces", "-cp", jarFile, "Hello"}; 382 383 OutputAnalyzer output = TestCommon.execCommon(execArgs); 384 385 try { 386 TestCommon.checkExecReturn(output, 0, true, "Hello World"); 387 } catch (Exception e) { 388 TestCommon.checkExecReturn(output, 1, true, matchMessages[0]); 389 } 390 391 // get the archive that has just been created. 392 File orgJsaFile = new File(TestCommon.getCurrentArchiveName()); 393 if (!orgJsaFile.exists()) { 394 throw new IOException(orgJsaFile + " does not exist!"); 395 } 396 397 // modify jsa header, test should fail 398 System.out.println("\n2. Corrupt header, should fail\n"); 399 modifyJsaHeader(copyFile(orgJsaFile, "corrupt-header")); 400 output = TestCommon.execCommon(execArgs); 401 output.shouldContain("The shared archive file has a bad magic number"); 402 output.shouldNotContain("Checksum verification failed"); 403 404 // modify _jvm_ident, test should fail 405 System.out.println("\n2a. Corrupt _jvm_ident, should fail\n"); 406 modifyJvmIdent(copyFile(orgJsaFile, "modify-jvm-ident")); 407 output = TestCommon.execCommon(execArgs); 408 output.shouldContain("The shared archive file was created by a different version or build of HotSpot"); 409 output.shouldNotContain("Checksum verification failed"); 410 411 // use the same archive as above, but run with -Xshare:auto 412 System.out.println("\n2b. Corrupt _jvm_ident run with -Xshare:auto\n"); 413 output = TestCommon.execAuto(execArgs); 414 output.shouldContain("The shared archive file was created by a different version or build of HotSpot"); 415 output.shouldContain("Hello World"); 416 417 // modify _magic, test should fail 418 System.out.println("\n2c. Corrupt _magic, should fail\n"); 419 modifyHeaderIntField(copyFile(orgJsaFile, "modify-magic"), offset_magic, 0x00000000); 420 output = TestCommon.execCommon(execArgs); 421 output.shouldContain("The shared archive file has a bad magic number"); 422 output.shouldNotContain("Checksum verification failed"); 423 424 // modify _version, test should fail 425 System.out.println("\n2d. Corrupt _version, should fail\n"); 426 modifyHeaderIntField(copyFile(orgJsaFile, "modify-version"), offset_version, 0x00000000); 427 output = TestCommon.execCommon(execArgs); 428 output.shouldContain("The shared archive file has the wrong version"); 429 output.shouldNotContain("Checksum verification failed"); 430 431 // modify content inside regions 432 System.out.println("\n3. Corrupt Content, should fail\n"); 433 for (int i=0; i<num_regions; i++) { 434 File newJsaFile = copyFile(orgJsaFile, (shared_region_name[i])); 435 if (modifyJsaContent(i, newJsaFile)) { 436 testAndCheck(verifyExecArgs); 437 } 438 } 439 440 // modify both header and content, test should fail 441 System.out.println("\n4. Corrupt Header and Content, should fail\n"); 442 File newJsaFile = copyFile(orgJsaFile, "header-and-content"); 443 modifyJsaHeader(newJsaFile); 444 modifyJsaContent(0, newJsaFile); // this will not be reached since failed on header change first 445 output = TestCommon.execCommon(execArgs); 446 output.shouldContain("The shared archive file has a bad magic number"); 447 output.shouldNotContain("Checksum verification failed"); 448 449 // delete bytes in data section 450 System.out.println("\n5. Delete bytes at beginning of data section, should fail\n"); 451 insertOrDeleteBytes(orgJsaFile, true); 452 testAndCheck(verifyExecArgs); 453 454 // insert bytes in data section forward 455 System.out.println("\n6. Insert bytes at beginning of data section, should fail\n"); 456 insertOrDeleteBytes(orgJsaFile, false); 457 testAndCheck(verifyExecArgs); 458 459 System.out.println("\n7. modify Content in random areas, should fail\n"); 460 modifyJsaContentRandomly(copyFile(orgJsaFile, "random-areas")); 461 testAndCheck(verifyExecArgs); 462 } 463 } 464