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