This is an automated email from the ASF dual-hosted git repository. shaojunwang pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-teaclave-java-tee-sdk.git
commit edc95be328bb832a63c72c47c0365f6dbfaa6b5c Author: cengfeng.lzy <[email protected]> AuthorDate: Wed Mar 16 18:01:13 2022 +0800 [Enc] Add native image level framework Summary: Create native image level framework that provides the following functions: - Library invocation entrypointss that follow the SVM protocals - Data structure definition - Data serialization and converting from WordBase to Object base - Service state maintainance Test Plan: all tests pass Reviewers: lei.yul, jeffery.wsj, sanhong.lsh Issue: https://aone.alibaba-inc.com/task/40646790 CR: https://code.aone.alibaba-inc.com/java-tee/JavaEnclave/codereview/8303453 --- .gitignore | 19 ++ build.sh | 20 +- .../common/annotations/EnclaveMethod.java | 45 ++++ .../common/annotations/EnclaveService.java | 52 ++++ sdk/enclave/pom.xml | 107 ++++++++- .../enclave/EnclaveEntry.java | 79 ++++++ .../enclave/EnclaveFeature.java | 130 ++++++++++ .../enclave/EnclavePrologue.java | 22 ++ .../enclave/InvocationWrapper.java | 69 ++++++ .../enclave/c/EnclaveEnvironment.java | 126 ++++++++++ .../META-INF/native-image/reflect-config.json | 20 ++ .../native-image/serialization-config.json | 44 ++++ .../src/main/resources/native/enc_environment.h | 17 ++ .../enclave/AroundNativeTest.java | 49 ++++ .../enclave/EnclaveTestHelper.java | 107 +++++++++ .../enclave/NativeImageTest.java | 264 +++++++++++++++++++++ .../enclave/NativeImageTestable.java | 12 + .../enclave/RunWithNativeImageTest.java | 166 +++++++++++++ .../enclave/SVMSimpleEnclaveCallTest.java | 123 ++++++++++ .../confidentialcomputing/enclave/TestTarget.java | 12 + .../framework/ServiceMethodInvokerTest.java | 8 + .../enclave/framework/ServiceOperationTest.java | 8 + .../enclave/testservice/MathService.java | 3 + .../enclave/testservice/Point.java | 11 +- ...nfidentialcomputing_enclave_EnclaveTestHelper.h | 53 +++++ .../test/resources/native/enc_invoke_entry_test.c | 76 ++++++ tools/cicd/Dockerfile | 5 +- tools/cicd/make.sh | 7 +- 28 files changed, 1633 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8e5dc50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# Compiled class file +*.class + +# Log file +*.log + + +# virtual machine crash logs +hs_err_pid* + +# IDE config +*.iml +.idea/ +.classpath +.project +.settings/ + +# Maven compiled directory +target/ diff --git a/build.sh b/build.sh index f9c39ec..b4dbae1 100644 --- a/build.sh +++ b/build.sh @@ -8,23 +8,19 @@ cd "${SHELL_FOLDER}" # workspace dir is the same as build.sh path location. WORKDIR="$PWD" -# The necessary GraalVM jars are compiled from [email protected]:graal/SGXGraalVM.git. When the patches are -# accepted by the community, these jars will be gradually replaced by the official jars. +# The necessary GraalVM jars are compiled from [email protected]:graal/SGXGraalVM.git. +# When the patches are accepted by the community, these jars will be gradually replaced by the official jars. VERSION="enclave-22.0.0" mkdir jartmp pushd jartmp > /dev/null -wget https://graal.oss-cn-beijing.aliyuncs.com/graal-enclave/JDK11-22.0.0/graal-processor-22.0.0.jar wget https://graal.oss-cn-beijing.aliyuncs.com/graal-enclave/JDK11-22.0.0/graal-sdk-enclave-22.0.0.jar -wget https://graal.oss-cn-beijing.aliyuncs.com/graal-enclave/JDK11-22.0.0/native-image-base-enclave-22.0.0.jar -wget https://graal.oss-cn-beijing.aliyuncs.com/graal-enclave/JDK11-22.0.0/objectfile-enclave-22.0.0.jar -wget https://graal.oss-cn-beijing.aliyuncs.com/graal-enclave/JDK11-22.0.0/pointsto-enclave-22.0.0.jar -wget https://graal.oss-cn-beijing.aliyuncs.com/graal-enclave/JDK11-22.0.0/svm-enclave-22.0.0.jar -mvn install:install-file -Dfile=graal-processor-22.0.0.jar -DgroupId=org.graalvm.compiler -DartifactId=graal-processor -Dversion=$VERSION -Dpackaging=jar + +mvn install:install-file -Dfile=$GRAALVM_HOME/lib/graal/graal-processor.jar -DgroupId=org.graalvm.compiler -DartifactId=graal-processor -Dversion=$VERSION -Dpackaging=jar mvn install:install-file -Dfile=graal-sdk-enclave-22.0.0.jar -DgroupId=org.graalvm.sdk -DartifactId=graal-sdk -Dversion=$VERSION -Dpackaging=jar -mvn install:install-file -Dfile=svm-enclave-22.0.0.jar -DgroupId=org.graalvm.nativeimage -DartifactId=svm -Dversion=$VERSION -Dpackaging=jar -mvn install:install-file -Dfile=objectfile-enclave-22.0.0.jar -DgroupId=org.graalvm.nativeimage -DartifactId=objectfile -Dversion=$VERSION -Dpackaging=jar -mvn install:install-file -Dfile=pointsto-enclave-22.0.0.jar -DgroupId=org.graalvm.nativeimage -DartifactId=pointsto -Dversion=$VERSION -Dpackaging=jar -mvn install:install-file -Dfile=native-image-base-enclave-22.0.0.jar -DgroupId=org.graalvm.nativeimage -DartifactId=native-image-base -Dversion=$VERSION -Dpackaging=jar +mvn install:install-file -Dfile=$GRAALVM_HOME/lib/svm/builder/svm.jar -DgroupId=org.graalvm.nativeimage -DartifactId=svm -Dversion=$VERSION -Dpackaging=jar +mvn install:install-file -Dfile=$GRAALVM_HOME/lib/svm/builder/objectfile.jar -DgroupId=org.graalvm.nativeimage -DartifactId=objectfile -Dversion=$VERSION -Dpackaging=jar +mvn install:install-file -Dfile=$GRAALVM_HOME/lib/svm/builder/pointsto.jar -DgroupId=org.graalvm.nativeimage -DartifactId=pointsto -Dversion=$VERSION -Dpackaging=jar +mvn install:install-file -Dfile=$GRAALVM_HOME/lib/svm/builder/native-image-base.jar -DgroupId=org.graalvm.nativeimage -DartifactId=native-image-base -Dversion=$VERSION -Dpackaging=jar popd > /dev/null rm -rf jartmp diff --git a/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/annotations/EnclaveMethod.java b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/annotations/EnclaveMethod.java new file mode 100644 index 0000000..7a5fa94 --- /dev/null +++ b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/annotations/EnclaveMethod.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021, Alibaba Group Holding Limited. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.alibaba.confidentialcomputing.common.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Mark a method is running inside the Enclave, but can be directly invoked from the Host. + * So its parameters and returned value types are required to get serialized. + * If a service provider's interface has been marked with {@link EnclaveService}, there is no need to mark its methods with + * this annotation. + * <p> + * Please refer {@link EnclaveService} for the details about automatic serialization type registration in native image scenario. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface EnclaveMethod { +} diff --git a/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/annotations/EnclaveService.java b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/annotations/EnclaveService.java new file mode 100644 index 0000000..0b193a8 --- /dev/null +++ b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/annotations/EnclaveService.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021, Alibaba Group Holding Limited. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.alibaba.confidentialcomputing.common.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Mark an interface is used as SPI service for Enclave. + * All its providers' public non-static methods are {@link EnclaveMethod}s. + * When the TEE side is native image (SVM), {@link EnclaveMethod}'s parameter and returned types may be automatically + * registered for reflection and serialization at native image build time. But a class can be automatically registered + * for serialization only when it: + * <ul> + * <li>Doesn't have {@code writeObject} method. The {@code writeObject} customizes the serialization rule, preventing + * native image generator automatically inferring the associated serialization types. A typical example is {@link java.util.ArrayList}. </li> + * <li>Is effective final, i.e. doesn't have subclasses.</li> + * </ul> + * Native image generator issues a warning when the parameter and returned value type don't obey the above two rules. + * To solve the problem, user can choose another class for replacement if possible, e.g. using array instead of {@link java.util.ArrayList}; + * or generating the serialization configuration by <a href="https://github.com/ziyilin/ziyi-forked-graal/blob/master/docs/reference-manual/native-image/Agent.md">native-image-agent</a>. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface EnclaveService { +} diff --git a/sdk/enclave/pom.xml b/sdk/enclave/pom.xml index 8c79f06..8b66556 100644 --- a/sdk/enclave/pom.xml +++ b/sdk/enclave/pom.xml @@ -12,6 +12,57 @@ <packaging>jar</packaging> <name>JavaEnclave-Enclave</name> <url></url> + <properties> + <graal.version>enclave-22.0.0</graal.version> + <svm.maven.version>0.9.10</svm.maven.version> + </properties> + <profiles> + <profile> + <id>native</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>${svm.maven.version}</version> + <extensions>true</extensions> + <executions> + <execution> + <id>build-native</id> + <goals> + <goal>build</goal> + </goals> + <configuration> + <imageName>libsvm_enclave_sdk</imageName> + <buildArgs> + <buildArg>--shared</buildArg> + <buildArg>--no-fallback</buildArg> + <buildArg>-H:+RunInEnclave</buildArg> + <buildArg>-H:OutputRelocatableImage=.</buildArg> + <buildArg>-H:Path=svm-output</buildArg> + </buildArgs> + </configuration> + <phase>package</phase> + </execution> + <execution> + <id>test-native</id> + <goals> + <goal>test</goal> + </goals> + <configuration> + <buildArgs> + <buildArg>--no-fallback</buildArg> + <buildArg>-H:+RunInEnclave</buildArg> + </buildArgs> + </configuration> + <phase>test</phase> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> <build> <plugins> <plugin> @@ -27,6 +78,17 @@ <arg>--add-exports</arg> <arg>jdk.internal.vm.ci/jdk.vm.ci.meta=ALL-UNNAMED</arg> </compilerArgs> + <!--OptionProcessor can automatically generate OptionDescriptor classes at javac time--> + <annotationProcessorPaths> + <path> + <groupId>org.graalvm.compiler</groupId> + <artifactId>graal-processor</artifactId> + <version>${graal.version}</version> + </path> + </annotationProcessorPaths> + <annotationProcessors> + <annotationProcessor>org.graalvm.compiler.options.processor.OptionProcessor</annotationProcessor> + </annotationProcessors> </configuration> </plugin> <plugin> @@ -93,16 +155,47 @@ </execution> </executions> </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <version>3.0.0</version> + <executions> + <execution> + <id>preTest</id> + <phase>test-compile</phase> + <goals> + <goal>java</goal> + </goals> + <configuration> + <mainClass>com.alibaba.confidentialcomputing.enclave.AroundNativeTest$PreTest</mainClass> + <classpathScope>test</classpathScope> + </configuration> + </execution> + <execution> + <id>postTest</id> + <phase>test</phase> + <goals> + <goal>java</goal> + </goals> + <configuration> + <mainClass>com.alibaba.confidentialcomputing.enclave.AroundNativeTest$PostTest</mainClass> + <classpathScope>test</classpathScope> + </configuration> + </execution> + </executions> + </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> - <version>2.22.1</version> + <version>3.0.0-M5</version> + <configuration> + <environmentVariables> + <LD_LIBRARY_PATH>/tmp/javaenclavetest-native-libs</LD_LIBRARY_PATH> + </environmentVariables> + </configuration> </plugin> </plugins> </build> - <properties> - <graal.version>enclave-22.0.0</graal.version> - </properties> <dependencies> <dependency> <groupId>org.graalvm.sdk</groupId> @@ -129,6 +222,12 @@ <artifactId>junit-jupiter-engine</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>junit-platform-native</artifactId> + <version>${svm.maven.version}</version> + <scope>test</scope> + </dependency> <dependency> <groupId>com.alibaba.confidentialcomputing</groupId> <artifactId>common</artifactId> diff --git a/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/EnclaveEntry.java b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/EnclaveEntry.java new file mode 100644 index 0000000..04d091c --- /dev/null +++ b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/EnclaveEntry.java @@ -0,0 +1,79 @@ +package com.alibaba.confidentialcomputing.enclave; + +import com.alibaba.confidentialcomputing.enclave.c.EnclaveEnvironment.CallBacks; +import com.alibaba.confidentialcomputing.enclave.c.EnclaveEnvironment.EncData; +import com.alibaba.confidentialcomputing.enclave.framework.LoadServiceInvoker; +import com.alibaba.confidentialcomputing.enclave.framework.ServiceMethodInvoker; +import com.alibaba.confidentialcomputing.enclave.framework.UnloadServiceInvoker; +import com.oracle.svm.core.c.function.CEntryPointOptions; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Isolate; +import org.graalvm.nativeimage.c.function.CEntryPoint; +import org.graalvm.nativeimage.c.type.CTypeConversion; + +/** + * This class defines the entry points for native image (shared library) deployed in TEE enclave. + */ +public class EnclaveEntry { + private static CallBacks callBackMethods; + + @SuppressWarnings("unused") + @CEntryPoint(name = "java_loadservice_invoke") + @CEntryPointOptions(prologue = EnclavePrologue.class) + public static int loadService(Isolate isolate, EncData input, EncData result, CallBacks callBacks) { + callBackMethods = callBacks; + int retCode = 0; + try { + InvocationWrapper.invoke(input, result, callBacks, ImageSingletons.lookup(LoadServiceInvoker.class)); + } catch (Throwable t) { + retCode = handleFrameworkException(t); + } + return retCode; + } + + @SuppressWarnings("unused") + @CEntryPoint(name = "java_unloadservice_invoke") + @CEntryPointOptions(prologue = EnclavePrologue.class) + public static int unloadService(Isolate isolate, EncData input, EncData result, CallBacks callBacks) { + callBackMethods = callBacks; + int retCode = 0; + try { + InvocationWrapper.invoke(input, result, callBacks, ImageSingletons.lookup(UnloadServiceInvoker.class)); + } catch (Throwable t) { + retCode = handleFrameworkException(t); + } + return retCode; + } + + @SuppressWarnings("unused") + @CEntryPoint(name = "java_enclave_invoke") + @CEntryPointOptions(prologue = EnclavePrologue.class) + public static int javaEnclaveInvoke(Isolate isolate, EncData input, EncData result, CallBacks callBacks) { + callBackMethods = callBacks; + int retCode = 0; + try { + InvocationWrapper.invoke(input, result, callBacks, ImageSingletons.lookup(ServiceMethodInvoker.class)); + } catch (Throwable t) { + retCode = handleFrameworkException(t); + } + return retCode; + } + + private static int handleFrameworkException(Throwable t) { + if (callBackMethods.isNonNull() && callBackMethods.getExceptionHandler().isNonNull()) { + StringBuilder stacktraceSB = new StringBuilder(); + for (StackTraceElement se : t.getStackTrace()) { + stacktraceSB.append(se.toString()).append("\n"); + } + try ( + CTypeConversion.CCharPointerHolder stacktrace = CTypeConversion.toCString(stacktraceSB.toString()); + CTypeConversion.CCharPointerHolder errMsg = CTypeConversion.toCString(t.getMessage()); + CTypeConversion.CCharPointerHolder exception = CTypeConversion.toCString(t.getClass().toString())) { + callBackMethods.getExceptionHandler().invoke(errMsg.get(), stacktrace.get(), exception.get()); + } + } else { + t.printStackTrace(); + } + return 1; + } +} diff --git a/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/EnclaveFeature.java b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/EnclaveFeature.java new file mode 100644 index 0000000..fe36075 --- /dev/null +++ b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/EnclaveFeature.java @@ -0,0 +1,130 @@ +package com.alibaba.confidentialcomputing.enclave; + +import com.alibaba.confidentialcomputing.common.annotations.EnclaveMethod; +import com.alibaba.confidentialcomputing.common.annotations.EnclaveService; +import com.alibaba.confidentialcomputing.common.exception.ConfidentialComputingException; +import com.alibaba.confidentialcomputing.enclave.framework.LoadServiceInvoker; +import com.alibaba.confidentialcomputing.enclave.framework.ServiceMethodInvoker; +import com.alibaba.confidentialcomputing.enclave.framework.UnloadServiceInvoker; +import com.oracle.svm.core.annotate.AutomaticFeature; +import com.oracle.svm.core.jdk.resources.NativeImageResourceFileSystemUtil; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.hosted.ServiceLoaderFeature; +import com.oracle.svm.reflect.hosted.ReflectionFeature; +import com.oracle.svm.reflect.serialize.hosted.SerializationFeature; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeReflection; +import org.graalvm.nativeimage.hosted.RuntimeSerialization; +import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +@AutomaticFeature +public class EnclaveFeature implements Feature { + + private ImageClassLoader imageClassLoader; + private final Map<Class<?>, Boolean> serializationCandidateTypes = new HashMap<>(); + private final Map<Class<?>, Boolean> reflectionCandidateTypes = new HashMap<>(); + private final Map<Method, Boolean> reflectionCandidateMethods = new HashMap<>(); + + @Override + public List<Class<? extends Feature>> getRequiredFeatures() { + return Arrays.asList(ReflectionFeature.class, SerializationFeature.class, ServiceLoaderFeature.class); + } + + @Override + public void duringSetup(DuringSetupAccess access) { + ImageSingletons.add(ServiceMethodInvoker.class, new ServiceMethodInvoker()); + ImageSingletons.add(LoadServiceInvoker.class, new LoadServiceInvoker()); + ImageSingletons.add(UnloadServiceInvoker.class, new UnloadServiceInvoker()); + ImageSingletons.lookup(RuntimeClassInitializationSupport.class).initializeAtBuildTime("com.alibaba.confidentialcomputing.enclave.EnclavePrologue", + "Prologue class should be initialize at build time."); + + FeatureImpl.DuringSetupAccessImpl config = (FeatureImpl.DuringSetupAccessImpl) access; + RuntimeSerialization.register(ConfidentialComputingException.class, RuntimeException.class, + ReflectiveOperationException.class, ClassNotFoundException.class); + RuntimeSerialization.registerAllAssociatedClasses(Collections.EMPTY_LIST.getClass()); + imageClassLoader = config.getImageClassLoader(); + } + + /** + * Collect reflection and serialization configurations from {@link EnclaveService} marked interfaces. + */ + @Override + public void duringAnalysis(DuringAnalysisAccess access) { + List<Class<?>> enclaveServices = imageClassLoader.findAnnotatedClasses(EnclaveService.class, true); + enclaveServices.forEach(serviceClazz -> { + reflectionCandidateTypes.putIfAbsent(serviceClazz, false); + byte[] serviceConfig = NativeImageResourceFileSystemUtil.getBytes("META-INF/services/" + serviceClazz.getName(), true); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(serviceConfig), StandardCharsets.UTF_8))) { + while (true) { + String line = reader.readLine(); + if (line == null) { + break; + } + Class<?> implementation = imageClassLoader.findClass(line).get(); + collectConfigs(implementation, Arrays.stream(implementation.getMethods()).filter(method -> + serviceClazz.isAssignableFrom(method.getDeclaringClass()) + ).collect(Collectors.toList())); + } + } catch (IOException e) { + VMError.shouldNotReachHere(e); + } + }); + List<Method> extraEnclaveMethods = imageClassLoader.findAnnotatedMethods(EnclaveMethod.class); + extraEnclaveMethods.forEach(method -> collectConfigs(method.getDeclaringClass(), List.of(method))); + + // Register all newly collected configures + if (registerCollectedConfigs()) { + access.requireAnalysisIteration(); + } + } + + + private void collectConfigs(Class<?> clazz, List<Method> methods) { + reflectionCandidateTypes.putIfAbsent(clazz, false); + methods.stream().filter(m -> !Modifier.isStatic(m.getModifiers())).forEach( + method -> { + for (Class<?> pType : method.getParameterTypes()) { + serializationCandidateTypes.putIfAbsent(pType, false); + } + serializationCandidateTypes.putIfAbsent(method.getReturnType(), false); + reflectionCandidateMethods.putIfAbsent(method, false); + } + ); + } + + private boolean registerCollectedConfigs() { + boolean registeredNewSerializations = registerCollectedConfigs(serializationCandidateTypes, RuntimeSerialization::registerAllAssociatedClasses); + boolean registeredNewReflectionTypes = registerCollectedConfigs(reflectionCandidateTypes, RuntimeReflection::register); + boolean registeredNewReflectionMethods = registerCollectedConfigs(reflectionCandidateMethods, RuntimeReflection::register); + return registeredNewSerializations || registeredNewReflectionTypes || registeredNewReflectionMethods; + } + + private <T> boolean registerCollectedConfigs(Map<T, Boolean> configs, Consumer<T> registerAction) { + boolean needRegisterNew = configs.entrySet().stream().anyMatch(entry -> !entry.getValue()); + configs.entrySet().stream().filter(entry -> !entry.getValue()).map(Map.Entry::getKey).forEach( + key -> { + registerAction.accept(key); + configs.put(key, true); + } + ); + return needRegisterNew; + } +} diff --git a/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/EnclavePrologue.java b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/EnclavePrologue.java new file mode 100644 index 0000000..596010a --- /dev/null +++ b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/EnclavePrologue.java @@ -0,0 +1,22 @@ +package com.alibaba.confidentialcomputing.enclave; + +import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.c.CGlobalData; +import com.oracle.svm.core.c.CGlobalDataFactory; +import com.oracle.svm.core.c.function.CEntryPointActions; +import com.oracle.svm.core.c.function.CEntryPointOptions; +import org.graalvm.nativeimage.Isolate; +import org.graalvm.nativeimage.c.type.CCharPointer; + +public class EnclavePrologue implements CEntryPointOptions.Prologue { + private static final CGlobalData<CCharPointer> errorMessage = CGlobalDataFactory.createCString("Failed to enter (or attach to) the global isolate in the current thread."); + + @Uninterruptible(reason = "prologue") + static void enter(Isolate isolate) { + + int code = CEntryPointActions.enterAttachThread(isolate, true); + if (code != 0) { + CEntryPointActions.failFatally(code, errorMessage.get()); + } + } +} diff --git a/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/InvocationWrapper.java b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/InvocationWrapper.java new file mode 100644 index 0000000..c732f7c --- /dev/null +++ b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/InvocationWrapper.java @@ -0,0 +1,69 @@ +package com.alibaba.confidentialcomputing.enclave; + +import com.alibaba.confidentialcomputing.common.EnclaveInvocationResult; +import com.alibaba.confidentialcomputing.common.SerializationHelper; +import com.alibaba.confidentialcomputing.common.exception.ConfidentialComputingException; +import com.alibaba.confidentialcomputing.enclave.c.EnclaveEnvironment.CallBacks; +import com.alibaba.confidentialcomputing.enclave.c.EnclaveEnvironment.EncData; +import com.alibaba.confidentialcomputing.enclave.framework.EnclaveMethodInvoker; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.nativeimage.c.type.CTypeConversion; +import org.graalvm.word.PointerBase; + +import java.io.IOException; + +/** + * This class deals with the whole method invocation process from native entry point to the actual Java invocation target. + * It is taken out in 3 steps:<p> + * <li>Transform the input data from C {@link PointerBase} to Java byte[] and then deserialize to get the actual input.</li> + * <li>Make the method invocation.</li> + * <li>Collect the returned value, serialize it to the Java byte[], and wrap it back to C {@link PointerBase}</li> + * </p> + */ +public class InvocationWrapper { + + public static <T> void invoke(EncData input, EncData result, CallBacks callBacks, EnclaveMethodInvoker<T> invoker) throws IOException { + byte[] data = transformInput(input); + EnclaveInvocationResult ret; + try { + ret = invoker.callMethod((T) SerializationHelper.deserialize(data)); + } catch (Throwable t) { + ret = new EnclaveInvocationResult(null, new ConfidentialComputingException(t)); + } + // Set method returned value to result parameter + wrapReturnValue(result, callBacks, ret); + } + + private static void wrapReturnValue(EncData result, CallBacks callBacks, EnclaveInvocationResult ret) throws IOException { + byte[] returnedValBytes; + returnedValBytes = SerializationHelper.serialize(ret); + int returnedValLen = returnedValBytes.length; + /* + * Data returned to C world should be allocated by the callback function in the C world. The memory hold by + * returnedValBytes shall be freed in the explicit finally clause. + */ + try (CTypeConversion.CCharPointerHolder byteHolder = CTypeConversion.toCBytes(returnedValBytes)) { + CCharPointer returned; + if (callBacks.isNonNull() && callBacks.getMemCpyCCharPointerFunctionPointer().isNonNull()) { + returned = callBacks.getMemCpyCCharPointerFunctionPointer().invoke(byteHolder.get(), returnedValLen); + + } else { + returned = byteHolder.get(); + System.out.println("Warning: Not calling call backs in native, there is memory leak risk."); + //throw new RuntimeException("Function pointer memcpy_char_pointer is not set"); + } + result.setData(returned); + result.setLen(returnedValLen); + } + } + + /** + * Transform input data from WordBase type to Java type. + */ + private static byte[] transformInput(EncData input) { + int len = input.getLen(); + byte[] data = new byte[len]; + CTypeConversion.asByteBuffer(input.getData(), len).get(data); + return data; + } +} diff --git a/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/c/EnclaveEnvironment.java b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/c/EnclaveEnvironment.java new file mode 100644 index 0000000..922ab29 --- /dev/null +++ b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/c/EnclaveEnvironment.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021, Alibaba Group Holding Limited. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.alibaba.confidentialcomputing.enclave.c; + +import com.oracle.svm.core.c.ProjectHeaderFile; +import com.oracle.svm.core.c.libc.TemporaryBuildDirectoryProvider; +import com.oracle.svm.core.util.VMError; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.c.CContext; +import org.graalvm.nativeimage.c.function.CFunctionPointer; +import org.graalvm.nativeimage.c.function.InvokeCFunctionPointer; +import org.graalvm.nativeimage.c.struct.CField; +import org.graalvm.nativeimage.c.struct.CStruct; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.word.PointerBase; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +@CContext(EnclaveEnvironment.EnclaveDirectives.class) +public class EnclaveEnvironment { + + static class EnclaveDirectives implements CContext.Directives { + + private static final String HEADER_FILE = "native/enc_environment.h"; + + @Override + public List<String> getHeaderFiles() { + // Register additional resolver to resolve header file from jar file as resource stream + ProjectHeaderFile.HeaderResolversRegistry.registerAdditionalResolver((projectName, headerFile) -> { + try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { + InputStream headerInputStream = EnclaveDirectives.class.getClassLoader().getResourceAsStream(HEADER_FILE); + if (headerInputStream == null) { + VMError.shouldNotReachHere("Can't find " + HEADER_FILE + " from classpath as resource"); + } + headerInputStream.transferTo(os); + Path headerPath = ImageSingletons.lookup(TemporaryBuildDirectoryProvider.class).getTemporaryBuildDirectory().resolve(HEADER_FILE); + Path nativeDir = headerPath.getParent(); + if (Files.notExists(nativeDir)) { + Files.createDirectory(nativeDir); + } + File tmpHeaderFile = Files.createFile(headerPath).toFile(); + try (FileOutputStream fos = new FileOutputStream(tmpHeaderFile)) { + fos.write(os.toByteArray()); + return new ProjectHeaderFile.HeaderSearchResult(Optional.of("\"" + tmpHeaderFile.getAbsolutePath() + "\""), tmpHeaderFile.getAbsolutePath()); + } + } catch (IOException e) { + VMError.shouldNotReachHere(e); + } + return null; + }); + return Collections.singletonList(ProjectHeaderFile.resolve("enclave", HEADER_FILE)); + } + } + + @CStruct("enc_data") + public interface EncData extends PointerBase { + @CField("data_len") + int getLen(); + + @CField("data_len") + void setLen(int len); + + @CField("data") + CCharPointer getData(); + + @CField("data") + void setData(CCharPointer data); + } + + @CStruct("callbacks") + public interface CallBacks extends PointerBase { + @CField("exception_handler") + ExceptionHandleFunctionPointer getExceptionHandler(); + + @CField("exception_handler") + void setExceptionHandler(ExceptionHandleFunctionPointer functionPointer); + + @CField("memcpy_char_pointer") + MemCpyCCharPointerFunctionPointer getMemCpyCCharPointerFunctionPointer(); + + @CField("memcpy_char_pointer") + void setMemCpyCCharPointerFunctionPointer(MemCpyCCharPointerFunctionPointer functionPointer); + } + + public interface ExceptionHandleFunctionPointer extends CFunctionPointer { + @InvokeCFunctionPointer + void invoke(CCharPointer errorMsg, CCharPointer stackTrace, CCharPointer exception); + } + + public interface MemCpyCCharPointerFunctionPointer extends CFunctionPointer { + @InvokeCFunctionPointer + CCharPointer invoke(CCharPointer source, int length); + } +} diff --git a/sdk/enclave/src/main/resources/META-INF/native-image/reflect-config.json b/sdk/enclave/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 0000000..1c5dc62 --- /dev/null +++ b/sdk/enclave/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,20 @@ +[ + { + "name": "java.security.MessageDigestSpi" + }, + { + "name": "[Ljava.lang.Object;" + }, + { + "name": "[Ljava.lang.String;" + }, + { + "name": "sun.security.provider.SHA", + "methods": [ + { + "name": "<init>", + "parameterTypes": [] + } + ] + } +] diff --git a/sdk/enclave/src/main/resources/META-INF/native-image/serialization-config.json b/sdk/enclave/src/main/resources/META-INF/native-image/serialization-config.json new file mode 100644 index 0000000..45dfb55 --- /dev/null +++ b/sdk/enclave/src/main/resources/META-INF/native-image/serialization-config.json @@ -0,0 +1,44 @@ +[ + { + "name":"com.alibaba.confidentialcomputing.common.EnclaveInvocationContext" + }, + { + "name":"com.alibaba.confidentialcomputing.common.EnclaveInvocationResult" + }, + { + "name":"com.alibaba.confidentialcomputing.common.ServiceHandler" + }, + { + "name": "[Lcom.alibaba.confidentialcomputing.common.ServiceHandler;" + }, + { + "name":"java.lang.String" + }, + { + "name":"java.util.ArrayList" + }, + { + "name": "java.util.Arrays$ArrayList" + }, + { + "name":"java.lang.Throwable" + }, + { + "name":"java.lang.Exception" + }, + { + "name":"java.io.IOException" + }, + { + "name":"java.io.ObjectStreamException" + }, + { + "name":"java.io.NotSerializableException" + }, + { + "name":"java.lang.StackTraceElement" + }, + { + "name":"[Ljava.lang.StackTraceElement;" + } +] diff --git a/sdk/enclave/src/main/resources/native/enc_environment.h b/sdk/enclave/src/main/resources/native/enc_environment.h new file mode 100644 index 0000000..dc6f683 --- /dev/null +++ b/sdk/enclave/src/main/resources/native/enc_environment.h @@ -0,0 +1,17 @@ + typedef struct enc_data_struct{ + //char array is used as byte array to store serialized data + char* data; + int data_len; + }enc_data; + +typedef struct callback_functions_struct{ + /* + * This method is invoked inside java_enclave_invoke method's exception catch + * section, when the execution is aborted by exceptions. The caller side can + * decide what to do with the exception. + * Exception details are passed back with parameters. + */ + void (*exception_handler)(char* err_msg, char* stack_trace, char* exception_name); + + char* (*memcpy_char_pointer)(char* src, int len); +}callbacks; diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/AroundNativeTest.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/AroundNativeTest.java new file mode 100644 index 0000000..ec28e4e --- /dev/null +++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/AroundNativeTest.java @@ -0,0 +1,49 @@ +package com.alibaba.confidentialcomputing.enclave; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +/** + * This class holds 2 main classes that are executed before and after maven surefire test by <a href="https://www.mojohaus.org/exec-maven-plugin/">exec-maven-plugin</a>. + */ +public class AroundNativeTest { + public static final Path tmpTestNativeLibsDir = Paths.get("/tmp/javaenclavetest-native-libs"); + + /** + * Before test starts, create the temporary directory to hold the dynamic native libraries that will be created + * during test. But the directory must be created beforehand, so that the {@code export LD_LIBRARY_PATH} action + * taken by surefire plugin can take effect. + */ + public static class PreTest { + public static void main(String[] args) throws IOException { + if (Files.notExists(tmpTestNativeLibsDir)) { + Files.createDirectories(tmpTestNativeLibsDir); + } + } + } + + public static class PostTest { + public static void main(String[] args) throws IOException { + if (Files.exists(tmpTestNativeLibsDir)) { + Files.walkFileTree(tmpTestNativeLibsDir, new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return super.visitFile(file, attrs); + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.delete(dir); + return super.postVisitDirectory(dir, exc); + } + }); + } + } + } +} diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/EnclaveTestHelper.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/EnclaveTestHelper.java index f47d64b..c51c90f 100644 --- a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/EnclaveTestHelper.java +++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/EnclaveTestHelper.java @@ -1,12 +1,119 @@ package com.alibaba.confidentialcomputing.enclave; +import com.alibaba.confidentialcomputing.common.EnclaveInvocationContext; +import com.alibaba.confidentialcomputing.common.EnclaveInvocationResult; +import com.alibaba.confidentialcomputing.common.SerializationHelper; +import com.alibaba.confidentialcomputing.common.ServiceHandler; +import com.alibaba.confidentialcomputing.enclave.testservice.IntegerMath; import com.alibaba.confidentialcomputing.enclave.testservice.MathService; import com.alibaba.confidentialcomputing.enclave.testservice.NumericMath; +import com.alibaba.confidentialcomputing.enclave.testservice.PointMath; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.Function; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; public class EnclaveTestHelper { public static final String MATH_SERVICE = MathService.class.getName(); public static final String NUMERIC_MATH = NumericMath.class.getName(); + public static final String INTEGER_MATH = IntegerMath.class.getName(); + public static final String POINT_MATH = PointMath.class.getName(); public static final String[] MATH_ADD_PARAM_TYPES = {"java.lang.Number", "java.lang.Number"}; + public static final String[] POINT_MATH_ADD_PARAM_TYPES = {"com.alibaba.confidentialcomputing.enclave.testservice.Point", + "com.alibaba.confidentialcomputing.enclave.testservice.Point"}; public static final String[] EMPTY_STRING_ARRAY = new String[0]; public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + public static final String VM_NAME = System.getProperty("java.vm.name"); + + public static native byte[] invokeEnclave(byte[] data); + + public static native byte[] loadService(byte[] data); + + public static native byte[] unloadService(byte[] data); + + public static native void createIsolate(); + + public static native void destroyIsolate(); + + public static boolean isInNativeImage() { + return VM_NAME.equals("Substrate VM"); + } + + public static ServiceHandler[] callLoadService(String serviceName) throws IOException, ClassNotFoundException { + EnclaveInvocationResult ret = callEnclaveJNI(serviceName, + input -> loadService(input)); + if (ret.getException() != null) { + ret.getException().printStackTrace(); + fail(); + } + return (ServiceHandler[]) ret.getResult(); + } + + public static EnclaveInvocationResult callEnclaveMethod(String service, String impl, String identity, String method, String[] paramTypes, Object[] values) throws IOException, ClassNotFoundException { + EnclaveInvocationContext enclaveInvocationContext = new EnclaveInvocationContext(new ServiceHandler(service, impl + , identity), method, paramTypes, values); + return callEnclaveJNI(enclaveInvocationContext, + input -> invokeEnclave(input)); + } + + private static EnclaveInvocationResult callEnclaveJNI(Object input, Function<byte[], byte[]> function) throws IOException, ClassNotFoundException { + byte[] data = SerializationHelper.serialize(input); + byte[] ret = function.apply(data); + return (EnclaveInvocationResult) SerializationHelper.deserialize(ret); + } + + public static Path createTestTmpDir(Path root) { + Path tmpDir; + try { + tmpDir = Files.createTempDirectory(root, "native-test-"); + } catch (IOException e) { + e.printStackTrace(); + tmpDir = null; + } + return tmpDir; + } + + public static String loadAndGetService(String serviceName, String implementation, int expectedServiceNum) { + ServiceHandler[] serviceHandlers = new ServiceHandler[0]; + try { + serviceHandlers = callLoadService(serviceName); + } catch (IOException | ClassNotFoundException e) { + fail(e); + } + assertEquals(expectedServiceNum, serviceHandlers.length); + // SVM doesn't guarantee the service order + for (ServiceHandler serviceHandler : serviceHandlers) { + if (serviceHandler.getServiceImplClassName().equals(implementation)) { + return serviceHandler.getInstanceIdentity(); + } + } + fail("Should not reach here"); + return null; + } + + public static Object call(String id, String serviceName, String className, String methodName, String[] paramTypes, Object[] paramValues) { + try { + EnclaveInvocationResult result = callEnclaveMethod(serviceName, + className, id, + methodName, + paramTypes, + paramValues); + Object wrappedResult = result.getResult(); + if (result.getException() != null) { + result.getException().printStackTrace(); + } + assertNotNull(wrappedResult, "Expect to have non-null result from invoking service method call."); + assertNull(result.getException()); + return wrappedResult; + } catch (IOException | ClassNotFoundException e) { + fail(e); + return 0; + } + } } diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/NativeImageTest.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/NativeImageTest.java new file mode 100644 index 0000000..ed676da --- /dev/null +++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/NativeImageTest.java @@ -0,0 +1,264 @@ +package com.alibaba.confidentialcomputing.enclave; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.SequenceInputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public abstract class NativeImageTest implements NativeImageTestable { + private static final String JNI_LIB_NAME = "encinvokeentrytest"; + public static final Path GRAALVM_HOME = Paths.get(System.getenv("GRAALVM_HOME")); + public static final Path MVN_BUILD_DIR = Paths.get("target"); + private static final String SVM_OUT = "svm-out"; + private static final String SVM_ENCLAVE_LIB = "svm_enclave_sdk"; + + static { + if (!GRAALVM_HOME.toFile().exists()) { + throw new RuntimeException("System environment variable GRAALVM_HOME is set to " + GRAALVM_HOME + + ", but the directory does not exist!"); + } + if (!MVN_BUILD_DIR.toFile().exists()) { + throw new RuntimeException("Maven default build directory " + MVN_BUILD_DIR.toAbsolutePath() + " doesn't exist." + + " Please check your maven's ${project.build.directory} property and make sure it's \"target\"."); + } + } + + protected String testName; + protected Path workingDir; + protected Path configRootDir; + protected Path configPathDir; + protected Path svmEncSDKClassDir; + protected Path serviceDir; + protected Path svmOutputDir; + protected Path testClassesDir; + protected Path svmCompileClassesDir; + + static class SVMCompileElements { + private final List<String> serviceConfigs = new ArrayList<>(); + private final List<String> svmConfigs = new ArrayList<>(); + private final List<String> otherResources = new ArrayList<>(); + private final List<Class<?>> classes = new ArrayList<>(); + + public void addServices(String... services) { + serviceConfigs.addAll(Arrays.asList(services)); + } + + public void addSvmConfigs(String... configs) { + svmConfigs.addAll(Arrays.asList(configs)); + } + + public void addResources(String... resources) { + otherResources.addAll(Arrays.asList(resources)); + } + + public void addClasses(Class<?>... classes2Compile) { + classes.addAll(Arrays.asList(classes2Compile)); + } + } + + public NativeImageTest() { + TestTarget targetTest = this.getClass().getAnnotation(TestTarget.class); + if (targetTest == null) { + throw new RuntimeException("The subclasses of NativeImageTest must use @TestTarget specify the test target class." + + " But class " + this.getClass().getName() + " doesn't have one."); + } + String targetTestClassName = targetTest.value().getName(); + int lastDot = targetTestClassName.lastIndexOf('.'); + testName = targetTestClassName.substring(lastDot + 1).toLowerCase(); + + workingDir = MVN_BUILD_DIR.resolve("native-work-dir-" + testName); + configRootDir = workingDir.resolve("config"); + configPathDir = configRootDir.resolve("META-INF/native-image"); + svmEncSDKClassDir = MVN_BUILD_DIR.resolve("classes"); + serviceDir = configRootDir.resolve("META-INF/services"); + svmOutputDir = workingDir.resolve(SVM_OUT); + svmCompileClassesDir = workingDir.resolve("bin"); + List<Path> dirsToCreate = new ArrayList<>(); + dirsToCreate.add(workingDir); + dirsToCreate.add(configRootDir); + dirsToCreate.add(configPathDir); + dirsToCreate.add(serviceDir); + dirsToCreate.add(svmCompileClassesDir); + dirsToCreate.forEach(p -> createDirs(p, "Can't create directory " + p + " at test preparation time")); + testClassesDir = MVN_BUILD_DIR.resolve("test-classes"); + } + + public void prepareNativeLibraries() { + collectSVMCompileItems(); + runWithNativeImageAgent(); + beforeSVMCompile(); + svmCompile(); + afterSVMCompile(); + compileJNILibrary(); + Path so1 = workingDir.resolve("lib" + JNI_LIB_NAME + ".so"); + Path so2 = workingDir.resolve("lib" + SVM_ENCLAVE_LIB + ".so"); + copyFile(so1, AroundNativeTest.tmpTestNativeLibsDir.resolve(so1.getFileName()), null); + copyFile(so2, AroundNativeTest.tmpTestNativeLibsDir.resolve(so2.getFileName()), null); + System.loadLibrary(JNI_LIB_NAME); + } + + private void collectSVMCompileItems() { + SVMCompileElements items = specifyTestClasses(); + if (items == null) { + throw new RuntimeException("Must specify the elements to be compiled by native-image for testing."); + } + + if (!items.svmConfigs.isEmpty()) { + items.svmConfigs.stream().map(s -> testClassesDir.resolve(s).toAbsolutePath()). + forEach(p -> copyFile(p, configPathDir.resolve(p.getFileName()), "Fail to copy configuration file.")); + } + + if (items.classes.isEmpty()) { + throw new RuntimeException("Must specify the classes to be compiled by native-image for testing."); + } else { + items.classes.forEach(c -> + { + String classLocation = getClassFileName(c); + Path destPath = svmCompileClassesDir.resolve(classLocation); + createDirs(destPath.getParent(), "Can't create class directories for native-image compilation."); + copyFile(testClassesDir.resolve(classLocation).toAbsolutePath(), destPath, "Can't copy class file."); + }); + } + + if (!items.serviceConfigs.isEmpty()) { + items.serviceConfigs.stream().map(s -> testClassesDir.resolve(s).toAbsolutePath()). + forEach(p -> copyFile(p, serviceDir.resolve(p.getFileName()), null)); + } + + if (!items.otherResources.isEmpty()) { + items.otherResources.forEach(s -> { + Path destPath = svmCompileClassesDir.resolve(s); + createDirs(destPath.getParent(), "Can't create resource directories for native-image compilation."); + copyFile(testClassesDir.resolve(s).toAbsolutePath(), destPath, "Can't copy resource file."); + }); + } + } + + abstract SVMCompileElements specifyTestClasses(); + + protected void svmCompile() { + List<String> command = new ArrayList<>(); + command.add(0, GRAALVM_HOME.resolve("bin/native-image").toString()); + command.add("-cp"); + StringBuilder sb = new StringBuilder(); + List<Path> svmBinFiles = Arrays.asList(svmCompileClassesDir, + configRootDir, + svmEncSDKClassDir, + MVN_BUILD_DIR.toAbsolutePath().getParent().getParent().resolve("common/target/classes") + ); + svmBinFiles.stream().map(p -> p.normalize().toAbsolutePath()).forEach(p -> { + if (Files.notExists(p)) { + throw new RuntimeException("File " + p + " on native-image class file doesn't exist."); + } + sb.append(p.toString()).append(File.pathSeparator); + }); + command.add(sb.deleteCharAt(sb.length() - 1).toString()); + command.add("--shared"); + command.add("--no-fallback"); + command.add("-H:OutputRelocatableImage=."); + command.add("-H:Path=" + SVM_OUT); + command.add("-H:+AllowIncompleteClasspath"); + command.add("-H:+ReportExceptionStackTraces"); + command.add("-H:Name=lib" + SVM_ENCLAVE_LIB); + command.add("-H:-DeleteLocalSymbols"); + List<String> extraOptions = extraSVMOptions(); + if (extraOptions != null && !extraOptions.isEmpty()) { + command.addAll(extraOptions); + } + NativeImageTest.executeNewProcess(command, workingDir); + } + + private void compileJNILibrary() { + System.out.println("###Prepare JNI library ...###"); + List<Path> requiredFilePaths = new ArrayList<>(); + requiredFilePaths.add(testClassesDir.resolve("native/com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper.h")); + requiredFilePaths.add(testClassesDir.resolve("native/enc_invoke_entry_test.c")); + requiredFilePaths.add(svmOutputDir.resolve("lib" + SVM_ENCLAVE_LIB + ".h")); + requiredFilePaths.add(svmOutputDir.resolve("graal_isolate.h")); + requiredFilePaths.add(svmOutputDir.resolve("enc_environment.h")); + requiredFilePaths.add(svmOutputDir.resolve("lib" + SVM_ENCLAVE_LIB + ".so")); + requiredFilePaths.forEach(p -> copyFile(p, workingDir.resolve(p.getFileName()), null)); + + List<String> command = new ArrayList<>(); + command.add("gcc"); + command.add("-fPIC"); + command.add("-I" + GRAALVM_HOME.toAbsolutePath() + "/include"); + command.add("-I" + GRAALVM_HOME.toAbsolutePath() + "/include/linux"); + command.add("enc_invoke_entry_test.c"); + command.add("-I."); + command.add("-L."); + command.add("-std=c99"); + command.add("-l" + SVM_ENCLAVE_LIB); + command.add("-lc"); + command.add("-shared"); + command.add("-o"); + command.add("lib" + JNI_LIB_NAME + ".so"); + executeNewProcess(command, workingDir); + } + + public static int executeNewProcess(List<String> command, Path workDir) { + if (command == null || command.isEmpty()) { + throw new RuntimeException("Didn't provide any execution command."); + } + ProcessBuilder pb = new ProcessBuilder(command).directory(workDir.toFile()); + String oneLineCommand = command.stream().reduce((e1, e2) -> e1 + " " + e2).orElse(""); + System.out.println(oneLineCommand); + Process p = null; + try { + p = pb.start(); + SequenceInputStream sis = new SequenceInputStream(p.getInputStream(), p.getErrorStream()); + InputStreamReader inst = new InputStreamReader(sis, StandardCharsets.UTF_8); + StringBuilder sb = new StringBuilder(); + try (BufferedReader br = new BufferedReader(inst)) { + String res; + while ((res = br.readLine()) != null) { + sb.append(res).append("\n"); + } + } + System.out.println(sb); + int exitCode = p.waitFor(); + if (exitCode != 0) { + throw new RuntimeException("Failed to execute command:\n " + oneLineCommand + + "\n Working directory is :" + workDir.toString() + "\n The exit code is " + exitCode); + } + return 0; + } catch (IOException | InterruptedException e) { + throw new RuntimeException("Failed to execute command:\n " + oneLineCommand, e); + } finally { + if (p != null) { + p.destroy(); + } + } + } + + public static void copyFile(Path source, Path dest, String errMSg) { + try { + Files.copy(source, dest, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new RuntimeException(errMSg, e); + } + } + + private static void createDirs(Path p, String errMsg) { + if (Files.notExists(p)) { + try { + Files.createDirectories(p); + } catch (IOException e) { + throw new RuntimeException(errMsg, e); + } + } + } + + public static String getClassFileName(Class<?> clazz) { + return clazz.getName().replace('.', '/') + ".class"; + } +} diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/NativeImageTestable.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/NativeImageTestable.java new file mode 100644 index 0000000..7afe107 --- /dev/null +++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/NativeImageTestable.java @@ -0,0 +1,12 @@ +package com.alibaba.confidentialcomputing.enclave; + +import java.util.List; + +public interface NativeImageTestable { + default void runWithNativeImageAgent(){} + default void beforeSVMCompile(){} + default void afterSVMCompile(){} + default List<String> extraSVMOptions() { + return null; + } +} diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/RunWithNativeImageTest.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/RunWithNativeImageTest.java new file mode 100644 index 0000000..44ad76e --- /dev/null +++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/RunWithNativeImageTest.java @@ -0,0 +1,166 @@ +package com.alibaba.confidentialcomputing.enclave; + +import com.alibaba.confidentialcomputing.enclave.testservice.IntegerMath; +import com.alibaba.confidentialcomputing.enclave.testservice.MathService; +import com.alibaba.confidentialcomputing.enclave.testservice.NumericMath; +import com.alibaba.confidentialcomputing.enclave.testservice.Point; +import com.alibaba.confidentialcomputing.enclave.testservice.PointMath; +import com.oracle.svm.core.annotate.AutomaticFeature; +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeSerialization; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.security.NoSuchAlgorithmException; + +import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.EMPTY_OBJECT_ARRAY; +import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.EMPTY_STRING_ARRAY; +import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.INTEGER_MATH; +import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.MATH_ADD_PARAM_TYPES; +import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.MATH_SERVICE; +import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.NUMERIC_MATH; +import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.POINT_MATH; +import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.POINT_MATH_ADD_PARAM_TYPES; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class RunWithNativeImageTest { + + @TestTarget(RunWithNativeImageTest.class) + static + class SimpleRunPreparation extends NativeImageTest { + + @Override + public SVMCompileElements specifyTestClasses() { + SVMCompileElements ret = new SVMCompileElements(); + // Specify the service file + ret.addServices("META-INF/services/com.alibaba.confidentialcomputing.enclave.testservice.MathService"); + + // Specify the classes need to be statically compiled into native image for this test + ret.addClasses( + UTFeature.class, MathService.class, NumericMath.class, PointMath.class, Point.class, IntegerMath.class + ); + return ret; + } + } + + static private NativeImageTest nativeImageTest; + + @BeforeAll + public static void prepareLibraries() { + nativeImageTest = new SimpleRunPreparation(); + nativeImageTest.prepareNativeLibraries(); + } + + @BeforeEach + public void setup() { + EnclaveTestHelper.createIsolate(); + } + + @AfterEach + public void teardown() { + EnclaveTestHelper.destroyIsolate(); + } + + /** + * Test calling a method with primitive parameters and returned value. + */ + @Test + public void testSimpleRun() { + String identity = loadAndGetService(NUMERIC_MATH); + assertEquals(3, callIntAdd(identity, 1, 2)); + } + + /** + * Test a multithreading case. + */ + @Test + public void testMultiThreadRun() { + String identity1 = loadAndGetService(NUMERIC_MATH); + String identity2 = loadAndGetService(NUMERIC_MATH); + + Thread t1 = new Thread(() -> { + try { + assertEquals(3, callIntAdd(identity1, 1, 2)); + } catch (Exception e) { + fail(e); + } + }); + Thread t2 = new Thread(() -> { + try { + assertEquals(9, callIntAdd(identity1, 4, 5)); + } catch (Exception e) { + fail(e); + } + }); + Thread t3 = new Thread(() -> { + try { + assertEquals(9, callIntAdd(identity2, 4, 5)); + } catch (Exception e) { + fail(e); + } + }); + t1.start(); + t2.start(); + t3.start(); + try { + t1.join(); + t2.join(); + t3.join(); + } catch (InterruptedException e) { + fail(e); + } + assertEquals(2, callGetCounter(identity1)); + assertEquals(1, callGetCounter(identity2)); + } + + /** + * Test calling an interface default method. + */ + @Test + public void testServiceDefaultMethod() { + String identity = loadAndGetService(INTEGER_MATH); + int ret = (Integer) call(identity, INTEGER_MATH, "getConstant", EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY); + assertEquals(100, ret); + } + + /** + * Test calling a method with referenced type of parameters and returned values. + */ + @Test + public void testPointAdd() { + String id = loadAndGetService(POINT_MATH); + Point ret = (Point) call(id, POINT_MATH, "add", POINT_MATH_ADD_PARAM_TYPES, + new Object[]{new Point(1, 1), new Point(2, 2)}); + assertEquals(3, ret.x); + assertEquals(3, ret.y); + } + + private static String loadAndGetService(String implementation) { + return EnclaveTestHelper.loadAndGetService(MATH_SERVICE, implementation, 3); + } + + private static int callIntAdd(String id, int x, int y) { + return (Integer) call(id, NUMERIC_MATH, "add", MATH_ADD_PARAM_TYPES, new Object[]{x, y}); + } + + private static int callGetCounter(String id) { + return (Integer) call(id, NUMERIC_MATH, "getCounter", EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY); + } + + private static Object call(String id, String className, String methodName, String[] paramTypes, Object[] paramValues) { + return EnclaveTestHelper.call(id, MATH_SERVICE, className, methodName, paramTypes, paramValues); + } + + @AutomaticFeature + static class UTFeature implements Feature { + + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + RuntimeSerialization.register(Number.class, NoSuchAlgorithmException.class); + } + } +} diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/SVMSimpleEnclaveCallTest.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/SVMSimpleEnclaveCallTest.java new file mode 100644 index 0000000..af9732c --- /dev/null +++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/SVMSimpleEnclaveCallTest.java @@ -0,0 +1,123 @@ +package com.alibaba.confidentialcomputing.enclave; + +import com.alibaba.confidentialcomputing.common.EnclaveInvocationContext; +import com.alibaba.confidentialcomputing.common.EnclaveInvocationResult; +import com.alibaba.confidentialcomputing.common.SerializationHelper; +import com.alibaba.confidentialcomputing.common.ServiceHandler; +import com.alibaba.confidentialcomputing.enclave.c.EnclaveEnvironment.CallBacks; +import com.alibaba.confidentialcomputing.enclave.c.EnclaveEnvironment.EncData; +import org.graalvm.nativeimage.Isolate; +import org.graalvm.nativeimage.IsolateThread; +import org.graalvm.nativeimage.Isolates; +import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.c.type.CTypeConversion; +import org.graalvm.word.WordFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.MATH_ADD_PARAM_TYPES; +import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.MATH_SERVICE; +import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.NUMERIC_MATH; +import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.isInNativeImage; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; + +public class SVMSimpleEnclaveCallTest { + private IsolateThread isolateThread; + + @BeforeEach + public void setup() { + if (isInNativeImage()) { + isolateThread = Isolates.createIsolate(Isolates.CreateIsolateParameters.getDefault()); + } + } + + @AfterEach + public void teardown() { + if (isInNativeImage()) { + Isolates.tearDownIsolate(isolateThread); + } + } + + @Test + public void test() throws IOException, ClassNotFoundException { + if (!isInNativeImage()) { + return; + } + ServiceHandler[] serviceHandlers = callLoadService(MATH_SERVICE); + assertEquals(3, serviceHandlers.length); + assertEquals(NUMERIC_MATH, serviceHandlers[0].getServiceImplClassName()); + String identity = serviceHandlers[0].getInstanceIdentity(); + EnclaveInvocationResult result = callEnclaveMethod(MATH_SERVICE, + NUMERIC_MATH, identity, + "add", + MATH_ADD_PARAM_TYPES, + new Object[]{1, 2}); + assertNotNull(result); + Object wrappedResult = result.getResult(); + if (result.getException() != null) { + result.getException().printStackTrace(); + } + assertNotNull(wrappedResult, "Expect to have non-null result from invoking service method call."); + assertNull(result.getException()); + assertEquals(3, (Integer) wrappedResult); + } + + private ServiceHandler[] callLoadService(String serviceName) throws IOException, ClassNotFoundException { + EnclaveInvocationResult ret = callEnclaveEntryPoint(serviceName, + (isolateThread, input, result, callbacks) -> EnclaveEntry.loadService(isolateThread, input, result, callbacks)); + if (ret.getException() != null) { + ret.getException().printStackTrace(); + fail(); + } + return (ServiceHandler[]) ret.getResult(); + } + + private EnclaveInvocationResult callUnloadService(String serviceName) throws IOException, ClassNotFoundException { + return callEnclaveEntryPoint(serviceName, + (isolateThread, input, result, callbacks) -> EnclaveEntry.unloadService(isolateThread, input, result, callbacks)); + } + + private EnclaveInvocationResult callEnclaveMethod(String service, String impl, String identity, String method, String[] paramTypes, Object[] values) throws IOException, ClassNotFoundException { + EnclaveInvocationContext enclaveInvocationContext = new EnclaveInvocationContext(new ServiceHandler(service, impl + , identity), method, paramTypes, values); + return callEnclaveEntryPoint(enclaveInvocationContext, + (isolateThread, input, result, callbacks) -> EnclaveEntry.javaEnclaveInvoke(isolateThread, input, result, callbacks)); + } + + private EnclaveInvocationResult callEnclaveEntryPoint(Object input, Caller caller) throws IOException, ClassNotFoundException { + byte[] data = SerializationHelper.serialize(input); + EncData entryInput = StackValue.get(EncData.class); + EncData entryResult = StackValue.get(EncData.class); + try (CTypeConversion.CCharPointerHolder byteHolder = CTypeConversion.toCBytes(data)) { + entryInput.setData(byteHolder.get()); + entryInput.setLen(data.length); + } + CallBacks callBacks = StackValue.get(CallBacks.class); + callBacks.setExceptionHandler(WordFactory.nullPointer()); + callBacks.setMemCpyCCharPointerFunctionPointer(WordFactory.nullPointer()); + int ret = caller.call(Isolates.getIsolate(isolateThread), entryInput, entryResult, callBacks); + if (ret == 0) { + return (EnclaveInvocationResult) SerializationHelper.deserialize(transformInput(entryResult)); + } else { + throw new RuntimeException("Fail to execute enclave method, return value is " + ret); + } + } + + @FunctionalInterface + private interface Caller { + int call(Isolate isolate, EncData input, EncData result, CallBacks callBacks); + } + + private static byte[] transformInput(EncData input) { + int len = input.getLen(); + byte[] data = new byte[len]; + CTypeConversion.asByteBuffer(input.getData(), len).get(data); + return data; + } +} diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/TestTarget.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/TestTarget.java new file mode 100644 index 0000000..f604331 --- /dev/null +++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/TestTarget.java @@ -0,0 +1,12 @@ +package com.alibaba.confidentialcomputing.enclave; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface TestTarget { + Class<?> value(); +} diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/framework/ServiceMethodInvokerTest.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/framework/ServiceMethodInvokerTest.java index 7a7dc11..b0fea25 100644 --- a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/framework/ServiceMethodInvokerTest.java +++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/framework/ServiceMethodInvokerTest.java @@ -6,6 +6,7 @@ import com.alibaba.confidentialcomputing.common.ServiceHandler; import com.alibaba.confidentialcomputing.common.exception.ConfidentialComputingException; import com.alibaba.confidentialcomputing.enclave.testservice.MathService; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -14,10 +15,12 @@ import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.EMPTY_ import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.MATH_ADD_PARAM_TYPES; import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.MATH_SERVICE; import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.NUMERIC_MATH; +import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.isInNativeImage; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeFalse; import java.lang.reflect.InvocationTargetException; @@ -26,6 +29,11 @@ public class ServiceMethodInvokerTest { private static ServiceMethodInvoker serviceMethodInvoker = new ServiceMethodInvoker(); private ServiceHandler[] services; + @BeforeAll + public static void svmCheck(){ + assumeFalse(isInNativeImage()); + } + @BeforeEach public void setup() { services = EnclaveContext.getInstance().loadService(MathService.class); diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/framework/ServiceOperationTest.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/framework/ServiceOperationTest.java index 726e8e5..1376e94 100644 --- a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/framework/ServiceOperationTest.java +++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/framework/ServiceOperationTest.java @@ -4,18 +4,26 @@ import com.alibaba.confidentialcomputing.common.EnclaveInvocationResult; import com.alibaba.confidentialcomputing.common.ServiceHandler; import com.alibaba.confidentialcomputing.enclave.testservice.NumericMath; import com.alibaba.confidentialcomputing.enclave.testservice.PointMath; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.MATH_SERVICE; +import static com.alibaba.confidentialcomputing.enclave.EnclaveTestHelper.isInNativeImage; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeFalse; /** * This class tests loading and unloading service. */ public class ServiceOperationTest { + @BeforeAll + public static void svmCheck(){ + assumeFalse(isInNativeImage()); + } + @Test public void testLoadingAndUnloading() { LoadServiceInvoker loadServiceInvoker = new LoadServiceInvoker(); diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/MathService.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/MathService.java index 9a01c09..8df60c8 100644 --- a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/MathService.java +++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/MathService.java @@ -1,5 +1,8 @@ package com.alibaba.confidentialcomputing.enclave.testservice; +import com.alibaba.confidentialcomputing.common.annotations.EnclaveService; + +@EnclaveService public interface MathService<T> { T add(T x, T y); diff --git a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/Point.java b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/Point.java index f252d7f..31623be 100644 --- a/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/Point.java +++ b/sdk/enclave/src/test/java/com/alibaba/confidentialcomputing/enclave/testservice/Point.java @@ -1,8 +1,13 @@ package com.alibaba.confidentialcomputing.enclave.testservice; -public class Point { - int x; - int y; +import java.io.Serializable; + +public class Point implements Serializable { + + private static final long serialVersionUID = -3715916707782706029L; + + public int x; + public int y; public Point(int x, int y){ this.x = x; diff --git a/sdk/enclave/src/test/resources/native/com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper.h b/sdk/enclave/src/test/resources/native/com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper.h new file mode 100644 index 0000000..18b564b --- /dev/null +++ b/sdk/enclave/src/test/resources/native/com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper.h @@ -0,0 +1,53 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include <jni.h> +/* Header for class com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper */ + +#ifndef _Included_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper +#define _Included_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper + * Method: invokeEnclave + * Signature: ([B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_invokeEnclave + (JNIEnv *, jclass, jbyteArray); + +/* + * Class: com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper + * Method: loadService + * Signature: ([B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_loadService + (JNIEnv *, jclass, jbyteArray); + +/* + * Class: com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper + * Method: unloadService + * Signature: ([B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_unloadService + (JNIEnv *, jclass, jbyteArray); + +/* + * Class: com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper + * Method: createIsolate + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_createIsolate + (JNIEnv *, jclass); + +/* + * Class: com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper + * Method: destroyIsolate + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_destroyIsolate + (JNIEnv *, jclass); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/sdk/enclave/src/test/resources/native/enc_invoke_entry_test.c b/sdk/enclave/src/test/resources/native/enc_invoke_entry_test.c new file mode 100644 index 0000000..8faa6f1 --- /dev/null +++ b/sdk/enclave/src/test/resources/native/enc_invoke_entry_test.c @@ -0,0 +1,76 @@ +#include <stdlib.h> +#include <string.h> +#include "com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper.h" +#include "enc_environment.h" +#ifdef MUSL +#include "libmusl_svmenclavesdk.h" +#else +#include "libsvm_enclave_sdk.h" +#endif + +typedef int (*enclave_invoke)(graal_isolate_t* isolate, enc_data* input, enc_data* result, callbacks* callBacks); + + char* memcpy_char_pointer(char* src, int len){ + int size = sizeof(char); + char *dest = (char*)malloc(len*size); + memcpy(dest, src, len); + return dest; + } + +static graal_isolatethread_t *thread = NULL; +static graal_isolate_t *isolate = NULL; + +jbyteArray enclave_call(JNIEnv* env, jclass clazz, jbyteArray data, enclave_invoke invoke){ +jboolean isCopy; + jbyte* a = (*env)->GetByteArrayElements(env, data, &isCopy); + enc_data invoke_data; + invoke_data.data=(char*)a; + invoke_data.data_len=(*env)->GetArrayLength(env, data); + + callbacks callback_methods; + callback_methods.memcpy_char_pointer=&memcpy_char_pointer; + callback_methods.exception_handler=NULL; // Must explicitly set + + enc_data ret; + int exit_code = invoke(isolate, &invoke_data, &ret, &callback_methods); + jbyteArray retVal; + if(exit_code == 0 ){ + retVal = (*env)->NewByteArray(env, ret.data_len); + jbyte *buf = (*env)->GetByteArrayElements(env, retVal, NULL); + memcpy(buf, ret.data, ret.data_len); + (*env)->ReleaseByteArrayElements(env, retVal, buf, 0); + //printf("Returned type is %.*s\n", ret.verify_info_len, ret.verify_info); + free(ret.data); + }else{ + retVal = NULL; + } + return retVal; +} + +JNIEXPORT void JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_createIsolate + (JNIEnv *env, jclass clazz){ + if (graal_create_isolate(NULL, &isolate, &thread) != 0) { + fprintf(stderr, "error on isolate creation or attach\n"); + } +} + +JNIEXPORT void JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_destroyIsolate + (JNIEnv *env, jclass clazz){ + //graal_tear_down_isolate(thread); + graal_detach_all_threads_and_tear_down_isolate(thread); +} + +JNIEXPORT jbyteArray JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_loadService +(JNIEnv* env, jclass clazz, jbyteArray data) { + return enclave_call(env, clazz, data, java_loadservice_invoke); +} + +JNIEXPORT jbyteArray JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_unloadService + (JNIEnv* env, jclass clazz, jbyteArray data){ + return enclave_call(env, clazz, data, java_unloadservice_invoke); +} + +JNIEXPORT jbyteArray JNICALL Java_com_alibaba_confidentialcomputing_enclave_EnclaveTestHelper_invokeEnclave +(JNIEnv* env, jclass clazz, jbyteArray data) { + return enclave_call(env, clazz, data, java_enclave_invoke); +} diff --git a/tools/cicd/Dockerfile b/tools/cicd/Dockerfile index c3a7c4d..f988bd3 100644 --- a/tools/cicd/Dockerfile +++ b/tools/cicd/Dockerfile @@ -7,6 +7,9 @@ ENV DEBIAN_FRONTEND noninteractive # install openjdk11 and maven. RUN apt-get update && \ - echo -e 'yes\n' | apt-get install -y openjdk-11-jdk && \ echo -e 'yes\n' | apt-get install -y maven && \ + echo -e 'yes\n' | apt-get install -y build-essential libz-dev zlib1g-dev && \ echo -e 'yes\n' | apt-get install -y wget +ADD ["graalvm-enclave-22.0.0.tar", "/root/tools/"] +ENV GRAALVM_HOME "/root/tools/graalvm-enclave-22.0.0" +ENV JAVA_HOME "/root/tools/graalvm-enclave-22.0.0" diff --git a/tools/cicd/make.sh b/tools/cicd/make.sh index 0120911..04657d5 100755 --- a/tools/cicd/make.sh +++ b/tools/cicd/make.sh @@ -1,7 +1,7 @@ #!/bin/bash BUILD_IMAGE=javaenclave_build -BUILD_TAG=v0.1.1 +BUILD_TAG=v0.1.2 SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) @@ -11,7 +11,11 @@ WORKDIR=$(dirname $(dirname "$PWD")) # check target images exist or not, build it if not. if [[ "$(docker images -q ${BUILD_IMAGE}:${BUILD_TAG} 2> /dev/null)" == "" ]]; then + # Get the customized Graal VM from [email protected]:graal/SGXGraalVM.git + # This should be replaced to the offical version when all patches are accepted by the Graal community + wget https://graal.oss-cn-beijing.aliyuncs.com/graal-enclave/JDK11-22.0.0/graalvm-enclave-22.0.0.tar docker build -t ${BUILD_IMAGE}:${BUILD_TAG} . + rm -f graalvm-enclave-22.0.0.tar fi # test JavaEnclave's unit test cases and samples @@ -19,3 +23,4 @@ docker run -i --rm --privileged --network host \ -w "${WORKDIR}" \ -v "${HOME}"/.m2:/root/.m2 -v "${WORKDIR}":"${WORKDIR}" \ ${BUILD_IMAGE}:${BUILD_TAG} /bin/bash build.sh + --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
