Move dex path and CLC validation to JNI.
Bug: 298183834
Bug: 296034438
Test: atest CtsCompilationTestCases
Test: atest ArtServiceTests
Test: atest art_standalone_artd_tests
Test: atest art_standalone_libartservice_tests
Change-Id: I7ed1a36392ea8ca6f583e4743baef043c9927ee5
Merged-In: I7ed1a36392ea8ca6f583e4743baef043c9927ee5
diff --git a/artd/Android.bp b/artd/Android.bp
index cacbe28..6de6aef 100644
--- a/artd/Android.bp
+++ b/artd/Android.bp
@@ -35,6 +35,7 @@
"profman_headers",
],
shared_libs: [
+ "libartservice",
"libarttools", // Contains "libc++fs".
"libbase",
"libbinder_ndk",
diff --git a/artd/artd.cc b/artd/artd.cc
index 09168a2..8b46d91 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -73,6 +73,7 @@
#include "path_utils.h"
#include "profman/profman_result.h"
#include "selinux/android.h"
+#include "service.h"
#include "tools/cmdline_builder.h"
#include "tools/tools.h"
@@ -109,6 +110,7 @@
using ::android::base::StringReplace;
using ::android::base::WriteStringToFd;
using ::android::fs_mgr::FstabEntry;
+using ::art::service::ValidateDexPath;
using ::art::tools::CmdlineBuilder;
using ::ndk::ScopedAStatus;
@@ -1177,44 +1179,6 @@
return ScopedAStatus::ok();
}
-ScopedAStatus Artd::validateDexPath(const std::string& in_dexPath,
- std::optional<std::string>* _aidl_return) {
- if (Result<void> result = ValidateDexPath(in_dexPath); !result.ok()) {
- *_aidl_return = result.error().message();
- } else {
- *_aidl_return = std::nullopt;
- }
- return ScopedAStatus::ok();
-}
-
-ScopedAStatus Artd::validateClassLoaderContext(const std::string& in_dexPath,
- const std::string& in_classLoaderContext,
- std::optional<std::string>* _aidl_return) {
- if (in_classLoaderContext == ClassLoaderContext::kUnsupportedClassLoaderContextEncoding) {
- *_aidl_return = std::nullopt;
- return ScopedAStatus::ok();
- }
-
- std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(in_classLoaderContext);
- if (context == nullptr) {
- *_aidl_return = ART_FORMAT("Class loader context '{}' is invalid", in_classLoaderContext);
- return ScopedAStatus::ok();
- }
-
- std::vector<std::string> flattened_context = context->FlattenDexPaths();
- std::string dex_dir = Dirname(in_dexPath);
- for (const std::string& context_element : flattened_context) {
- std::string context_path = std::filesystem::path(dex_dir).append(context_element);
- if (Result<void> result = ValidateDexPath(context_path); !result.ok()) {
- *_aidl_return = result.error().message();
- return ScopedAStatus::ok();
- }
- }
-
- *_aidl_return = std::nullopt;
- return ScopedAStatus::ok();
-}
-
Result<void> Artd::Start() {
OR_RETURN(SetLogVerbosity());
diff --git a/artd/artd.h b/artd/artd.h
index 8d9fa6f..c78aaa9 100644
--- a/artd/artd.h
+++ b/artd/artd.h
@@ -172,13 +172,6 @@
const aidl::com::android::server::art::RuntimeArtifactsPath& in_runtimeArtifactsPath,
int64_t* _aidl_return) override;
- ndk::ScopedAStatus validateDexPath(const std::string& in_dexPath,
- std::optional<std::string>* _aidl_return) override;
-
- ndk::ScopedAStatus validateClassLoaderContext(const std::string& in_dexPath,
- const std::string& in_classLoaderContext,
- std::optional<std::string>* _aidl_return) override;
-
android::base::Result<void> Start();
private:
diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl
index a296337..014f63e 100644
--- a/artd/binder/com/android/server/art/IArtd.aidl
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -190,17 +190,4 @@
*/
long deleteRuntimeArtifacts(
in com.android.server.art.RuntimeArtifactsPath runtimeArtifactsPath);
-
- /**
- * Returns an error message if the given dex path is invalid, or null if the validation
- * passes.
- */
- @nullable @utf8InCpp String validateDexPath(@utf8InCpp String dexPath);
-
- /**
- * Returns an error message if the given class loader context is invalid, or null if the
- * validation passes.
- */
- @nullable @utf8InCpp String validateClassLoaderContext(@utf8InCpp String dexPath,
- @utf8InCpp String classLoaderContext);
}
diff --git a/artd/path_utils.cc b/artd/path_utils.cc
index aa5a2b1..61d185b 100644
--- a/artd/path_utils.cc
+++ b/artd/path_utils.cc
@@ -31,6 +31,7 @@
#include "fstab/fstab.h"
#include "oat_file_assistant.h"
#include "runtime_image.h"
+#include "service.h"
#include "tools/tools.h"
namespace art {
@@ -49,6 +50,9 @@
using ::android::fs_mgr::Fstab;
using ::android::fs_mgr::FstabEntry;
using ::android::fs_mgr::ReadFstabFromProcMounts;
+using ::art::service::ValidateDexPath;
+using ::art::service::ValidatePathElement;
+using ::art::service::ValidatePathElementSubstring;
using PrebuiltProfilePath = ProfilePath::PrebuiltProfilePath;
using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath;
@@ -61,45 +65,6 @@
// Only to be changed for testing.
std::string_view gListRootDir = "/";
-Result<void> ValidateAbsoluteNormalPath(const std::string& path_str) {
- if (path_str.empty()) {
- return Errorf("Path is empty");
- }
- if (path_str.find('\0') != std::string::npos) {
- return Errorf("Path '{}' has invalid character '\\0'", path_str);
- }
- std::filesystem::path path(path_str);
- if (!path.is_absolute()) {
- return Errorf("Path '{}' is not an absolute path", path_str);
- }
- if (path.lexically_normal() != path_str) {
- return Errorf("Path '{}' is not in normal form", path_str);
- }
- return {};
-}
-
-Result<void> ValidatePathElementSubstring(const std::string& path_element_substring,
- const std::string& name) {
- if (path_element_substring.empty()) {
- return Errorf("{} is empty", name);
- }
- if (path_element_substring.find('/') != std::string::npos) {
- return Errorf("{} '{}' has invalid character '/'", name, path_element_substring);
- }
- if (path_element_substring.find('\0') != std::string::npos) {
- return Errorf("{} '{}' has invalid character '\\0'", name, path_element_substring);
- }
- return {};
-}
-
-Result<void> ValidatePathElement(const std::string& path_element, const std::string& name) {
- OR_RETURN(ValidatePathElementSubstring(path_element, name));
- if (path_element == "." || path_element == "..") {
- return Errorf("Invalid {} '{}'", name, path_element);
- }
- return {};
-}
-
} // namespace
Result<std::string> GetAndroidDataOrError() {
@@ -190,11 +155,6 @@
return {};
}
-Result<void> ValidateDexPath(const std::string& dex_path) {
- OR_RETURN(ValidateAbsoluteNormalPath(dex_path));
- return {};
-}
-
Result<std::string> BuildArtBinPath(const std::string& binary_name) {
return ART_FORMAT("{}/bin/{}", OR_RETURN(GetArtRootOrError()), binary_name);
}
diff --git a/artd/path_utils.h b/artd/path_utils.h
index 77aa82a..b42b639 100644
--- a/artd/path_utils.h
+++ b/artd/path_utils.h
@@ -46,8 +46,6 @@
android::base::Result<void> ValidateRuntimeArtifactsPath(
const aidl::com::android::server::art::RuntimeArtifactsPath& runtime_artifacts_path);
-android::base::Result<void> ValidateDexPath(const std::string& dex_path);
-
android::base::Result<std::string> BuildArtBinPath(const std::string& binary_name);
// Returns the absolute path to the OAT file built from the `ArtifactsPath`.
diff --git a/artd/path_utils_test.cc b/artd/path_utils_test.cc
index 960bf97..47b503f 100644
--- a/artd/path_utils_test.cc
+++ b/artd/path_utils_test.cc
@@ -62,29 +62,12 @@
HasValue(android_data_ + "/dalvik-cache/arm64/a@b.apk@classes.dex"));
}
-TEST_F(PathUtilsTest, BuildOatPathEmptyDexPath) {
- EXPECT_THAT(BuildOatPath(ArtifactsPath{.dexPath = "", .isa = "arm64", .isInDalvikCache = false}),
- HasError(WithMessage("Path is empty")));
-}
-
-TEST_F(PathUtilsTest, BuildOatPathRelativeDexPath) {
+TEST_F(PathUtilsTest, BuildOatPathInvalidDexPath) {
EXPECT_THAT(
BuildOatPath(ArtifactsPath{.dexPath = "a/b.apk", .isa = "arm64", .isInDalvikCache = false}),
HasError(WithMessage("Path 'a/b.apk' is not an absolute path")));
}
-TEST_F(PathUtilsTest, BuildOatPathNonNormalDexPath) {
- EXPECT_THAT(BuildOatPath(ArtifactsPath{
- .dexPath = "/a/c/../b.apk", .isa = "arm64", .isInDalvikCache = false}),
- HasError(WithMessage("Path '/a/c/../b.apk' is not in normal form")));
-}
-
-TEST_F(PathUtilsTest, BuildOatPathNul) {
- EXPECT_THAT(BuildOatPath(ArtifactsPath{
- .dexPath = "/a/\0/b.apk"s, .isa = "arm64", .isInDalvikCache = false}),
- HasError(WithMessage("Path '/a/\0/b.apk' has invalid character '\\0'"s)));
-}
-
TEST_F(PathUtilsTest, BuildOatPathInvalidIsa) {
EXPECT_THAT(BuildOatPath(
ArtifactsPath{.dexPath = "/a/b.apk", .isa = "invalid", .isInDalvikCache = false}),
@@ -109,51 +92,27 @@
EXPECT_THAT(BuildPrimaryRefProfilePath(
PrimaryRefProfilePath{.packageName = "...", .profileName = "primary"}),
HasValue(android_data_ + "/misc/profiles/ref/.../primary.prof"));
- EXPECT_THAT(BuildPrimaryRefProfilePath(
- PrimaryRefProfilePath{.packageName = "!@#$%^&*()_+-=", .profileName = "primary"}),
- HasValue(android_data_ + "/misc/profiles/ref/!@#$%^&*()_+-=/primary.prof"));
}
TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathPackageNameWrong) {
EXPECT_THAT(BuildPrimaryRefProfilePath(
- PrimaryRefProfilePath{.packageName = "", .profileName = "primary"}),
- HasError(WithMessage("packageName is empty")));
- EXPECT_THAT(BuildPrimaryRefProfilePath(
- PrimaryRefProfilePath{.packageName = ".", .profileName = "primary"}),
- HasError(WithMessage("Invalid packageName '.'")));
- EXPECT_THAT(BuildPrimaryRefProfilePath(
PrimaryRefProfilePath{.packageName = "..", .profileName = "primary"}),
HasError(WithMessage("Invalid packageName '..'")));
EXPECT_THAT(BuildPrimaryRefProfilePath(
PrimaryRefProfilePath{.packageName = "a/b", .profileName = "primary"}),
HasError(WithMessage("packageName 'a/b' has invalid character '/'")));
- EXPECT_THAT(BuildPrimaryRefProfilePath(
- PrimaryRefProfilePath{.packageName = "a\0b"s, .profileName = "primary"}),
- HasError(WithMessage("packageName 'a\0b' has invalid character '\\0'"s)));
}
TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathProfileNameOk) {
EXPECT_THAT(BuildPrimaryRefProfilePath(
- PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "."}),
- HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/..prof"));
- EXPECT_THAT(BuildPrimaryRefProfilePath(
PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = ".."}),
HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/...prof"));
- EXPECT_THAT(BuildPrimaryRefProfilePath(PrimaryRefProfilePath{.packageName = "com.android.foo",
- .profileName = "!@#$%^&*()_+-="}),
- HasValue(android_data_ + "/misc/profiles/ref/com.android.foo/!@#$%^&*()_+-=.prof"));
}
TEST_F(PathUtilsTest, BuildPrimaryRefProfilePathProfileNameWrong) {
EXPECT_THAT(BuildPrimaryRefProfilePath(
- PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = ""}),
- HasError(WithMessage("profileName is empty")));
- EXPECT_THAT(BuildPrimaryRefProfilePath(
PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "a/b"}),
HasError(WithMessage("profileName 'a/b' has invalid character '/'")));
- EXPECT_THAT(BuildPrimaryRefProfilePath(
- PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "a\0b"s}),
- HasError(WithMessage("profileName 'a\0b' has invalid character '\\0'"s)));
}
TEST_F(PathUtilsTest, BuildFinalProfilePathForPrimary) {
@@ -193,18 +152,8 @@
EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{
.finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo",
.profileName = "primary"},
- .id = ""}),
- HasError(WithMessage("id is empty")));
- EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{
- .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo",
- .profileName = "primary"},
.id = "123/45"}),
HasError(WithMessage("id '123/45' has invalid character '/'")));
- EXPECT_THAT(BuildTmpProfilePath(TmpProfilePath{
- .finalPath = PrimaryRefProfilePath{.packageName = "com.android.foo",
- .profileName = "primary"},
- .id = "123\0a"s}),
- HasError(WithMessage("id '123\0a' has invalid character '\\0'"s)));
}
TEST_F(PathUtilsTest, BuildPrebuiltProfilePath) {
diff --git a/libartservice/service/Android.bp b/libartservice/service/Android.bp
index 6287acc..19b3bef 100644
--- a/libartservice/service/Android.bp
+++ b/libartservice/service/Android.bp
@@ -30,8 +30,11 @@
srcs: [
"native/service.cc",
],
+ export_include_dirs: ["native"],
shared_libs: [
+ "libarttools", // Contains "libc++fs".
"libbase",
+ "libnativehelper",
],
}
@@ -43,6 +46,8 @@
"com.android.art.debug",
],
shared_libs: [
+ "libart",
+ "libartbase",
],
}
@@ -57,6 +62,8 @@
"com.android.art.debug",
],
shared_libs: [
+ "libartd",
+ "libartbased",
],
}
@@ -178,6 +185,9 @@
srcs: [
"native/service_test.cc",
],
+ static_libs: [
+ "libgmock",
+ ],
}
// Version of ART gtest `art_libartservice_tests` bundled with the ART APEX on target.
@@ -230,6 +240,7 @@
],
jni_libs: [
+ "libartservice",
// The two libraries below are required by ExtendedMockito.
"libdexmakerjvmtiagent",
"libstaticjvmtiagent",
diff --git a/libartservice/service/java/com/android/server/art/ArtJni.java b/libartservice/service/java/com/android/server/art/ArtJni.java
new file mode 100644
index 0000000..2cc9ea8
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/ArtJni.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.art;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import dalvik.system.VMRuntime;
+
+/**
+ * JNI methods for ART Service, with wrappers added for testability because Mockito cannot mock JNI
+ * methods.
+ *
+ * @hide
+ */
+public class ArtJni {
+ static {
+ if (VMRuntime.getRuntime().vmLibrary().equals("libartd.so")) {
+ System.loadLibrary("artserviced");
+ } else {
+ System.loadLibrary("artservice");
+ }
+ }
+
+ private ArtJni() {}
+
+ /**
+ * Returns an error message if the given dex path is invalid, or null if the validation passes.
+ */
+ @Nullable
+ public static String validateDexPath(@NonNull String dexPath) {
+ return validateDexPathNative(dexPath);
+ }
+
+ /**
+ * Returns an error message if the given class loader context is invalid, or null if the
+ * validation passes.
+ */
+ @Nullable
+ public static String validateClassLoaderContext(
+ @NonNull String dexPath, @NonNull String classLoaderContext) {
+ return validateClassLoaderContextNative(dexPath, classLoaderContext);
+ }
+
+ @Nullable private static native String validateDexPathNative(@NonNull String dexPath);
+ @Nullable
+ private static native String validateClassLoaderContextNative(
+ @NonNull String dexPath, @NonNull String classLoaderContext);
+}
diff --git a/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java
index 97b3c8a..c73f71b 100644
--- a/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java
@@ -566,7 +566,8 @@
}
mDexUse = new DexUse();
if (proto != null) {
- mDexUse.fromProto(proto, this::validateDexPath, this::validateClassLoaderContext);
+ mDexUse.fromProto(
+ proto, ArtJni::validateDexPath, ArtJni::validateClassLoaderContext);
}
}
}
@@ -600,12 +601,12 @@
for (var entry : classLoaderContextByDexContainerFile.entrySet()) {
Utils.assertNonEmpty(entry.getKey());
- String errorMsg = validateDexPath(entry.getKey());
+ String errorMsg = ArtJni.validateDexPath(entry.getKey());
if (errorMsg != null) {
throw new IllegalArgumentException(errorMsg);
}
Utils.assertNonEmpty(entry.getValue());
- errorMsg = validateClassLoaderContext(entry.getKey(), entry.getValue());
+ errorMsg = ArtJni.validateClassLoaderContext(entry.getKey(), entry.getValue());
if (errorMsg != null) {
throw new IllegalArgumentException(errorMsg);
}
@@ -623,29 +624,6 @@
}
}
- @Nullable
- private String validateDexPath(@NonNull String dexPath) {
- try {
- return mInjector.getArtd().validateDexPath(dexPath);
- } catch (RemoteException e) {
- String errorMsg = "Failed to validate dex path " + dexPath;
- Log.e(TAG, errorMsg, e);
- return errorMsg;
- }
- }
-
- @Nullable
- private String validateClassLoaderContext(
- @NonNull String dexPath, @NonNull String classLoaderContext) {
- try {
- return mInjector.getArtd().validateClassLoaderContext(dexPath, classLoaderContext);
- } catch (RemoteException e) {
- String errorMsg = "Failed to validate class loader context " + classLoaderContext;
- Log.e(TAG, errorMsg, e);
- return errorMsg;
- }
- }
-
/** @hide */
@Nullable
public String getSecondaryClassLoaderContext(
diff --git a/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java
index f34cd43..83cb81c 100644
--- a/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java
+++ b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java
@@ -81,8 +81,8 @@
private static final String SPLIT_APK = "/data/app/" + OWNING_PKG_NAME + "/split_0.apk";
@Rule
- public StaticMockitoRule mockitoRule =
- new StaticMockitoRule(SystemProperties.class, Constants.class, Process.class);
+ public StaticMockitoRule mockitoRule = new StaticMockitoRule(
+ SystemProperties.class, Constants.class, Process.class, ArtJni.class);
private final UserHandle mUserHandle = Binder.getCallingUserHandle();
@@ -155,8 +155,8 @@
mTempFile = File.createTempFile("package-dex-usage", ".pb");
mTempFile.deleteOnExit();
- lenient().when(mArtd.validateDexPath(any())).thenReturn(null);
- lenient().when(mArtd.validateClassLoaderContext(any(), any())).thenReturn(null);
+ lenient().when(ArtJni.validateDexPath(any())).thenReturn(null);
+ lenient().when(ArtJni.validateClassLoaderContext(any(), any())).thenReturn(null);
lenient().when(mInjector.getArtd()).thenReturn(mArtd);
lenient().when(mInjector.getCurrentTimeMillis()).thenReturn(0l);
@@ -712,14 +712,14 @@
@Test(expected = IllegalArgumentException.class)
public void testInvalidDexPath() throws Exception {
- lenient().when(mArtd.validateDexPath(any())).thenReturn("invalid");
+ lenient().when(ArtJni.validateDexPath(any())).thenReturn("invalid");
mDexUseManager.notifyDexContainersLoaded(
mSnapshot, OWNING_PKG_NAME, Map.of("/a/b.jar", "PCL[]"));
}
@Test(expected = IllegalArgumentException.class)
public void testInvalidClassLoaderContext() throws Exception {
- lenient().when(mArtd.validateClassLoaderContext(any(), any())).thenReturn("invalid");
+ lenient().when(ArtJni.validateClassLoaderContext(any(), any())).thenReturn("invalid");
mDexUseManager.notifyDexContainersLoaded(
mSnapshot, OWNING_PKG_NAME, Map.of("/a/b.jar", "PCL[]"));
}
diff --git a/libartservice/service/native/service.cc b/libartservice/service/native/service.cc
index d33cb59..7d223e5 100644
--- a/libartservice/service/native/service.cc
+++ b/libartservice/service/native/service.cc
@@ -16,12 +16,105 @@
#include "service.h"
+#include <jni.h>
+
+#include <filesystem>
+
+#include "android-base/errors.h"
+#include "android-base/file.h"
+#include "android-base/result.h"
+#include "class_loader_context.h"
+#include "nativehelper/utils.h"
+
namespace art {
namespace service {
-std::string getMsg() {
- return "hello world!";
+using ::android::base::Dirname;
+using ::android::base::Result;
+
+Result<void> ValidateAbsoluteNormalPath(const std::string& path_str) {
+ if (path_str.empty()) {
+ return Errorf("Path is empty");
+ }
+ if (path_str.find('\0') != std::string::npos) {
+ return Errorf("Path '{}' has invalid character '\\0'", path_str);
+ }
+ std::filesystem::path path(path_str);
+ if (!path.is_absolute()) {
+ return Errorf("Path '{}' is not an absolute path", path_str);
+ }
+ if (path.lexically_normal() != path_str) {
+ return Errorf("Path '{}' is not in normal form", path_str);
+ }
+ return {};
}
+Result<void> ValidatePathElementSubstring(const std::string& path_element_substring,
+ const std::string& name) {
+ if (path_element_substring.empty()) {
+ return Errorf("{} is empty", name);
+ }
+ if (path_element_substring.find('/') != std::string::npos) {
+ return Errorf("{} '{}' has invalid character '/'", name, path_element_substring);
+ }
+ if (path_element_substring.find('\0') != std::string::npos) {
+ return Errorf("{} '{}' has invalid character '\\0'", name, path_element_substring);
+ }
+ return {};
}
+
+Result<void> ValidatePathElement(const std::string& path_element, const std::string& name) {
+ OR_RETURN(ValidatePathElementSubstring(path_element, name));
+ if (path_element == "." || path_element == "..") {
+ return Errorf("Invalid {} '{}'", name, path_element);
+ }
+ return {};
}
+
+Result<void> ValidateDexPath(const std::string& dex_path) {
+ OR_RETURN(ValidateAbsoluteNormalPath(dex_path));
+ return {};
+}
+
+extern "C" JNIEXPORT jstring JNICALL
+Java_com_android_server_art_ArtJni_validateDexPathNative(JNIEnv* env, jobject, jstring j_dex_path) {
+ std::string dex_path(GET_UTF_OR_RETURN(env, j_dex_path));
+
+ if (Result<void> result = ValidateDexPath(dex_path); !result.ok()) {
+ return CREATE_UTF_OR_RETURN(env, result.error().message()).release();
+ } else {
+ return nullptr;
+ }
+}
+
+extern "C" JNIEXPORT jstring JNICALL
+Java_com_android_server_art_ArtJni_validateClassLoaderContextNative(
+ JNIEnv* env, jobject, jstring j_dex_path, jstring j_class_loader_context) {
+ ScopedUtfChars dex_path = GET_UTF_OR_RETURN(env, j_dex_path);
+ std::string class_loader_context(GET_UTF_OR_RETURN(env, j_class_loader_context));
+
+ if (class_loader_context == ClassLoaderContext::kUnsupportedClassLoaderContextEncoding) {
+ return nullptr;
+ }
+
+ std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(class_loader_context);
+ if (context == nullptr) {
+ return CREATE_UTF_OR_RETURN(
+ env, ART_FORMAT("Class loader context '{}' is invalid", class_loader_context))
+ .release();
+ }
+
+ std::vector<std::string> flattened_context = context->FlattenDexPaths();
+ std::string dex_dir = Dirname(dex_path);
+ for (const std::string& context_element : flattened_context) {
+ std::string context_path = std::filesystem::path(dex_dir).append(context_element);
+ if (Result<void> result = ValidateDexPath(context_path); !result.ok()) {
+ return CREATE_UTF_OR_RETURN(env, result.error().message()).release();
+ }
+ }
+
+ return nullptr;
+}
+
+} // namespace service
+} // namespace art
diff --git a/libartservice/service/native/service.h b/libartservice/service/native/service.h
index 2b680a2..7f43f17 100644
--- a/libartservice/service/native/service.h
+++ b/libartservice/service/native/service.h
@@ -19,10 +19,20 @@
#include <string>
+#include "android-base/result.h"
+
namespace art {
namespace service {
-std::string getMsg();
+android::base::Result<void> ValidateAbsoluteNormalPath(const std::string& path_str);
+
+android::base::Result<void> ValidatePathElementSubstring(const std::string& path_element_substring,
+ const std::string& name);
+
+android::base::Result<void> ValidatePathElement(const std::string& path_element,
+ const std::string& name);
+
+android::base::Result<void> ValidateDexPath(const std::string& dex_path);
} // namespace service
} // namespace art
diff --git a/libartservice/service/native/service_test.cc b/libartservice/service/native/service_test.cc
index eb84b73..8300bf5 100644
--- a/libartservice/service/native/service_test.cc
+++ b/libartservice/service/native/service_test.cc
@@ -15,14 +15,97 @@
*/
#include "service.h"
+
+#include "android-base/result-gmock.h"
#include "gtest/gtest.h"
namespace art {
+namespace service {
+namespace {
+
+using ::android::base::testing::HasError;
+using ::android::base::testing::Ok;
+using ::android::base::testing::WithMessage;
+
+using std::literals::operator""s; // NOLINT
class ArtServiceTest : public testing::Test {};
-TEST_F(ArtServiceTest, Hello) {
- EXPECT_EQ("hello world!", art::service::getMsg());
+TEST_F(ArtServiceTest, ValidatePathElementOk) {
+ EXPECT_THAT(ValidatePathElement("com.android.foo", "packageName"), Ok());
+ EXPECT_THAT(ValidatePathElement("...", "packageName"), Ok());
+ EXPECT_THAT(ValidatePathElement("!@#$%^&*()_+-=", "packageName"), Ok());
}
+TEST_F(ArtServiceTest, ValidatePathElementEmpty) {
+ EXPECT_THAT(ValidatePathElement("", "packageName"),
+ HasError(WithMessage("packageName is empty")));
+}
+
+TEST_F(ArtServiceTest, ValidatePathElementDot) {
+ EXPECT_THAT(ValidatePathElement(".", "packageName"),
+ HasError(WithMessage("Invalid packageName '.'")));
+}
+
+TEST_F(ArtServiceTest, ValidatePathElementDotDot) {
+ EXPECT_THAT(ValidatePathElement("..", "packageName"),
+ HasError(WithMessage("Invalid packageName '..'")));
+}
+
+TEST_F(ArtServiceTest, ValidatePathElementSlash) {
+ EXPECT_THAT(ValidatePathElement("a/b", "packageName"),
+ HasError(WithMessage("packageName 'a/b' has invalid character '/'")));
+}
+
+TEST_F(ArtServiceTest, ValidatePathElementNul) {
+ EXPECT_THAT(ValidatePathElement("a\0b"s, "packageName"),
+ HasError(WithMessage("packageName 'a\0b' has invalid character '\\0'"s)));
+}
+
+TEST_F(ArtServiceTest, ValidatePathElementSubstringOk) {
+ EXPECT_THAT(ValidatePathElementSubstring("com.android.foo", "packageName"), Ok());
+ EXPECT_THAT(ValidatePathElementSubstring(".", "packageName"), Ok());
+ EXPECT_THAT(ValidatePathElementSubstring("..", "packageName"), Ok());
+ EXPECT_THAT(ValidatePathElementSubstring("...", "packageName"), Ok());
+ EXPECT_THAT(ValidatePathElementSubstring("!@#$%^&*()_+-=", "packageName"), Ok());
+}
+
+TEST_F(ArtServiceTest, ValidatePathElementSubstringEmpty) {
+ EXPECT_THAT(ValidatePathElementSubstring("", "packageName"),
+ HasError(WithMessage("packageName is empty")));
+}
+
+TEST_F(ArtServiceTest, ValidatePathElementSubstringSlash) {
+ EXPECT_THAT(ValidatePathElementSubstring("a/b", "packageName"),
+ HasError(WithMessage("packageName 'a/b' has invalid character '/'")));
+}
+
+TEST_F(ArtServiceTest, ValidatePathElementSubstringNul) {
+ EXPECT_THAT(ValidatePathElementSubstring("a\0b"s, "packageName"),
+ HasError(WithMessage("packageName 'a\0b' has invalid character '\\0'"s)));
+}
+
+TEST_F(ArtServiceTest, ValidateDexPathOk) { EXPECT_THAT(ValidateDexPath("/a/b.apk"), Ok()); }
+
+TEST_F(ArtServiceTest, ValidateDexPathEmpty) {
+ EXPECT_THAT(ValidateDexPath(""), HasError(WithMessage("Path is empty")));
+}
+
+TEST_F(ArtServiceTest, ValidateDexPathRelative) {
+ EXPECT_THAT(ValidateDexPath("a/b.apk"),
+ HasError(WithMessage("Path 'a/b.apk' is not an absolute path")));
+}
+
+TEST_F(ArtServiceTest, ValidateDexPathNonNormal) {
+ EXPECT_THAT(ValidateDexPath("/a/c/../b.apk"),
+ HasError(WithMessage("Path '/a/c/../b.apk' is not in normal form")));
+}
+
+TEST_F(ArtServiceTest, ValidateDexPathNul) {
+ EXPECT_THAT(ValidateDexPath("/a/\0/b.apk"s),
+ HasError(WithMessage("Path '/a/\0/b.apk' has invalid character '\\0'"s)));
+}
+
+} // namespace
+} // namespace service
} // namespace art