Skip to content

Commit 4709b7c

Browse files
committed
[GR-51085] Prevent embedding full build path in Native Image generated shared libs.
PullRequest: graal/16746
2 parents 8b1c0a3 + 100777d commit 4709b7c

File tree

6 files changed

+215
-30
lines changed

6 files changed

+215
-30
lines changed

sdk/mx.sdk/mx_sdk_vm_impl.py

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,10 +1224,7 @@ def is_ee_supported(self):
12241224
def is_pgo_supported(self):
12251225
return self.is_ee_supported()
12261226

1227-
search_tool = 'strings'
1228-
has_search_tool = shutil.which(search_tool) is not None
1229-
1230-
def native_image(self, build_args, output_file, out=None, err=None, find_bad_strings=False):
1227+
def native_image(self, build_args, output_file, out=None, err=None):
12311228
assert self._svm_supported
12321229
stage1 = get_stage1_graalvm_distribution()
12331230
native_image_project_name = GraalVmLauncher.launcher_project_name(mx_sdk.LauncherConfig(mx.exe_suffix('native-image'), [], "", []), stage1=True)
@@ -1240,20 +1237,6 @@ def native_image(self, build_args, output_file, out=None, err=None, find_bad_str
12401237

12411238
mx.run(native_image_command, nonZeroIsFatal=True, out=out, err=err)
12421239

1243-
if find_bad_strings and not mx.is_windows():
1244-
if not self.__class__.has_search_tool:
1245-
mx.abort(f"Searching for strings requires '{self.__class__.search_tool}' executable.")
1246-
try:
1247-
strings_in_image = subprocess.check_output([self.__class__.search_tool, output_file], stderr=None).decode().strip().split('\n')
1248-
bad_strings = (output_directory, dirname(native_image_bin))
1249-
for entry in strings_in_image:
1250-
for bad_string in bad_strings:
1251-
if bad_string in entry:
1252-
mx.abort(f"Found forbidden string '{bad_string}' in native image {output_file}.")
1253-
1254-
except subprocess.CalledProcessError:
1255-
mx.abort(f"Using '{self.__class__.search_tool}' to search for strings in native image {output_file} failed.")
1256-
12571240
def is_debug_supported(self):
12581241
return self._debug_supported
12591242

@@ -2402,7 +2385,7 @@ def build(self):
24022385
mx.ensure_dir_exists(dirname(output_file))
24032386

24042387
# Disable build server (different Java properties on each build prevent server reuse)
2405-
self.svm_support.native_image(build_args, output_file, find_bad_strings=True)
2388+
self.svm_support.native_image(build_args, output_file)
24062389

24072390
with open(self._get_command_file(), 'w') as f:
24082391
f.writelines((l + os.linesep for l in build_args))
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package org.graalvm.nativeimage.test;
42+
43+
import java.io.IOException;
44+
import java.nio.file.Files;
45+
import java.nio.file.Path;
46+
import java.util.ArrayList;
47+
import java.util.List;
48+
49+
/**
50+
* Utility for scanning a binary file to find matches for given strings.
51+
*
52+
* This is a partial port of the {@code AbsPathInImage.java} OpenJDK test.
53+
*/
54+
public class FindPathsInBinary {
55+
56+
private static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase().contains("windows");
57+
58+
/**
59+
* Searches the binary file in {@code args[0]} for the patterns in
60+
* {@code args[1], args[2], ...}. A line is printed to stdout for each match found.
61+
*/
62+
public static void main(String[] args) throws IOException {
63+
if (args.length < 2) {
64+
System.err.printf("Usage: %s <file to scan> <patterns to find>%n", FindPathsInBinary.class.getName());
65+
System.exit(-1);
66+
}
67+
Path fileToScan = Path.of(args[0]);
68+
List<byte[]> searchPatterns = new ArrayList<>();
69+
for (int i = 1; i < args.length; i++) {
70+
expandPatterns(searchPatterns, args[i]);
71+
}
72+
73+
scanFile(fileToScan, searchPatterns);
74+
}
75+
76+
/**
77+
* Add path pattern to list of patterns to search for. Create all possible variants depending on
78+
* platform.
79+
*/
80+
private static void expandPatterns(List<byte[]> searchPatterns, String pattern) {
81+
if (IS_WINDOWS) {
82+
String forward = pattern.replace('\\', '/');
83+
String back = pattern.replace('/', '\\');
84+
if (pattern.charAt(1) == ':') {
85+
String forwardUpper = String.valueOf(pattern.charAt(0)).toUpperCase() + forward.substring(1);
86+
String forwardLower = String.valueOf(pattern.charAt(0)).toLowerCase() + forward.substring(1);
87+
String backUpper = String.valueOf(pattern.charAt(0)).toUpperCase() + back.substring(1);
88+
String backLower = String.valueOf(pattern.charAt(0)).toLowerCase() + back.substring(1);
89+
searchPatterns.add(forwardUpper.getBytes());
90+
searchPatterns.add(forwardLower.getBytes());
91+
searchPatterns.add(backUpper.getBytes());
92+
searchPatterns.add(backLower.getBytes());
93+
} else {
94+
searchPatterns.add(forward.getBytes());
95+
searchPatterns.add(back.getBytes());
96+
}
97+
} else {
98+
searchPatterns.add(pattern.getBytes());
99+
}
100+
}
101+
102+
private static void scanFile(Path file, List<byte[]> searchPatterns) throws IOException {
103+
List<String> matches = scanBytes(Files.readAllBytes(file), searchPatterns);
104+
if (matches.size() > 0) {
105+
for (String match : matches) {
106+
System.out.println(match);
107+
}
108+
}
109+
}
110+
111+
private static List<String> scanBytes(byte[] data, List<byte[]> searchPatterns) {
112+
List<String> matches = new ArrayList<>();
113+
for (int i = 0; i < data.length; i++) {
114+
for (byte[] searchPattern : searchPatterns) {
115+
boolean found = true;
116+
for (int j = 0; j < searchPattern.length; j++) {
117+
if ((i + j >= data.length || data[i + j] != searchPattern[j])) {
118+
found = false;
119+
break;
120+
}
121+
}
122+
if (found) {
123+
int cs = charsStart(data, i);
124+
int co = charsOffset(data, i, searchPattern.length);
125+
matches.add(new String(data, cs, co));
126+
// No need to search the same string for multiple patterns
127+
break;
128+
}
129+
}
130+
}
131+
return matches;
132+
}
133+
134+
private static int charsStart(byte[] data, int startIndex) {
135+
int index = startIndex;
136+
while (--index > 0) {
137+
byte datum = data[index];
138+
if (datum < 32 || datum > 126) {
139+
break;
140+
}
141+
}
142+
return index + 1;
143+
}
144+
145+
private static int charsOffset(byte[] data, int startIndex, int startOffset) {
146+
int offset = startOffset;
147+
while (startIndex + ++offset < data.length) {
148+
byte datum = data[startIndex + offset];
149+
if (datum < 32 || datum > 126) {
150+
break;
151+
}
152+
}
153+
return offset;
154+
}
155+
}

substratevm/mx.substratevm/mx_substratevm.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1275,6 +1275,34 @@ def _native_image_launcher_extra_jvm_args():
12751275
'sdk:JNIUTILS',
12761276
'substratevm:GRAAL_HOTSPOT_LIBRARY']
12771277

