1 /*
   2  * Copyright (c) 2014, 2017, 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  *   AppCDS does not support uncompressed oops
  29  *  @requires (vm.opt.UseCompressedOops == null) | (vm.opt.UseCompressedOops == true)
  30  *  @library /test/lib
  31  *  @modules java.base/jdk.internal.misc
  32  *           java.compiler
  33  *           java.management
  34  *           jdk.jartool/sun.tools.jar
  35  *           jdk.internal.jvmstat/sun.jvmstat.monitor
  36  *  @build sun.hotspot.WhiteBox
  37  *  @compile test-classes/Hello.java
  38  *  @run main ClassFileInstaller sun.hotspot.WhiteBox
  39  *  @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI SharedArchiveConsistency
  40  */
  41 import jdk.test.lib.process.OutputAnalyzer;
  42 import jdk.test.lib.Utils;
  43 import java.io.File;
  44 import java.io.FileInputStream;
  45 import java.io.FileOutputStream;
  46 import java.io.IOException;
  47 import java.nio.ByteBuffer;
  48 import java.nio.ByteOrder;
  49 import java.nio.channels.FileChannel;
  50 import java.nio.file.Files;
  51 import java.nio.file.Path;
  52 import java.nio.file.Paths;
  53 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
  54 import java.nio.file.StandardOpenOption;
  55 import static java.nio.file.StandardOpenOption.READ;
  56 import static java.nio.file.StandardOpenOption.WRITE;
  57 import java.util.ArrayList;
  58 import java.util.HashSet;
  59 import java.util.List;
  60 import java.util.Random;
  61 import sun.hotspot.WhiteBox;
  62 
  63 public class SharedArchiveConsistency {
  64     public static WhiteBox wb;
  65     public static int offset_magic;    // FileMapHeader::_magic
  66     public static int sp_offset_crc;   // FileMapHeader::space_info::_crc
  67     public static int file_header_size = -1;// total size of header, variant, need calculation
  68     public static int space_info_size; // size of space_info
  69     public static int sp_offset;       // offset of FileMapHeader::space_info
  70     public static int sp_used_offset;  // offset of space_info::_used
  71     public static int size_t_size;     // size of size_t
  72 
  73     public static File jsa;        // will be updated during test
  74     public static File orgJsaFile; // kept the original file not touched.
  75     public static String[] shared_region_name = {"MiscCode", "ReadWrite", "ReadOnly", "MiscData"};
  76     public static int num_regions = shared_region_name.length;
  77     public static String[] matchMessages = {
  78         "Unable to use shared archive",
  79         "An error has occurred while processing the shared archive file.",
  80         "Checksum verification failed.",
  81         "The shared archive file has been truncated."
  82     };
  83 
  84     public static void getFileOffsetInfo() throws Exception {
  85         wb = WhiteBox.getWhiteBox();
  86         offset_magic = wb.getOffsetForName("FileMapHeader::_magic");
  87         sp_offset_crc = wb.getOffsetForName("space_info::_crc");
  88         try {
  89             int nonExistOffset = wb.getOffsetForName("FileMapHeader::_non_exist_offset");
  90             System.exit(-1); // should fail
  91         } catch (Exception e) {
  92             // success
  93         }
  94 
  95         sp_offset = wb.getOffsetForName("FileMapHeader::_space[0]") - offset_magic;
  96         sp_used_offset = wb.getOffsetForName("space_info::_used") - sp_offset_crc;
  97         size_t_size = wb.getOffsetForName("size_t_size");
  98         space_info_size  = wb.getOffsetForName("space_info_size");
  99     }
 100 
 101     public static int getFileHeaderSize(FileChannel fc) throws Exception {
 102         if (file_header_size != -1) {
 103             return file_header_size;
 104         }
 105         // this is not real header size, it is struct size
 106         file_header_size = wb.getOffsetForName("file_header_size");
 107         int offset_path_misc_info = wb.getOffsetForName("FileMapHeader::_paths_misc_info_size") -
 108             offset_magic;
 109         int path_misc_info_size   = (int)readInt(fc, offset_path_misc_info, size_t_size);
 110         file_header_size += path_misc_info_size; //readInt(fc, offset_path_misc_info, size_t_size);
 111         System.out.println("offset_path_misc_info = " + offset_path_misc_info);
 112         System.out.println("path_misc_info_size   = " + path_misc_info_size);
 113         System.out.println("file_header_size      = " + file_header_size);
 114         file_header_size = (int)align_up_page(file_header_size);
 115         System.out.println("file_header_size (aligned to page) = " + file_header_size);
 116         return file_header_size;
 117     }
 118 
 119     public static long align_up_page(long l) throws Exception {
 120         // wb is obtained in getFileOffsetInfo() which is called first in main() else we should call
 121         // WhiteBox.getWhiteBox() here first.
 122         int pageSize = wb.getVMPageSize();
 123         return (l + pageSize -1) & (~ (pageSize - 1));
 124     }
 125 
 126     private static long getRandomBetween(long start, long end) throws Exception {
 127         if (start > end) {
 128             throw new IllegalArgumentException("start must be less than end");
 129         }
 130         Random aRandom = Utils.getRandomInstance();
 131         int d = aRandom.nextInt((int)(end - start));
 132         if (d < 1) {
 133             d = 1;
 134         }
 135         return start + d;
 136     }
 137 
 138     public static long readInt(FileChannel fc, long offset, int nbytes) throws Exception {
 139         ByteBuffer bb = ByteBuffer.allocate(nbytes);
 140         bb.order(ByteOrder.nativeOrder());
 141         fc.position(offset);
 142         fc.read(bb);
 143         return  (nbytes > 4 ? bb.getLong(0) : bb.getInt(0));
 144     }
 145 
 146     public static void writeData(FileChannel fc, long offset, ByteBuffer bb) throws Exception {
 147         fc.position(offset);
 148         fc.write(bb);
 149         fc.force(true);
 150     }
 151 
 152     public static FileChannel getFileChannel() throws Exception {
 153         List<StandardOpenOption> arry = new ArrayList<StandardOpenOption>();
 154         arry.add(READ);
 155         arry.add(WRITE);
 156         return FileChannel.open(jsa.toPath(), new HashSet<StandardOpenOption>(arry));
 157     }
 158 
 159     public static void modifyJsaContentRandomly() throws Exception {
 160         FileChannel fc = getFileChannel();
 161         // corrupt random area in the data areas (MiscCode, ReadWrite, ReadOnly, MiscData)
 162         long[] used    = new long[num_regions];       // record used bytes
 163         long start0, start, end, off;
 164         int used_offset, path_info_size;
 165 
 166         int bufSize;
 167         System.out.printf("%-12s%-12s%-12s%-12s%-12s\n", "Space Name", "Offset", "Used bytes", "Reg Start", "Random Offset");
 168         start0 = getFileHeaderSize(fc);
 169         for (int i = 0; i < num_regions; i++) {
 170             used_offset = sp_offset + space_info_size * i + sp_used_offset;
 171             // read 'used'
 172             used[i] = readInt(fc, used_offset, size_t_size);
 173             start = start0;
 174             for (int j = 0; j < i; j++) {
 175                 start += align_up_page(used[j]);
 176             }
 177             end = start + used[i];
 178             off = getRandomBetween(start, end);
 179             System.out.printf("%-12s%-12d%-12d%-12d%-12d\n", shared_region_name[i], used_offset, used[i], start, off);
 180             if (end - off < 1024) {
 181                 bufSize = (int)(end - off + 1);
 182             } else {
 183                 bufSize = 1024;
 184             }
 185             ByteBuffer bbuf = ByteBuffer.wrap(new byte[bufSize]);
 186             writeData(fc, off, bbuf);
 187         }
 188         if (fc.isOpen()) {
 189             fc.close();
 190         }
 191     }
 192 
 193     public static void modifyJsaContent() throws Exception {
 194         FileChannel fc = getFileChannel();
 195         byte[] buf = new byte[4096];
 196         ByteBuffer bbuf = ByteBuffer.wrap(buf);
 197 
 198         long total = 0L;
 199         long used_offset = 0L;
 200         long[] used = new long[num_regions];
 201         System.out.printf("%-12s%-12s\n", "Space name", "Used bytes");
 202         for (int i = 0; i < num_regions; i++) {
 203             used_offset = sp_offset + space_info_size* i + sp_used_offset;
 204             // read 'used'
 205             used[i] = readInt(fc, used_offset, size_t_size);
 206             System.out.printf("%-12s%-12d\n", shared_region_name[i], used[i]);
 207             total += used[i];
 208         }
 209         System.out.printf("%-12s%-12d\n", "Total: ", total);
 210         long corrupt_used_offset =  getFileHeaderSize(fc);
 211         System.out.println("Corrupt RO section, offset = " + corrupt_used_offset);
 212         while (used_offset < used[0]) {
 213             writeData(fc, corrupt_used_offset, bbuf);
 214             bbuf.clear();
 215             used_offset += 4096;
 216         }
 217         fc.force(true);
 218         if (fc.isOpen()) {
 219             fc.close();
 220         }
 221     }
 222 
 223     public static void modifyJsaHeader() throws Exception {
 224         FileChannel fc = getFileChannel();
 225         // screw up header info
 226         byte[] buf = new byte[getFileHeaderSize(fc)];
 227         ByteBuffer bbuf = ByteBuffer.wrap(buf);
 228         writeData(fc, 0L, bbuf);
 229         if (fc.isOpen()) {
 230             fc.close();
 231         }
 232     }
 233 
 234     public static void copyFile(File from, File to) throws Exception {
 235         if (to.exists()) {
 236             if(!to.delete()) {
 237                 throw new IOException("Could not delete file " + to);
 238             }
 239         }
 240         to.createNewFile();
 241         setReadWritePermission(to);
 242         Files.copy(from.toPath(), to.toPath(), REPLACE_EXISTING);
 243     }
 244 
 245     // Copy file with bytes deleted or inserted
 246     // del -- true, deleted, false, inserted
 247     public static void copyFile(File from, File to, boolean del) throws Exception {
 248         try (
 249             FileChannel inputChannel = new FileInputStream(from).getChannel();
 250             FileChannel outputChannel = new FileOutputStream(to).getChannel()
 251         ) {
 252             long size = inputChannel.size();
 253             int init_size = getFileHeaderSize(inputChannel);
 254             outputChannel.transferFrom(inputChannel, 0, init_size);
 255             int n = (int)getRandomBetween(0, 1024);
 256             if (del) {
 257                 System.out.println("Delete " + n + " bytes at data start section");
 258                 inputChannel.position(init_size + n);
 259                 outputChannel.transferFrom(inputChannel, init_size, size - init_size - n);
 260             } else {
 261                 System.out.println("Insert " + n + " bytes at data start section");
 262                 outputChannel.position(init_size);
 263                 outputChannel.write(ByteBuffer.wrap(new byte[n]));
 264                 outputChannel.transferFrom(inputChannel, init_size + n , size - init_size);
 265             }
 266         }
 267     }
 268 
 269     public static void restoreJsaFile() throws Exception {
 270         Files.copy(orgJsaFile.toPath(), jsa.toPath(), REPLACE_EXISTING);
 271     }
 272 
 273     public static void setReadWritePermission(File file) throws Exception {
 274         if (!file.canRead()) {
 275             if (!file.setReadable(true)) {
 276                 throw new IOException("Cannot modify file " + file + " as readable");
 277             }
 278         }
 279         if (!file.canWrite()) {
 280             if (!file.setWritable(true)) {
 281                 throw new IOException("Cannot modify file " + file + " as writable");
 282             }
 283         }
 284     }
 285 
 286     public static void testAndCheck(String[] execArgs) throws Exception {
 287         OutputAnalyzer output = TestCommon.execCommon(execArgs);
 288         String stdtxt = output.getOutput();
 289         System.out.println("Note: this test may fail in very rare occasions due to CRC32 checksum collision");
 290         for (String message : matchMessages) {
 291             if (stdtxt.contains(message)) {
 292                 // match any to return
 293                 return;
 294             }
 295         }
 296         TestCommon.checkExec(output);
 297     }
 298 
 299     // dump with hello.jsa, then
 300     // read the jsa file
 301     //   1) run normal
 302     //   2) modify header
 303     //   3) keep header correct but modify content
 304     //   4) update both header and content, test
 305     //   5) delete bytes in data begining
 306     //   6) insert bytes in data begining
 307     //   7) randomly corrupt data in four areas: RO, RW. MISC DATA, MISC CODE
 308     public static void main(String... args) throws Exception {
 309         // must call to get offset info first!!!
 310         getFileOffsetInfo();
 311         Path currentRelativePath = Paths.get("");
 312         String currentDir = currentRelativePath.toAbsolutePath().toString();
 313         System.out.println("Current relative path is: " + currentDir);
 314         // get jar file
 315         String jarFile = JarBuilder.getOrCreateHelloJar();
 316 
 317         // dump (appcds.jsa created)
 318         TestCommon.testDump(jarFile, null);
 319 
 320         // test, should pass
 321         System.out.println("1. Normal, should pass but may fail\n");
 322         String[] execArgs = {"-cp", jarFile, "Hello"};
 323 
 324         OutputAnalyzer output = TestCommon.execCommon(execArgs);
 325 
 326         try {
 327             TestCommon.checkExecReturn(output, 0, true, "Hello World");
 328         } catch (Exception e) {
 329             TestCommon.checkExecReturn(output, 1, true, matchMessages[0]);
 330         }
 331 
 332         // get current archive name
 333         jsa = new File(TestCommon.getCurrentArchiveName());
 334         if (!jsa.exists()) {
 335             throw new IOException(jsa + " does not exist!");
 336         }
 337 
 338         setReadWritePermission(jsa);
 339 
 340         // save as original untouched
 341         orgJsaFile = new File(new File(currentDir), "appcds.jsa.bak");
 342         copyFile(jsa, orgJsaFile);
 343 
 344 
 345         // modify jsa header, test should fail
 346         System.out.println("\n2. Corrupt header, should fail\n");
 347         modifyJsaHeader();
 348         output = TestCommon.execCommon(execArgs);
 349         output.shouldContain("The shared archive file has the wrong version");
 350         output.shouldNotContain("Checksum verification failed");
 351 
 352         // modify content
 353         System.out.println("\n3. Corrupt Content, should fail\n");
 354         copyFile(orgJsaFile, jsa);
 355         modifyJsaContent();
 356         testAndCheck(execArgs);
 357 
 358         // modify both header and content, test should fail
 359         System.out.println("\n4. Corrupt Header and Content, should fail\n");
 360         copyFile(orgJsaFile, jsa);
 361         modifyJsaHeader();
 362         modifyJsaContent();  // this will not be reached since failed on header change first
 363         output = TestCommon.execCommon(execArgs);
 364         output.shouldContain("The shared archive file has the wrong version");
 365         output.shouldNotContain("Checksum verification failed");
 366 
 367         // delete bytes in data sectoin
 368         System.out.println("\n5. Delete bytes at begining of data section, should fail\n");
 369         copyFile(orgJsaFile, jsa, true);
 370         testAndCheck(execArgs);
 371 
 372         // insert bytes in data sectoin forward
 373         System.out.println("\n6. Insert bytes at begining of data section, should fail\n");
 374         copyFile(orgJsaFile, jsa, false);
 375         testAndCheck(execArgs);
 376 
 377         System.out.println("\n7. modify Content in random areas, should fail\n");
 378         copyFile(orgJsaFile, jsa);
 379         modifyJsaContentRandomly();
 380         testAndCheck(execArgs);
 381     }
 382 }