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 // TODO 8348568 Re-enable
222 // case coh_no_cds -> run_test(true, false);
223 // case coh_cds -> run_test(true, true);
224 }
225 }
226 }