1278+
def allow_build_path_in_libgraal():
1279+
"""
1280+
Determines if the ALLOW_ABSOLUTE_PATHS_IN_OUTPUT env var is any other value than ``false``.
1281+
"""
1282+
return mx.get_env('ALLOW_ABSOLUTE_PATHS_IN_OUTPUT', None) != 'false'
1283+
1284+
def prevent_build_path_in_libgraal():
1285+
"""
1286+
If `allow_build_path_in_libgraal() == False`, returns linker
1287+
options to prevent the build path from showing up in a string in libgraal.
1288+
"""
1289+
if not allow_build_path_in_libgraal():
1290+
if mx.is_linux():
1291+
return ['-H:NativeLinkerOption=-Wl,-soname=libjvmcicompiler.so']
1292+
if mx.is_darwin():
1293+
return [
1294+
'-H:NativeLinkerOption=-Wl,-install_name,@rpath/libjvmcicompiler.dylib',
1295+
1296+
# native-image doesn't support generating debug info on Darwin
1297+
# but the helper C libraries are built with debug info which
1298+
# can include the build path. Use the -S to strip the debug info
1299+
# info from the helper C libraries to avoid these paths.
1300+
'-H:NativeLinkerOption=-Wl,-S'
1301+
]
1302+
if mx.is_windows():
1303+
return ['-H:NativeLinkerOption=-pdbaltpath:%_PDB%']
1304+
return []
1305+
12781306
libgraal_build_args = [
12791307
## Pass via JVM args opening up of packages needed for image builder early on
12801308
'-J--add-exports=jdk.graal.compiler/jdk.graal.compiler.hotspot=ALL-UNNAMED',
@@ -1330,12 +1358,12 @@ def _native_image_launcher_extra_jvm_args():
13301358
# No need for container support in libgraal as HotSpot already takes care of it
13311359
'-H:-UseContainerSupport',
13321360
] + ([
1333-
# Force page size to support libgraal on AArch64 machines with a page size up to 64K.
1334-
'-H:PageSize=64K'
1361+
# Force page size to support libgraal on AArch64 machines with a page size up to 64K.
1362+
'-H:PageSize=64K'
13351363
] if mx.get_arch() == 'aarch64' else []) + ([
1336-
# Build libgraal with 'Full RELRO' to prevent GOT overwriting exploits on Linux (GR-46838)
1337-
'-H:NativeLinkerOption=-Wl,-z,relro,-z,now',
1338-
] if mx.is_linux() else []))
1364+
# Build libgraal with 'Full RELRO' to prevent GOT overwriting exploits on Linux (GR-46838)
1365+
'-H:NativeLinkerOption=-Wl,-z,relro,-z,now',
1366+
] if mx.is_linux() else [])) + prevent_build_path_in_libgraal()
13391367

