1 /*
  2  * Copyright (c) 2025, Red Hat, Inc.
  3  * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
  4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  5  *
  6  * This code is free software; you can redistribute it and/or modify it
  7  * under the terms of the GNU General Public License version 2 only, as
  8  * published by the Free Software Foundation.
  9  *
 10  * This code is distributed in the hope that it will be useful, but WITHOUT
 11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 13  * version 2 for more details (a copy is included in the LICENSE file that
 14  * accompanied this code).
 15  *
 16  * You should have received a copy of the GNU General Public License version
 17  * 2 along with this work; if not, write to the Free Software Foundation,
 18  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 19  *
 20  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 21  * or visit www.oracle.com if you need additional information or have any
 22  * questions.
 23  */
 24 
 25 /*
 26  * @test id=no_coh_no_cds
 27  * @summary Test that dereferencing a Klass that is the result of a decode(0) crashes accessing the nKlass guard zone
 28  * @library /test/lib
 29  * @requires vm.bits == 64 & vm.debug == true & vm.flagless
 30  * @requires os.family != "aix"
 31  * @comment This test relies on crashing which conflicts with ASAN checks
 32  * @requires !vm.asan
 33  * @modules java.base/jdk.internal.misc
 34  *          java.management
 35  * @build jdk.test.whitebox.WhiteBox
 36  * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 37  * @run driver AccessZeroNKlassHitsProtectionZone no_coh_no_cds
 38  */
 39 
 40 /*
 41  * @test id=no_coh_cds
 42  * @summary Test that dereferencing a Klass that is the result of a decode(0) crashes accessing the nKlass guard zone
 43  * @requires vm.cds & vm.bits == 64 & vm.debug == true & vm.flagless
 44  * @requires os.family != "aix"
 45  * @comment This test relies on crashing which conflicts with ASAN checks
 46  * @requires !vm.asan
 47  * @library /test/lib
 48  * @modules java.base/jdk.internal.misc
 49  *          java.management
 50  * @build jdk.test.whitebox.WhiteBox
 51  * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 52  * @run driver AccessZeroNKlassHitsProtectionZone no_coh_cds
 53  */
 54 
 55 /*
 56  * @test id=coh_no_cds
 57  * @summary Test that dereferencing a Klass that is the result of a decode(0) crashes accessing the nKlass guard zone
 58  * @requires vm.bits == 64 & vm.debug == true & vm.flagless
 59  * @requires os.family != "aix"
 60  * @comment This test relies on crashing which conflicts with ASAN checks
 61  * @requires !vm.asan
 62  * @library /test/lib
 63  * @modules java.base/jdk.internal.misc
 64  *          java.management
 65  * @build jdk.test.whitebox.WhiteBox
 66  * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 67  * @run driver AccessZeroNKlassHitsProtectionZone coh_no_cds
 68  */
 69 
 70 /*
 71  * @test id=coh_cds
 72  * @summary Test that dereferencing a Klass that is the result of a decode(0) crashes accessing the nKlass guard zone
 73  * @requires vm.cds & vm.bits == 64 & vm.debug == true & vm.flagless
 74  * @requires os.family != "aix"
 75  * @comment This test relies on crashing which conflicts with ASAN checks
 76  * @requires !vm.asan
 77  * @library /test/lib
 78  * @modules java.base/jdk.internal.misc
 79  *          java.management
 80  * @build jdk.test.whitebox.WhiteBox
 81  * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 82  * @run driver AccessZeroNKlassHitsProtectionZone coh_cds
 83  */
 84 
 85 import jdk.test.lib.process.OutputAnalyzer;
 86 import jdk.test.lib.process.ProcessTools;
 87 import jdk.test.whitebox.WhiteBox;
 88 import jtreg.SkippedException;
 89 
 90 import java.io.File;
 91 import java.io.IOException;
 92 import java.util.ArrayList;
 93 import java.util.regex.Pattern;
 94 
 95 // Test that dereferencing a Klass that is the result of a narrowKlass=0 will give us immediate crashes
 96 // that hit the protection zone at encoding base.
 97 public class AccessZeroNKlassHitsProtectionZone {
 98 
 99     private static OutputAnalyzer run_test(boolean COH, boolean CDS, String forceBaseString) throws IOException, SkippedException {
100         ArrayList<String> args = new ArrayList<>();
101         args.add("-Xbootclasspath/a:.");
102         args.add("-XX:+UnlockDiagnosticVMOptions");
103         args.add("-XX:+WhiteBoxAPI");
104         args.add("-XX:CompressedClassSpaceSize=128m");
105         args.add("-Xmx128m");
106         args.add("-XX:-CreateCoredumpOnCrash");
107         args.add("-Xlog:metaspace*");
108         args.add("-Xlog:cds");
109         if (COH) {
110             args.add("-XX:+UseCompactObjectHeaders");
111         }
112         if (CDS) {
113             args.add("-Xshare:on");
114         } else {
115             args.add("-Xshare:off");
116             args.add("-XX:CompressedClassSpaceBaseAddress=" + forceBaseString);
117         }
118         args.add(AccessZeroNKlassHitsProtectionZone.class.getName());
119         args.add("runwb");
120         ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(args.toArray(new String[0]));
121 
122         OutputAnalyzer output = new OutputAnalyzer(pb.start());
123         output.reportDiagnosticSummary();
124         return output;
125     }
126 
127     private static void run_test(boolean COH, boolean CDS) throws IOException, SkippedException {
128         // Notes:
129         // We want to enforce zero-based encoding, to test the protection page in that case. For zero-based encoding,
130         // protection page is at address zero, no need to test that.
131         // If CDS is on, we never use zero-based, forceBase is ignored.
132         // If CDS is off, we use forceBase to (somewhat) reliably force the encoding base to beyond 32G,
133         // in order to prevent zero-based encoding. Since that may fail, we try several times.
134         OutputAnalyzer output = null;
135         long forceBase = -1;
136         if (CDS) {
137             output = run_test(COH, CDS, "");
138             // Not all distributions build COH archives. We tolerate that.
139             if (COH) {
140                 String s = output.firstMatch("Specified shared archive file not found .*coh.jsa");
141                 if (s != null) {
142                     throw new SkippedException("Failed to find COH archive, was it not built? Skipping test.");
143                 }
144             }
145         } else {
146             long g4 = 0x1_0000_0000L;
147             long start = g4 * 8; // 32g
148             long step = g4;
149             long end = start + step * 16;
150             for (forceBase = start; forceBase < end; forceBase += step) {
151                 String thisBaseString = String.format("0x%016X", forceBase).toLowerCase();
152                 output = run_test(COH, CDS, thisBaseString);
153                 if (output.contains("CompressedClassSpaceBaseAddress=" + thisBaseString + " given, but reserving class space failed.") ||
154                     output.matches ("CompressedClassSpaceBaseAddress=" + thisBaseString + " given with shift .*, cannot be used to encode class pointers")) {
155                     // possible output:
156                     //     CompressedClassSpaceBaseAddress=0x0000000c00000000 given, but reserving class space failed.
157                     //     CompressedClassSpaceBaseAddress=0x0000000d00000000 given with shift 6, cannot be used to encode class pointers
158                     // try next one
159                 } else if (output.contains("Successfully forced class space address to " + thisBaseString)) {
160                     break;
161                 } else {
162                     throw new RuntimeException("Unexpected");
163                 }
164             }
165             if (forceBase >= end) {
166                 throw new SkippedException("Failed to force ccs to any of the given bases. Skipping test.");
167             }
168         }
169 
170         // Parse the encoding base from the output. In case of CDS, it depends on ASLR. Even in case of CDS=off, we want
171         // to double-check it is the force address.
172         String nKlassBaseString = output.firstMatch("Narrow klass base: 0x([0-9a-f]+)", 1);
173         if (nKlassBaseString == null) {
174             throw new RuntimeException("did not find Narrow klass base in log output");
175         }
176         long nKlassBase = Long.valueOf(nKlassBaseString, 16);
177 
178         if (!CDS && nKlassBase != forceBase) {
179             throw new RuntimeException("Weird - we should have mapped at force base"); // .. otherwise we would have skipped out above
180         }
181         if (nKlassBase == 0) {
182             throw new RuntimeException("We should not be running zero-based at this point.");
183         }
184 
185         // Calculate the expected crash address pattern. The precise crash address is unknown, but should be located
186         // in the lower part of the guard page following the encoding base. We just accept any address matching the
187         // upper 52 digits (leaving 4K = 12 bits = 4 nibbles of wiggle room)
188         String expectedCrashAddressString = nKlassBaseString.substring(0, nKlassBaseString.length() - 3);
189 
190         // output from whitebox function: Klass* should point to encoding base
191         output.shouldMatch("WB_DecodeNKlassAndAccessKlass: nk 0 k 0x" + nKlassBaseString);
192 
193         // Then, we should have crashed
194         output.shouldNotHaveExitValue(0);
195         output.shouldContain("# A fatal error has been detected");
196 
197         // The hs-err file should contain a reference to the nKlass protection zone, like this:
198         // "RDI=0x0000000800000000 points into nKlass protection zone"
199         File hsErrFile = HsErrFileUtils.openHsErrFileFromOutput(output);
200 
201         ArrayList<Pattern> hsErrPatternList = new ArrayList<>();
202         hsErrPatternList.add(Pattern.compile(".*(SIGBUS|SIGSEGV|EXCEPTION_ACCESS_VIOLATION).*"));
203 
204         hsErrPatternList.add(Pattern.compile(".*siginfo:.*" + expectedCrashAddressString + ".*"));
205         hsErrPatternList.add(Pattern.compile(".*" + expectedCrashAddressString + ".*points into nKlass protection zone.*"));
206         Pattern[] hsErrPattern = hsErrPatternList.toArray(new Pattern[0]);
207         HsErrFileUtils.checkHsErrFileContent(hsErrFile, hsErrPattern, true);
208     }
209 
210     enum Argument { runwb, no_coh_no_cds, no_coh_cds, coh_no_cds, coh_cds };
211     public static void main(String[] args) throws Exception {
212         if (args.length != 1) {
213             throw new RuntimeException("Expecting one argument");
214         }
215         Argument arg = Argument.valueOf(args[0]);
216         System.out.println(arg);
217         switch (arg) {
218             case runwb -> WhiteBox.getWhiteBox().decodeNKlassAndAccessKlass(0);
219             case no_coh_no_cds -> run_test(false, false);
220             case no_coh_cds -> run_test(false, true);
221             case coh_no_cds -> run_test(true, false);
222             case coh_cds -> run_test(true, true);
223         }
224     }
225 }