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 
getFileOffsetInfo()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 
getFileHeaderSize(FileChannel fc)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 
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 (MiscCode, ReadWrite, ReadOnly, MiscData)
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("%-12s%-12s%-12s%-12s%-12s\n", "Space Name", "Offset", "Used bytes", "Reg Start", "Random Offset");
172         start0 = getFileHeaderSize(fc);
173         for (int i = 0; i < num_regions; i++) {
174             used_offset = sp_offset + CDSFileMapRegion_size * i + sp_used_offset;
175             // read 'used'
176             used[i] = readInt(fc, used_offset, size_t_size);
177             start = start0;
178             for (int j = 0; j < i; j++) {
179                 start += align_up_page(used[j]);
180             }
181             end = start + used[i];
182             off = getRandomBetween(start, end);
183             System.out.printf("%-12s%-12d%-12d%-12d%-12d\n", shared_region_name[i], used_offset, used[i], start, off);
184             if (end - off < 1024) {
185                 bufSize = (int)(end - off + 1);
186             } else {
187                 bufSize = 1024;
188             }
189             ByteBuffer bbuf = ByteBuffer.wrap(new byte[bufSize]);
190             writeData(fc, off, bbuf);
191         }
192         if (fc.isOpen()) {
193             fc.close();
194         }
195     }
196 
modifyJsaContent(File jsaFile)197     public static void modifyJsaContent(File jsaFile) throws Exception {
198         FileChannel fc = getFileChannel(jsaFile);
199         byte[] buf = new byte[4096];
200         ByteBuffer bbuf = ByteBuffer.wrap(buf);
201 
202         long total = 0L;
203         long used_offset = 0L;
204         long[] used = new long[num_regions];
205         System.out.printf("%-12s%-12s\n", "Space name", "Used bytes");
206         for (int i = 0; i < num_regions; i++) {
207             used_offset = sp_offset + CDSFileMapRegion_size* i + sp_used_offset;
208             // read 'used'
209             used[i] = readInt(fc, used_offset, size_t_size);
210             System.out.printf("%-12s%-12d\n", shared_region_name[i], used[i]);
211             total += used[i];
212         }
213         System.out.printf("%-12s%-12d\n", "Total: ", total);
214         long corrupt_used_offset =  getFileHeaderSize(fc);
215         System.out.println("Corrupt RO section, offset = " + corrupt_used_offset);
216         while (used_offset < used[0]) {
217             writeData(fc, corrupt_used_offset, bbuf);
218             bbuf.clear();
219             used_offset += 4096;
220         }
221         if (fc.isOpen()) {
222             fc.close();
223         }
224     }
225 
modifyJsaHeader(File jsaFile)226     public static void modifyJsaHeader(File jsaFile) throws Exception {
227         FileChannel fc = getFileChannel(jsaFile);
228         // screw up header info
229         byte[] buf = new byte[getFileHeaderSize(fc)];
230         ByteBuffer bbuf = ByteBuffer.wrap(buf);
231         writeData(fc, 0L, bbuf);
232         if (fc.isOpen()) {
233             fc.close();
234         }
235     }
236 
modifyJvmIdent()237     public static void modifyJvmIdent() throws Exception {
238         FileChannel fc = getFileChannel(jsa);
239         int headerSize = getFileHeaderSize(fc);
240         System.out.println("    offset_jvm_ident " + offset_jvm_ident);
241         byte[] buf = new byte[256];
242         ByteBuffer bbuf = ByteBuffer.wrap(buf);
243         writeData(fc, (long)offset_jvm_ident, bbuf);
244         if (fc.isOpen()) {
245             fc.close();
246         }
247     }
248 
modifyHeaderIntField(long offset, int value)249     public static void modifyHeaderIntField(long offset, int value) throws Exception {
250         FileChannel fc = getFileChannel(jsa);
251         int headerSize = getFileHeaderSize(fc);
252         System.out.println("    offset " + offset);
253         byte[] buf = ByteBuffer.allocate(4).putInt(value).array();
254         ByteBuffer bbuf = ByteBuffer.wrap(buf);
255         writeData(fc, offset, bbuf);
256         if (fc.isOpen()) {
257             fc.close();
258         }
259     }
260 
copyFile(File from, File to)261     public static void copyFile(File from, File to) throws Exception {
262         if (to.exists()) {
263             if(!to.delete()) {
264                 throw new IOException("Could not delete file " + to);
265             }
266         }
267         to.createNewFile();
268         setReadWritePermission(to);
269         Files.copy(from.toPath(), to.toPath(), REPLACE_EXISTING);
270     }
271 
272     // Copy file with bytes deleted or inserted
273     // del -- true, deleted, false, inserted
copyFile(File from, File to, boolean del)274     public static void copyFile(File from, File to, boolean del) throws Exception {
275         try (
276             FileChannel inputChannel = new FileInputStream(from).getChannel();
277             FileChannel outputChannel = new FileOutputStream(to).getChannel()
278         ) {
279             long size = inputChannel.size();
280             int init_size = getFileHeaderSize(inputChannel);
281             outputChannel.transferFrom(inputChannel, 0, init_size);
282             int n = (int)getRandomBetween(0, 1024);
283             if (del) {
284                 System.out.println("Delete " + n + " bytes at data start section");
285                 inputChannel.position(init_size + n);
286                 outputChannel.transferFrom(inputChannel, init_size, size - init_size - n);
287             } else {
288                 System.out.println("Insert " + n + " bytes at data start section");
289                 outputChannel.position(init_size);
290                 outputChannel.write(ByteBuffer.wrap(new byte[n]));
291                 outputChannel.transferFrom(inputChannel, init_size + n , size - init_size);
292             }
293         }
294     }
295 
restoreJsaFile()296     public static void restoreJsaFile() throws Exception {
297         Files.copy(orgJsaFile.toPath(), jsa.toPath(), REPLACE_EXISTING);
298     }
299 
setReadWritePermission(File file)300     public static void setReadWritePermission(File file) throws Exception {
301         if (!file.canRead()) {
302             if (!file.setReadable(true)) {
303                 throw new IOException("Cannot modify file " + file + " as readable");
304             }
305         }
306         if (!file.canWrite()) {
307             if (!file.setWritable(true)) {
308                 throw new IOException("Cannot modify file " + file + " as writable");
309             }
310         }
311     }
312 
testAndCheck(String[] execArgs)313     public static void testAndCheck(String[] execArgs) throws Exception {
314         OutputAnalyzer output = TestCommon.execCommon(execArgs);
315         String stdtxt = output.getOutput();
316         System.out.println("Note: this test may fail in very rare occasions due to CRC32 checksum collision");
317         for (String message : matchMessages) {
318             if (stdtxt.contains(message)) {
319                 // match any to return
320                 return;
321             }
322         }
323         TestCommon.checkExec(output);
324     }
325 
326     // dump with hello.jsa, then
327     // read the jsa file
328     //   1) run normal
329     //   2) modify header
330     //   3) keep header correct but modify content
331     //   4) update both header and content, test
332     //   5) delete bytes in data begining
333     //   6) insert bytes in data begining
334     //   7) randomly corrupt data in four areas: RO, RW. MISC DATA, MISC CODE
main(String... args)335     public static void main(String... args) throws Exception {
336         // must call to get offset info first!!!
337         getFileOffsetInfo();
338         Path currentRelativePath = Paths.get("");
339         String currentDir = currentRelativePath.toAbsolutePath().toString();
340         System.out.println("Current relative path is: " + currentDir);
341         // get jar file
342         String jarFile = JarBuilder.getOrCreateHelloJar();
343 
344         // dump (appcds.jsa created)
345         TestCommon.testDump(jarFile, null);
346 
347         // test, should pass
348         System.out.println("1. Normal, should pass but may fail\n");
349         String[] execArgs = {"-Xlog:cds", "-cp", jarFile, "Hello"};
350 
351         OutputAnalyzer output = TestCommon.execCommon(execArgs);
352 
353         try {
354             TestCommon.checkExecReturn(output, 0, true, "Hello World");
355         } catch (Exception e) {
356             TestCommon.checkExecReturn(output, 1, true, matchMessages[0]);
357         }
358 
359         // get current archive name
360         jsa = new File(TestCommon.getCurrentArchiveName());
361         if (!jsa.exists()) {
362             throw new IOException(jsa + " does not exist!");
363         }
364 
365         setReadWritePermission(jsa);
366 
367         // save as original untouched
368         orgJsaFile = new File(new File(currentDir), "appcds.jsa.bak");
369         copyFile(jsa, orgJsaFile);
370 
371         // modify jsa header, test should fail
372         System.out.println("\n2. Corrupt header, should fail\n");
373         modifyJsaHeader(jsa);
374         output = TestCommon.execCommon(execArgs);
375         output.shouldContain("The shared archive file has a bad magic number");
376         output.shouldNotContain("Checksum verification failed");
377 
378         copyFile(orgJsaFile, jsa);
379         // modify _jvm_ident and _paths_misc_info_size, test should fail
380         System.out.println("\n2a. Corrupt _jvm_ident and _paths_misc_info_size, should fail\n");
381         modifyJvmIdent();
382         modifyHeaderIntField(offset_paths_misc_info_size, Integer.MAX_VALUE);
383         output = TestCommon.execCommon(execArgs);
384         output.shouldContain("The shared archive file was created by a different version or build of HotSpot");
385         output.shouldNotContain("Checksum verification failed");
386 
387         copyFile(orgJsaFile, jsa);
388         // modify _jvm_ident and run with -Xshare:auto
389         System.out.println("\n2b. Corrupt _jvm_ident run with -Xshare:auto\n");
390         modifyJvmIdent();
391         output = TestCommon.execAuto(execArgs);
392         output.shouldContain("The shared archive file was created by a different version or build of HotSpot");
393         output.shouldContain("Hello World");
394 
395         copyFile(orgJsaFile, jsa);
396         // modify _magic and _paths_misc_info_size, test should fail
397         System.out.println("\n2c. Corrupt _magic and _paths_misc_info_size, should fail\n");
398         modifyHeaderIntField(offset_magic, 0x00000000);
399         modifyHeaderIntField(offset_paths_misc_info_size, Integer.MAX_VALUE);
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         copyFile(orgJsaFile, jsa);
405         // modify _version and _paths_misc_info_size, test should fail
406         System.out.println("\n2d. Corrupt _version and _paths_misc_info_size, should fail\n");
407         modifyHeaderIntField(offset_version, 0x00000000);
408         modifyHeaderIntField(offset_paths_misc_info_size, Integer.MAX_VALUE);
409         output = TestCommon.execCommon(execArgs);
410         output.shouldContain("The shared archive file has the wrong version");
411         output.shouldNotContain("Checksum verification failed");
412 
413         // modify content
414         System.out.println("\n3. Corrupt Content, should fail\n");
415         copyFile(orgJsaFile, jsa);
416         modifyJsaContent(jsa);
417         testAndCheck(execArgs);
418 
419         // modify both header and content, test should fail
420         System.out.println("\n4. Corrupt Header and Content, should fail\n");
421         copyFile(orgJsaFile, jsa);
422         modifyJsaHeader(jsa);
423         modifyJsaContent(jsa);  // this will not be reached since failed on header change first
424         output = TestCommon.execCommon(execArgs);
425         output.shouldContain("The shared archive file has a bad magic number");
426         output.shouldNotContain("Checksum verification failed");
427 
428         // delete bytes in data sectoin
429         System.out.println("\n5. Delete bytes at begining of data section, should fail\n");
430         copyFile(orgJsaFile, jsa, true);
431         TestCommon.setCurrentArchiveName(jsa.toString());
432         testAndCheck(execArgs);
433 
434         // insert bytes in data sectoin forward
435         System.out.println("\n6. Insert bytes at begining of data section, should fail\n");
436         copyFile(orgJsaFile, jsa, false);
437         testAndCheck(execArgs);
438 
439         System.out.println("\n7. modify Content in random areas, should fail\n");
440         copyFile(orgJsaFile, jsa);
441         TestCommon.setCurrentArchiveName(jsa.toString());
442         modifyJsaContentRandomly(jsa);
443         testAndCheck(execArgs);
444     }
445 }
446