13401368
libgraal = mx_sdk_vm.GraalVmJreComponent(
13411369
suite=suite,

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/CCLinkerInvocation.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -322,11 +322,6 @@ protected void setOutputKind(List<String> cmd) {
322322
break;
323323
case SHARED_LIBRARY:
324324
cmd.add("-shared");
325-
/*
326-
* Ensure shared library name in image does not use fully qualified build-path
327-
* (GR-46837)
328-
*/
329-
cmd.add("-Wl,-soname=" + outputFile.getFileName());
330325
break;
331326
default:
332327
VMError.shouldNotReachHereUnexpectedInput(imageKind); // ExcludeFromJacocoGeneratedReport

vm/ci/ci_common/libgraal.jsonnet

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,12 @@ local utils = import '../../../ci/ci_common/common-utils.libsonnet';
4040
},
4141
libgraal_compiler_zgc:: self.libgraal_compiler_base(extra_vm_args=['-XX:+UseZGC']),
4242
# enable economy mode building with the -Ob flag
43-
libgraal_compiler_quickbuild:: self.libgraal_compiler_base(quickbuild_args=['-Ob']),
43+
libgraal_compiler_quickbuild:: self.libgraal_compiler_base(quickbuild_args=['-Ob']) + {
44+
environment+: {
45+
# Exercise support for preventing build paths being embedded in libgraal.
46+
ALLOW_ABSOLUTE_PATHS_IN_OUTPUT: 'false'
47+
}
48+
},
4449

4550
libgraal_truffle_base(quickbuild_args=[], extra_vm_args=[], coverage=false): self.libgraal_build(['-J-esa', '-J-ea', '-esa', '-ea'] + quickbuild_args) + {
4651
environment+: {

vm/mx.vm/mx_vm_gate.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,23 @@ def append_extra_logs():
128128
for extra_log_file in extra_log_files:
129129
remove(extra_log_file)
130130

131+
def _test_libgraal_check_build_path(libgraal_location):
132+
"""
133+
If ``mx_substratevm.allow_build_path_in_libgraal()`` is False, tests that libgraal does not contain
134+
strings whose prefix is the absolute path of the SDK suite.
135+
"""
136+
import mx_compiler
137+
import mx_substratevm
138+
import subprocess
139+
if not mx_substratevm.allow_build_path_in_libgraal():
140+
sdk_suite_dir = mx.suite('sdk').dir
141+
tool_path = join(sdk_suite_dir, 'src/org.graalvm.nativeimage.test/src/org/graalvm/nativeimage/test/FindPathsInBinary.java'.replace('/', os.sep))
142+
cmd = [mx_compiler.jdk.java, tool_path, libgraal_location, sdk_suite_dir]
143+
mx.logv(' '.join(cmd))
144+
matches = subprocess.check_output(cmd, universal_newlines=True).strip()
145+
if len(matches) != 0:
146+
mx.abort(f"Found strings in {libgraal_location} with illegal prefix \"{sdk_suite_dir}\":\n{matches}\n\nRe-run: {' '.join(cmd)}")
147+
131148
def _test_libgraal_basic(extra_vm_arguments, libgraal_location):
132149
"""
133150
Tests basic libgraal execution by running CountUppercase, ensuring it has a 0 exit code
@@ -514,6 +531,8 @@ def gate_body(args, tasks):
514531
if args.extra_vm_argument:
515532
extra_vm_arguments += args.extra_vm_argument
516533

534+
with Task('LibGraal Compiler:CheckBuildPaths', tasks, tags=[VmGateTasks.libgraal], report='compiler') as t:
535+
if t: _test_libgraal_check_build_path(libgraal_location)
517536
with Task('LibGraal Compiler:Basic', tasks, tags=[VmGateTasks.libgraal], report='compiler') as t:
518537
if t: _test_libgraal_basic(extra_vm_arguments, libgraal_location)
519538
with Task('LibGraal Compiler:FatalErrorHandling', tasks, tags=[VmGateTasks.libgraal], report='compiler') as t:

0 commit comments

Comments
 (0)