1 /*
   2  * Copyright (c) 2014, 2018, 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; // FileMapHeader::_jvm_ident
  67     public static int sp_offset_crc;    // CDSFileMapRegion::_crc
  68     public static int offset_paths_misc_info_size;
  69     public static int file_header_size = -1;// total size of header, variant, need calculation
  70     public static int CDSFileMapRegion_size; // size of CDSFileMapRegion
  71     public static int sp_offset;       // offset of CDSFileMapRegion
  72     public static int sp_used_offset;  // offset of CDSFileMapRegion::_used
  73     public static int size_t_size;     // size of size_t
  74     public static int int_size;        // size of int
  75 
  76     public static File jsa;        // will be updated during test
  77     public static File orgJsaFile; // kept the original file not touched.
  78     public static String[] shared_region_name = {"MiscCode", "ReadWrite", "ReadOnly", "MiscData"};
  79     public static int num_regions = shared_region_name.length;
  80     public static String[] matchMessages = {
  81         "Unable to use shared archive",
  82         "An error has occurred while processing the shared archive file.",
  83         "Checksum verification failed.",
  84         "The shared archive file has been truncated."
  85     };
  86 
  87     public static void getFileOffsetInfo() throws Exception {
  88         wb = WhiteBox.getWhiteBox();
  89         offset_magic = wb.getOffsetForName("FileMapHeader::_magic");
  90         offset_version = wb.getOffsetForName("FileMapHeader::_version");
  91         offset_jvm_ident = wb.getOffsetForName("FileMapHeader::_jvm_ident");
  92         sp_offset_crc = wb.getOffsetForName("CDSFileMapRegion::_crc");
  93         try {
  94             int nonExistOffset = wb.getOffsetForName("FileMapHeader::_non_exist_offset");
  95             System.exit(-1); // should fail
  96         } catch (Exception e) {
  97             // success
  98         }
  99 
 100         sp_offset = wb.getOffsetForName("FileMapHeader::_space[0]") - offset_magic;
 101         sp_used_offset = wb.getOffsetForName("CDSFileMapRegion::_used") - sp_offset_crc;
 102         size_t_size = wb.getOffsetForName("size_t_size");
 103         CDSFileMapRegion_size  = wb.getOffsetForName("CDSFileMapRegion_size");
 104     }
 105 
 106     public static int getFileHeaderSize(FileChannel fc) throws Exception {
 107         if (file_header_size != -1) {
 108             return file_header_size;
 109         }
 110         // this is not real header size, it is struct size
 111         file_header_size = wb.getOffsetForName("file_header_size");
 112         offset_paths_misc_info_size = wb.getOffsetForName("FileMapHeader::_paths_misc_info_size") -
 113             offset_magic;
 114         int path_misc_info_size   = (int)readInt(fc, offset_paths_misc_info_size, size_t_size);
 115         file_header_size += path_misc_info_size;
 116         System.out.println("offset_paths_misc_info_size = " + offset_paths_misc_info_size);
 117         System.out.println("path_misc_info_size   = " + path_misc_info_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 
 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 
 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 
 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 
 151     public static void writeData(FileChannel fc, long offset, ByteBuffer bb) throws Exception {
 152         fc.position(offset);
 153         fc.write(bb);
 154         fc.force(true);
 155     }
 156 
 157     public static FileChannel getFileChannel(File jsaFile) throws Exception {
 158         List<StandardOpenOption> arry = new ArrayList<StandardOpenOption>();
 159         arry.add(READ);
 160         arry.add(WRITE);
 161         return FileChannel.open(jsaFile.toPath(), new HashSet<StandardOpenOption>(arry));
 162     }
 163 
 164     public static void modifyJsaContentRandomly(File jsaFile) throws Exception {
 165         FileChannel fc = getFileChannel(jsaFile);
 166         // corrupt random area in the data areas (MiscCode, ReadWrite, ReadOnly, MiscData)
 167         long[] used    = new long[num_regions];       // record used bytes
 168         long start0, start, end, off;
 169         int used_offset, path_info_size;
 170 
 171         int bufSize;
 172         System.out.printf("%-12s%-12s%-12s%-12s%-12s\n", "Space Name", "Offset", "Used bytes", "Reg Start", "Random Offset");
 173         start0 = getFileHeaderSize(fc);
 174         for (int i = 0; i < num_regions; i++) {
 175             used_offset = sp_offset + CDSFileMapRegion_size * i + sp_used_offset;
 176             // read 'used'
 177             used[i] = readInt(fc, used_offset, size_t_size);
 178             start = start0;
 179             for (int j = 0; j < i; j++) {
 180                 start += align_up_page(used[j]);
 181             }
 182             end = start + used[i];
 183             off = getRandomBetween(start, end);
 184             System.out.printf("%-12s%-12d%-12d%-12d%-12d\n", shared_region_name[i], used_offset, 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 
 198     public static void modifyJsaContent(File jsaFile) throws Exception {
 199         FileChannel fc = getFileChannel(jsaFile);
 200         byte[] buf = new byte[4096];
 201         ByteBuffer bbuf = ByteBuffer.wrap(buf);
 202 
 203         long total = 0L;
 204         long used_offset = 0L;
 205         long[] used = new long[num_regions];
 206         System.out.printf("%-12s%-12s\n", "Space name", "Used bytes");
 207         for (int i = 0; i < num_regions; i++) {
 208             used_offset = sp_offset + CDSFileMapRegion_size* i + sp_used_offset;
 209             // read 'used'
 210             used[i] = readInt(fc, used_offset, size_t_size);
 211             System.out.printf("%-12s%-12d\n", shared_region_name[i], used[i]);
 212             total += used[i];
 213         }
 214         System.out.printf("%-12s%-12d\n", "Total: ", total);
 215         long corrupt_used_offset =  getFileHeaderSize(fc);
 216         System.out.println("Corrupt RO section, offset = " + corrupt_used_offset);
 217         while (used_offset < used[0]) {
 218             writeData(fc, corrupt_used_offset, bbuf);
 219             bbuf.clear();
 220             used_offset += 4096;
 221         }
 222         fc.force(true);
 223         if (fc.isOpen()) {
 224             fc.close();
 225         }
 226     }
 227 
 228     public static void modifyJsaHeader(File jsaFile) throws Exception {
 229         FileChannel fc = getFileChannel(jsaFile);
 230         // screw up header info
 231         byte[] buf = new byte[getFileHeaderSize(fc)];
 232         ByteBuffer bbuf = ByteBuffer.wrap(buf);
 233         writeData(fc, 0L, bbuf);
 234         if (fc.isOpen()) {
 235             fc.close();
 236         }
 237     }
 238 
 239     public static void modifyJvmIdent() throws Exception {
 240         FileChannel fc = getFileChannel(jsa);
 241         int headerSize = getFileHeaderSize(fc);
 242         System.out.println("    offset_jvm_ident " + offset_jvm_ident);
 243         byte[] buf = new byte[256];
 244         ByteBuffer bbuf = ByteBuffer.wrap(buf);
 245         writeData(fc, (long)offset_jvm_ident, bbuf);
 246         if (fc.isOpen()) {
 247             fc.close();
 248         }
 249     }
 250 
 251     public static void modifyHeaderIntField(long offset, int value) throws Exception {
 252         FileChannel fc = getFileChannel(jsa);
 253         int headerSize = getFileHeaderSize(fc);
 254         System.out.println("    offset " + offset);
 255         byte[] buf = ByteBuffer.allocate(4).putInt(value).array();
 256         ByteBuffer bbuf = ByteBuffer.wrap(buf);
 257         writeData(fc, offset, bbuf);
 258         if (fc.isOpen()) {
 259             fc.close();
 260         }
 261     }
 262 
 263     public static void copyFile(File from, File to) throws Exception {
 264         if (to.exists()) {
 265             if(!to.delete()) {
 266                 throw new IOException("Could not delete file " + to);
 267             }
 268         }
 269         to.createNewFile();
 270         setReadWritePermission(to);
 271         Files.copy(from.toPath(), to.toPath(), REPLACE_EXISTING);
 272     }
 273 
 274     // Copy file with bytes deleted or inserted
 275     // del -- true, deleted, false, inserted
 276     public static void copyFile(File from, File to, boolean del) throws Exception {
 277         try (
 278             FileChannel inputChannel = new FileInputStream(from).getChannel();
 279             FileChannel outputChannel = new FileOutputStream(to).getChannel()
 280         ) {
 281             long size = inputChannel.size();
 282             int init_size = getFileHeaderSize(inputChannel);
 283             outputChannel.transferFrom(inputChannel, 0, init_size);
 284             int n = (int)getRandomBetween(0, 1024);
 285             if (del) {
 286                 System.out.println("Delete " + n + " bytes at data start section");
 287                 inputChannel.position(init_size + n);
 288                 outputChannel.transferFrom(inputChannel, init_size, size - init_size - n);
 289             } else {
 290                 System.out.println("Insert " + n + " bytes at data start section");
 291                 outputChannel.position(init_size);
 292                 outputChannel.write(ByteBuffer.wrap(new byte[n]));
 293                 outputChannel.transferFrom(inputChannel, init_size + n , size - init_size);
 294             }
 295         }
 296     }
 297 
 298     public static void restoreJsaFile() throws Exception {
 299         Files.copy(orgJsaFile.toPath(), jsa.toPath(), REPLACE_EXISTING);
 300     }
 301 
 302     public static void setReadWritePermission(File file) throws Exception {
 303         if (!file.canRead()) {
 304             if (!file.setReadable(true)) {
 305                 throw new IOException("Cannot modify file " + file + " as readable");
 306             }
 307         }
 308         if (!file.canWrite()) {
 309             if (!file.setWritable(true)) {
 310                 throw new IOException("Cannot modify file " + file + " as writable");
 311             }
 312         }
 313     }
 314 
 315     public static void testAndCheck(String[] execArgs) throws Exception {
 316         OutputAnalyzer output = TestCommon.execCommon(execArgs);
 317         String stdtxt = output.getOutput();
 318         System.out.println("Note: this test may fail in very rare occasions due to CRC32 checksum collision");
 319         for (String message : matchMessages) {
 320             if (stdtxt.contains(message)) {
 321                 // match any to return
 322                 return;
 323             }
 324         }
 325         TestCommon.checkExec(output);
 326     }
 327 
 328     // dump with hello.jsa, then
 329     // read the jsa file
 330     //   1) run normal
 331     //   2) modify header
 332     //   3) keep header correct but modify content
 333     //   4) update both header and content, test
 334     //   5) delete bytes in data begining
 335     //   6) insert bytes in data begining
 336     //   7) randomly corrupt data in four areas: RO, RW. MISC DATA, MISC CODE
 337     public static void main(String... args) throws Exception {
 338         // must call to get offset info first!!!
 339         getFileOffsetInfo();
 340         Path currentRelativePath = Paths.get("");
 341         String currentDir = currentRelativePath.toAbsolutePath().toString();
 342         System.out.println("Current relative path is: " + currentDir);
 343         // get jar file
 344         String jarFile = JarBuilder.getOrCreateHelloJar();
 345 
 346         // dump (appcds.jsa created)
 347         TestCommon.testDump(jarFile, null);
 348 
 349         // test, should pass
 350         System.out.println("1. Normal, should pass but may fail\n");
 351         String[] execArgs = {"-Xlog:cds", "-cp", jarFile, "Hello"};
 352 
 353         OutputAnalyzer output = TestCommon.execCommon(execArgs);
 354 
 355         try {
 356             TestCommon.checkExecReturn(output, 0, true, "Hello World");
 357         } catch (Exception e) {
 358             TestCommon.checkExecReturn(output, 1, true, matchMessages[0]);
 359         }
 360 
 361         // get current archive name
 362         jsa = new File(TestCommon.getCurrentArchiveName());
 363         if (!jsa.exists()) {
 364             throw new IOException(jsa + " does not exist!");
 365         }
 366 
 367         setReadWritePermission(jsa);
 368 
 369         // save as original untouched
 370         orgJsaFile = new File(new File(currentDir), "appcds.jsa.bak");
 371         copyFile(jsa, orgJsaFile);
 372 
 373         // modify jsa header, test should fail
 374         System.out.println("\n2. Corrupt header, should fail\n");
 375         modifyJsaHeader(jsa);
 376         output = TestCommon.execCommon(execArgs);
 377         output.shouldContain("The shared archive file has a bad magic number");
 378         output.shouldNotContain("Checksum verification failed");
 379 
 380         copyFile(orgJsaFile, jsa);
 381         // modify _jvm_ident and _paths_misc_info_size, test should fail
 382         System.out.println("\n2a. Corrupt _jvm_ident and _paths_misc_info_size, should fail\n");
 383         modifyJvmIdent();
 384         modifyHeaderIntField(offset_paths_misc_info_size, Integer.MAX_VALUE);
 385         output = TestCommon.execCommon(execArgs);
 386         output.shouldContain("The shared archive file was created by a different version or build of HotSpot");
 387         output.shouldNotContain("Checksum verification failed");
 388 
 389         copyFile(orgJsaFile, jsa);
 390         // modify _magic and _paths_misc_info_size, test should fail
 391         System.out.println("\n2b. Corrupt _magic and _paths_misc_info_size, should fail\n");
 392         modifyHeaderIntField(offset_magic, 0x00000000);
 393         modifyHeaderIntField(offset_paths_misc_info_size, Integer.MAX_VALUE);
 394         output = TestCommon.execCommon(execArgs);
 395         output.shouldContain("The shared archive file has a bad magic number");
 396         output.shouldNotContain("Checksum verification failed");
 397 
 398         copyFile(orgJsaFile, jsa);
 399         // modify _version and _paths_misc_info_size, test should fail
 400         System.out.println("\n2c. Corrupt _version and _paths_misc_info_size, should fail\n");
 401         modifyHeaderIntField(offset_version, 0x00000000);
 402         modifyHeaderIntField(offset_paths_misc_info_size, Integer.MAX_VALUE);
 403         output = TestCommon.execCommon(execArgs);
 404         output.shouldContain("The shared archive file has the wrong version");
 405         output.shouldNotContain("Checksum verification failed");
 406 
 407         // modify content
 408         System.out.println("\n3. Corrupt Content, should fail\n");
 409         copyFile(orgJsaFile, jsa);
 410         modifyJsaContent(jsa);
 411         testAndCheck(execArgs);
 412 
 413         // modify both header and content, test should fail
 414         System.out.println("\n4. Corrupt Header and Content, should fail\n");
 415         copyFile(orgJsaFile, jsa);
 416         modifyJsaHeader(jsa);
 417         modifyJsaContent(jsa);  // this will not be reached since failed on header change first
 418         output = TestCommon.execCommon(execArgs);
 419         output.shouldContain("The shared archive file has a bad magic number");
 420         output.shouldNotContain("Checksum verification failed");
 421 
 422         // delete bytes in data sectoin
 423         System.out.println("\n5. Delete bytes at begining of data section, should fail\n");
 424         copyFile(orgJsaFile, jsa, true);
 425         TestCommon.setCurrentArchiveName(jsa.toString());
 426         testAndCheck(execArgs);
 427 
 428         // insert bytes in data sectoin forward
 429         System.out.println("\n6. Insert bytes at begining of data section, should fail\n");
 430         copyFile(orgJsaFile, jsa, false);
 431         testAndCheck(execArgs);
 432 
 433         System.out.println("\n7. modify Content in random areas, should fail\n");
 434         copyFile(orgJsaFile, jsa);
 435         TestCommon.setCurrentArchiveName(jsa.toString());
 436         modifyJsaContentRandomly(jsa);
 437         testAndCheck(execArgs);
 438     }
 439 }