Fix Core Platform API JNI check
Fix an inverted flag around kIsTargetBuild that broke check.
Simplify SharedObjectKind's so there are only values - part of the ART
module and other. Being part of the module is equivalent to
DexDomain::CorePlatform.
Add a test for Core Platform API checks for native code. The test is
derived from 674-hiddenapi, and separate because the existing test is
already complex.
Add 2030-core-platform-api-jni to the same knownfailures as
674-hiddenapi since the newer test likely to have the same failures.
Bug: 128517070
Bug: 144502743
Test: art/test/run-test --host 2030
Test: art/test/run-test 2030
Test: erroneous warnings gone from master
Change-Id: I1a365a28b89bd46b3e403f6deb736f350c4baf10
diff --git a/runtime/hidden_api_jni.cc b/runtime/hidden_api_jni.cc
index 074c4c3..2e32224 100644
--- a/runtime/hidden_api_jni.cc
+++ b/runtime/hidden_api_jni.cc
@@ -25,6 +25,7 @@
#include <mutex>
#include "android-base/logging.h"
+#include "android-base/thread_annotations.h"
#include "unwindstack/Regs.h"
#include "unwindstack/RegsGetLocal.h"
@@ -45,7 +46,7 @@
// The maximum number of frames to back trace through when performing Core Platform API checks of
// native code.
-static constexpr size_t kMaxFrames = 3;
+static constexpr size_t kMaxFramesForHiddenApiJniCheck = 3;
static std::mutex gUnwindingMutex;
@@ -73,25 +74,16 @@
};
static UnwindHelper& GetUnwindHelper() {
- static UnwindHelper helper(kMaxFrames);
+ static UnwindHelper helper(kMaxFramesForHiddenApiJniCheck);
return helper;
}
} // namespace
-enum class SharedObjectKind {
- kRuntime = 0,
- kApexModule = 1,
- kOther = 2
-};
-
std::ostream& operator<<(std::ostream& os, SharedObjectKind kind) {
switch (kind) {
- case SharedObjectKind::kRuntime:
- os << "Runtime";
- break;
- case SharedObjectKind::kApexModule:
- os << "APEX Module";
+ case SharedObjectKind::kArtModule:
+ os << "ART module";
break;
case SharedObjectKind::kOther:
os << "Other";
@@ -118,12 +110,9 @@
return SharedObjectKind::kOther;
}
- bool HasCache() const {
- return memory_type_table_.Size() != 0;
- }
-
void BuildCache() {
- DCHECK(!HasCache());
+ std::lock_guard<std::mutex> guard(mutex_);
+ DCHECK_EQ(memory_type_table_.Size(), 0u);
art::MemoryTypeTable<SharedObjectKind>::Builder builder;
builder_ = &builder;
libjavacore_loaded_ = false;
@@ -141,7 +130,18 @@
builder_ = nullptr;
}
+ void SetLibraryPathClassifier(JniLibraryPathClassifier* fc_classifier) {
+ std::lock_guard<std::mutex> guard(mutex_);
+ fc_classifier_ = fc_classifier;
+ }
+
+ bool HasLibraryPathClassifier() const {
+ std::lock_guard<std::mutex> guard(mutex_);
+ return fc_classifier_ != nullptr;
+ }
+
void DropCache() {
+ const std::lock_guard<std::mutex> guard(mutex_);
memory_type_table_ = {};
}
@@ -149,6 +149,7 @@
CodeRangeCache() {}
bool Find(uintptr_t address, SharedObjectKind* kind) const {
+ std::lock_guard<std::mutex> guard(mutex_);
const art::MemoryTypeRange<SharedObjectKind>* range = memory_type_table_.Lookup(address);
if (range == nullptr) {
return false;
@@ -169,7 +170,14 @@
}
uintptr_t start = info->dlpi_addr + phdr.p_vaddr;
const uintptr_t limit = art::RoundUp(start + phdr.p_memsz, art::kPageSize);
- SharedObjectKind kind = GetKind(info->dlpi_name, start, limit);
+ SharedObjectKind kind = GetKind(info->dlpi_name);
+ if (cache->fc_classifier_ != nullptr) {
+ std::optional<SharedObjectKind> maybe_kind =
+ cache->fc_classifier_->Classify(info->dlpi_name);
+ if (maybe_kind.has_value()) {
+ kind = maybe_kind.value();
+ }
+ }
art::MemoryTypeRange<SharedObjectKind> range{start, limit, kind};
if (!builder->Add(range)) {
LOG(WARNING) << "Overlapping/invalid range found in ELF headers: " << range;
@@ -191,25 +199,29 @@
return 0;
}
- static SharedObjectKind GetKind(const char* so_name, uintptr_t start, uintptr_t limit) {
- uintptr_t runtime_method = reinterpret_cast<uintptr_t>(CodeRangeCache::GetKind);
- if (runtime_method >= start && runtime_method < limit) {
- return SharedObjectKind::kRuntime;
- }
- return art::LocationIsOnApex(so_name) ? SharedObjectKind::kApexModule
- : SharedObjectKind::kOther;
+ static SharedObjectKind GetKind(const char* so_name) {
+ return art::LocationIsOnArtModule(so_name) ? SharedObjectKind::kArtModule
+ : SharedObjectKind::kOther;
}
- art::MemoryTypeTable<SharedObjectKind> memory_type_table_;
-
// Table builder, only valid during BuildCache().
- art::MemoryTypeTable<SharedObjectKind>::Builder* builder_;
+ art::MemoryTypeTable<SharedObjectKind>::Builder* builder_ GUARDED_BY(mutex_) = nullptr;
+
+ // Table for mapping PC addresses to their shared object files.
+ art::MemoryTypeTable<SharedObjectKind> memory_type_table_ GUARDED_BY(mutex_);
+
+ // Classifier used to override shared object classifications during tests.
+ JniLibraryPathClassifier* fc_classifier_ GUARDED_BY(mutex_) = nullptr;
// Sanity checking state.
bool libjavacore_loaded_;
bool libnativehelper_loaded_;
bool libopenjdk_loaded_;
+ // Mutex to protect fc_classifier_ and related state during testing. Outside of testing we
+ // only generate the |memory_type_table_| once.
+ mutable std::mutex mutex_;
+
static constexpr std::string_view kLibjavacore = "libjavacore.so";
static constexpr std::string_view kLibnativehelper = "libnativehelper.so";
static constexpr std::string_view kLibopenjdk = art::kIsDebugBuild ? "libopenjdkd.so"
@@ -231,21 +243,39 @@
CorePlatformApiCookie cookie =
bit_cast<CorePlatformApiCookie, uint32_t>(self->CorePlatformApiCookie());
bool is_core_platform_api_approved = false; // Default value for non-device testing.
- if (!kIsTargetBuild) {
- // On target device, if policy says enforcement is disabled, then treat all callers as
- // approved.
+ const bool is_under_test = CodeRangeCache::GetSingleton().HasLibraryPathClassifier();
+ if (kIsTargetBuild || is_under_test) {
+ // On target device (or running tests). If policy says enforcement is disabled,
+ // then treat all callers as approved.
auto policy = Runtime::Current()->GetCorePlatformApiEnforcementPolicy();
if (policy == hiddenapi::EnforcementPolicy::kDisabled) {
is_core_platform_api_approved = true;
} else if (cookie.depth == 0) {
- // On target device, only check the caller at depth 0 (the outermost entry into JNI
- // interface).
+ // On target device, only check the caller at depth 0 which corresponds to the outermost
+ // entry into the JNI interface. When performing the check here, we note that |*this| is
+ // stack allocated at entry points to JNI field and method resolution |*methods. We can use
+ // the address of |this| to find the callers frame.
DCHECK_EQ(cookie.approved, false);
- void* caller_pc = CaptureCallerPc();
+ void* caller_pc = nullptr;
+ {
+ std::lock_guard<std::mutex> guard(gUnwindingMutex);
+ unwindstack::Unwinder* unwinder = GetUnwindHelper().Unwinder();
+ std::unique_ptr<unwindstack::Regs> regs(unwindstack::Regs::CreateFromLocal());
+ RegsGetLocal(regs.get());
+ unwinder->SetRegs(regs.get());
+ unwinder->Unwind();
+ for (auto it = unwinder->frames().begin(); it != unwinder->frames().end(); ++it) {
+ // Unwind to frame above the tlsJniStackMarker. The stack markers should be on the first
+ // frame calling JNI methods.
+ if (it->sp > reinterpret_cast<uint64_t>(this)) {
+ caller_pc = reinterpret_cast<void*>(it->pc);
+ break;
+ }
+ }
+ }
if (caller_pc != nullptr) {
SharedObjectKind kind = CodeRangeCache::GetSingleton().GetSharedObjectKind(caller_pc);
- is_core_platform_api_approved = ((kind == SharedObjectKind::kRuntime) ||
- (kind == SharedObjectKind::kApexModule));
+ is_core_platform_api_approved = (kind == SharedObjectKind::kArtModule);
}
}
}
@@ -279,30 +309,15 @@
return cookie.approved;
}
-void* ScopedCorePlatformApiCheck::CaptureCallerPc() {
- std::lock_guard<std::mutex> guard(gUnwindingMutex);
- unwindstack::Unwinder* unwinder = GetUnwindHelper().Unwinder();
- std::unique_ptr<unwindstack::Regs> regs(unwindstack::Regs::CreateFromLocal());
- RegsGetLocal(regs.get());
- unwinder->SetRegs(regs.get());
- unwinder->Unwind();
- for (auto it = unwinder->frames().begin(); it != unwinder->frames().end(); ++it) {
- // Unwind to frame above the tlsJniStackMarker. The stack markers should be on the first frame
- // calling JNI methods.
- if (it->sp > reinterpret_cast<uint64_t>(this)) {
- return reinterpret_cast<void*>(it->pc);
- }
- }
- return nullptr;
-}
-
-void JniInitializeNativeCallerCheck() {
+void JniInitializeNativeCallerCheck(JniLibraryPathClassifier* classifier) {
// This method should be called only once and before there are multiple runtime threads.
- DCHECK(!CodeRangeCache::GetSingleton().HasCache());
+ CodeRangeCache::GetSingleton().DropCache();
+ CodeRangeCache::GetSingleton().SetLibraryPathClassifier(classifier);
CodeRangeCache::GetSingleton().BuildCache();
}
void JniShutdownNativeCallerCheck() {
+ CodeRangeCache::GetSingleton().SetLibraryPathClassifier(nullptr);
CodeRangeCache::GetSingleton().DropCache();
}
@@ -322,7 +337,7 @@
return false;
}
-void JniInitializeNativeCallerCheck() {}
+void JniInitializeNativeCallerCheck(JniLibraryPathClassifier* f ATTRIBUTE_UNUSED) {}
void JniShutdownNativeCallerCheck() {}
diff --git a/runtime/hidden_api_jni.h b/runtime/hidden_api_jni.h
index a084378..15de6ef 100644
--- a/runtime/hidden_api_jni.h
+++ b/runtime/hidden_api_jni.h
@@ -17,6 +17,8 @@
#ifndef ART_RUNTIME_HIDDEN_API_JNI_H_
#define ART_RUNTIME_HIDDEN_API_JNI_H_
+#include <optional>
+
#include "base/macros.h"
namespace art {
@@ -37,16 +39,25 @@
static bool IsCurrentCallerApproved(Thread* self);
private:
- // Captures calling PC for frame above the frame allocating the current ScopedCorePlatformApiCheck
- // instance.
- void* CaptureCallerPc();
-
// Instances should only be stack allocated, copy and assignment not useful.
DISALLOW_ALLOCATION();
DISALLOW_COPY_AND_ASSIGN(ScopedCorePlatformApiCheck);
};
-void JniInitializeNativeCallerCheck();
+// Kind of memory page from mapped shared object files.
+enum class SharedObjectKind {
+ kArtModule = 0, // Part of the ART module.
+ kOther = 1 // Neither of the above.
+};
+
+
+class JniLibraryPathClassifier {
+ public:
+ virtual std::optional<SharedObjectKind> Classify(const char* so_name) = 0;
+ virtual ~JniLibraryPathClassifier() {}
+};
+
+void JniInitializeNativeCallerCheck(JniLibraryPathClassifier* fc = nullptr);
void JniShutdownNativeCallerCheck();
} // namespace hiddenapi
diff --git a/runtime/jni/jni_internal.cc b/runtime/jni/jni_internal.cc
index 882e10f..887ad9a 100644
--- a/runtime/jni/jni_internal.cc
+++ b/runtime/jni/jni_internal.cc
@@ -88,9 +88,17 @@
template<typename T>
ALWAYS_INLINE static bool ShouldDenyAccessToMember(T* member, Thread* self)
REQUIRES_SHARED(Locks::mutator_lock_) {
- if (hiddenapi::ScopedCorePlatformApiCheck::IsCurrentCallerApproved(self)) {
+ const bool native_caller_trusted =
+ hiddenapi::ScopedCorePlatformApiCheck::IsCurrentCallerApproved(self);
+ if (native_caller_trusted) {
+ // A trusted caller is in the same domain as the ART module so is assumed to always have
+ // access to the APIs that the module provides.
return false;
}
+
+ // Construct AccessContext from the first calling class on stack.
+ // If the calling class cannot be determined, e.g. unattached threads,
+ // we conservatively assume the caller is trusted.
return hiddenapi::ShouldDenyAccessToMember(
member,
[&]() REQUIRES_SHARED(Locks::mutator_lock_) {
diff --git a/test/2030-core-platform-api-jni/build b/test/2030-core-platform-api-jni/build
new file mode 100644
index 0000000..330a6de
--- /dev/null
+++ b/test/2030-core-platform-api-jni/build
@@ -0,0 +1,38 @@
+#!/bin/bash
+#
+# Copyright 2018 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.
+
+set -e
+
+# Build the jars twice. First with applying hiddenapi, creating a boot jar, then
+# a second time without to create a normal jar. We need to do this because we
+# want to load the jar once as an app module and once as a member of the boot
+# class path. The DexFileVerifier would fail on the former as it does not allow
+# hidden API access flags in dex files. DexFileVerifier is not invoked on boot
+# class path dex files, so the boot jar loads fine in the latter case.
+
+export USE_HIDDENAPI=true
+./default-build "$@"
+
+# Move the jar file into the resource folder to be bundled with the test.
+mkdir res
+mv ${TEST_NAME}.jar res/boot.jar
+
+# Clear all intermediate files otherwise default-build would either skip
+# compilation or fail rebuilding.
+rm -rf classes*
+
+export USE_HIDDENAPI=false
+./default-build "$@"
diff --git a/test/2030-core-platform-api-jni/check b/test/2030-core-platform-api-jni/check
new file mode 100644
index 0000000..c319a0a
--- /dev/null
+++ b/test/2030-core-platform-api-jni/check
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 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.
+
+# Remove pid and date from the log messages.
+grep -vE '^dalvikvm(32|64) E [^]]+]' "$2" \
+ | grep -v JNI_OnLoad \
+ | grep -v JNI_OnUnload \
+ > "$2.tmp"
+
+./default-check "$1" "$2.tmp"
diff --git a/test/2030-core-platform-api-jni/core-platform-api-jni.cc b/test/2030-core-platform-api-jni/core-platform-api-jni.cc
new file mode 100644
index 0000000..525982f
--- /dev/null
+++ b/test/2030-core-platform-api-jni/core-platform-api-jni.cc
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include "hidden_api.h"
+#include "hidden_api_jni.h"
+#include "jni.h"
+#include "runtime.h"
+
+#include "nativehelper/JNIHelp.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "nativehelper/ScopedUtfChars.h"
+
+class TestLibraryPathClassifier : public art::hiddenapi::JniLibraryPathClassifier {
+ public:
+ std::optional<art::hiddenapi::SharedObjectKind> Classify(const char* so_path) override {
+ // so_path is the path to a shared object. We have the filename minus suffix (expected to be
+ // .so).
+ const char* last_separator = strrchr(so_path, '/');
+ std::string_view filename = (last_separator != nullptr) ? last_separator + 1 : so_path;
+ if (filename == so_name_) {
+ return so_kind_;
+ }
+ return {};
+ }
+
+ void Configure(const char* so_file, art::hiddenapi::SharedObjectKind kind) {
+ so_name_ = so_file;
+ so_kind_ = kind;
+ }
+
+ private:
+ std::string so_name_;
+ art::hiddenapi::SharedObjectKind so_kind_ = art::hiddenapi::SharedObjectKind::kOther;
+};
+
+static TestLibraryPathClassifier* GetLibraryPathClassifier() {
+ static TestLibraryPathClassifier g_classifier;
+ return &g_classifier;
+}
+
+static void InstallLibraryPathClassifier(JNIEnv* env,
+ jstring j_library_path,
+ art::hiddenapi::SharedObjectKind kind) {
+ ScopedUtfChars library_path(env, j_library_path);
+ const char* last_separator = strrchr(library_path.c_str(), '/');
+ const char* library_so = (last_separator != nullptr) ? last_separator + 1 : library_path.c_str();
+ GetLibraryPathClassifier()->Configure(library_so, kind);
+ art::hiddenapi::JniInitializeNativeCallerCheck(GetLibraryPathClassifier());
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_treatAsArtModule(JNIEnv* env,
+ jclass /*klass*/,
+ jstring library_name) {
+ InstallLibraryPathClassifier(env, library_name, art::hiddenapi::SharedObjectKind::kArtModule);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_treatAsOtherLibrary(JNIEnv* env,
+ jclass /*klass*/,
+ jstring library_name) {
+ InstallLibraryPathClassifier(env, library_name, art::hiddenapi::SharedObjectKind::kOther);
+}
diff --git a/test/2030-core-platform-api-jni/expected.txt b/test/2030-core-platform-api-jni/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2030-core-platform-api-jni/expected.txt
diff --git a/test/2030-core-platform-api-jni/hiddenapi-flags.csv b/test/2030-core-platform-api-jni/hiddenapi-flags.csv
new file mode 100644
index 0000000..42626f7
--- /dev/null
+++ b/test/2030-core-platform-api-jni/hiddenapi-flags.csv
@@ -0,0 +1,108 @@
+LNullaryConstructorBlacklistAndCorePlatformApi;-><init>()V,blacklist,core-platform-api
+LNullaryConstructorBlacklist;-><init>()V,blacklist
+LNullaryConstructorDarkGreylist;-><init>()V,greylist-max-o
+LNullaryConstructorLightGreylist;-><init>()V,greylist
+LParentClass;->fieldPackageBlacklistAndCorePlatformApi:I,blacklist,core-platform-api
+LParentClass;->fieldPackageBlacklist:I,blacklist
+LParentClass;->fieldPackageDarkGreylist:I,greylist-max-o
+LParentClass;->fieldPackageLightGreylist:I,greylist
+LParentClass;->fieldPackageStaticBlacklistAndCorePlatformApi:I,blacklist,core-platform-api
+LParentClass;->fieldPackageStaticBlacklist:I,blacklist
+LParentClass;->fieldPackageStaticDarkGreylist:I,greylist-max-o
+LParentClass;->fieldPackageStaticLightGreylist:I,greylist
+LParentClass;->fieldPrivateBlacklistAndCorePlatformApi:I,blacklist,core-platform-api
+LParentClass;->fieldPrivateBlacklist:I,blacklist
+LParentClass;->fieldPrivateDarkGreylist:I,greylist-max-o
+LParentClass;->fieldPrivateLightGreylist:I,greylist
+LParentClass;->fieldPrivateStaticBlacklistAndCorePlatformApi:I,blacklist,core-platform-api
+LParentClass;->fieldPrivateStaticBlacklist:I,blacklist
+LParentClass;->fieldPrivateStaticDarkGreylist:I,greylist-max-o
+LParentClass;->fieldPrivateStaticLightGreylist:I,greylist
+LParentClass;->fieldProtectedBlacklistAndCorePlatformApi:I,blacklist,core-platform-api
+LParentClass;->fieldProtectedBlacklist:I,blacklist
+LParentClass;->fieldProtectedDarkGreylist:I,greylist-max-o
+LParentClass;->fieldProtectedLightGreylist:I,greylist
+LParentClass;->fieldProtectedStaticBlacklistAndCorePlatformApi:I,blacklist,core-platform-api
+LParentClass;->fieldProtectedStaticBlacklist:I,blacklist
+LParentClass;->fieldProtectedStaticDarkGreylist:I,greylist-max-o
+LParentClass;->fieldProtectedStaticLightGreylist:I,greylist
+LParentClass;->fieldPublicBlacklistAndCorePlatformApiB:I,blacklist,core-platform-api
+LParentClass;->fieldPublicBlacklistAndCorePlatformApi:I,blacklist,core-platform-api
+LParentClass;->fieldPublicBlacklistB:I,blacklist
+LParentClass;->fieldPublicBlacklist:I,blacklist
+LParentClass;->fieldPublicDarkGreylistB:I,greylist-max-o
+LParentClass;->fieldPublicDarkGreylist:I,greylist-max-o
+LParentClass;->fieldPublicLightGreylistB:I,greylist
+LParentClass;->fieldPublicLightGreylist:I,greylist
+LParentClass;->fieldPublicStaticBlacklistAndCorePlatformApiB:I,blacklist,core-platform-api
+LParentClass;->fieldPublicStaticBlacklistAndCorePlatformApi:I,blacklist,core-platform-api
+LParentClass;->fieldPublicStaticBlacklistB:I,blacklist
+LParentClass;->fieldPublicStaticBlacklist:I,blacklist
+LParentClass;->fieldPublicStaticDarkGreylistB:I,greylist-max-o
+LParentClass;->fieldPublicStaticDarkGreylist:I,greylist-max-o
+LParentClass;->fieldPublicStaticLightGreylistB:I,greylist
+LParentClass;->fieldPublicStaticLightGreylist:I,greylist
+LParentClass;-><init>(DB)V,greylist-max-o
+LParentClass;-><init>(DC)V,blacklist
+LParentClass;-><init>(DI)V,blacklist,core-platform-api
+LParentClass;-><init>(DZ)V,greylist
+LParentClass;-><init>(FB)V,greylist-max-o
+LParentClass;-><init>(FC)V,blacklist
+LParentClass;-><init>(FI)V,blacklist,core-platform-api
+LParentClass;-><init>(FZ)V,greylist
+LParentClass;-><init>(IB)V,greylist-max-o
+LParentClass;-><init>(IC)V,blacklist
+LParentClass;-><init>(II)V,blacklist,core-platform-api
+LParentClass;-><init>(IZ)V,greylist
+LParentClass;-><init>(JB)V,greylist-max-o
+LParentClass;-><init>(JC)V,blacklist
+LParentClass;-><init>(JI)V,blacklist,core-platform-api
+LParentClass;-><init>(JZ)V,greylist
+LParentClass;->methodPackageBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
+LParentClass;->methodPackageBlacklist()I,blacklist
+LParentClass;->methodPackageDarkGreylist()I,greylist-max-o
+LParentClass;->methodPackageLightGreylist()I,greylist
+LParentClass;->methodPackageStaticBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
+LParentClass;->methodPackageStaticBlacklist()I,blacklist
+LParentClass;->methodPackageStaticDarkGreylist()I,greylist-max-o
+LParentClass;->methodPackageStaticLightGreylist()I,greylist
+LParentClass;->methodPrivateBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
+LParentClass;->methodPrivateBlacklist()I,blacklist
+LParentClass;->methodPrivateDarkGreylist()I,greylist-max-o
+LParentClass;->methodPrivateLightGreylist()I,greylist
+LParentClass;->methodPrivateStaticBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
+LParentClass;->methodPrivateStaticBlacklist()I,blacklist
+LParentClass;->methodPrivateStaticDarkGreylist()I,greylist-max-o
+LParentClass;->methodPrivateStaticLightGreylist()I,greylist
+LParentClass;->methodProtectedBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
+LParentClass;->methodProtectedBlacklist()I,blacklist
+LParentClass;->methodProtectedDarkGreylist()I,greylist-max-o
+LParentClass;->methodProtectedLightGreylist()I,greylist
+LParentClass;->methodProtectedStaticBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
+LParentClass;->methodProtectedStaticBlacklist()I,blacklist
+LParentClass;->methodProtectedStaticDarkGreylist()I,greylist-max-o
+LParentClass;->methodProtectedStaticLightGreylist()I,greylist
+LParentClass;->methodPublicBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
+LParentClass;->methodPublicBlacklist()I,blacklist
+LParentClass;->methodPublicDarkGreylist()I,greylist-max-o
+LParentClass;->methodPublicLightGreylist()I,greylist
+LParentClass;->methodPublicStaticBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
+LParentClass;->methodPublicStaticBlacklist()I,blacklist
+LParentClass;->methodPublicStaticDarkGreylist()I,greylist-max-o
+LParentClass;->methodPublicStaticLightGreylist()I,greylist
+LParentInterface;->fieldPublicStaticBlacklistAndCorePlatformApi:I,blacklist,core-platform-api
+LParentInterface;->fieldPublicStaticBlacklist:I,blacklist
+LParentInterface;->fieldPublicStaticDarkGreylist:I,greylist-max-o
+LParentInterface;->fieldPublicStaticLightGreylist:I,greylist
+LParentInterface;->methodPublicBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
+LParentInterface;->methodPublicBlacklist()I,blacklist
+LParentInterface;->methodPublicDarkGreylist()I,greylist-max-o
+LParentInterface;->methodPublicDefaultBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
+LParentInterface;->methodPublicDefaultBlacklist()I,blacklist
+LParentInterface;->methodPublicDefaultDarkGreylist()I,greylist-max-o
+LParentInterface;->methodPublicDefaultLightGreylist()I,greylist
+LParentInterface;->methodPublicLightGreylist()I,greylist
+LParentInterface;->methodPublicStaticBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
+LParentInterface;->methodPublicStaticBlacklist()I,blacklist
+LParentInterface;->methodPublicStaticDarkGreylist()I,greylist-max-o
+LParentInterface;->methodPublicStaticLightGreylist()I,greylist
diff --git a/test/2030-core-platform-api-jni/info.txt b/test/2030-core-platform-api-jni/info.txt
new file mode 100644
index 0000000..94aac1e
--- /dev/null
+++ b/test/2030-core-platform-api-jni/info.txt
@@ -0,0 +1,16 @@
+Test whether hidden API access flags are being enforced. The test is composed of
+two JARs. The first (parent) defines methods and fields and the second (child)
+tries to access them with reflection/JNI/MethodHandles or link against them.
+Note that the first is compiled twice - once with and once without hidden access
+flags.
+
+The test then proceeds to exercise the following combinations of class loading:
+(a) Both parent and child dex loaded with PathClassLoader, parent's class loader
+ is the child's class loader's parent. Access flags should not be enforced as
+ the parent does not belong to boot class path.
+(b) Parent is appended to boot class path, child is loaded with PathClassLoader.
+ In this situation child should not be able to access hidden methods/fields
+ of the parent.
+(c) Both parent and child are appended to boot class path. Restrictions should
+ not apply as hidden APIs are accessible within the boundaries of the boot
+ class path.
diff --git a/test/2030-core-platform-api-jni/run b/test/2030-core-platform-api-jni/run
new file mode 100755
index 0000000..2babeef
--- /dev/null
+++ b/test/2030-core-platform-api-jni/run
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright (C) 2019 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.
+
+# Make verification soft fail so that we can re-verify boot classpath
+# methods at runtime.
+exec ${RUN} $@ --verify-soft-fail
\ No newline at end of file
diff --git a/test/2030-core-platform-api-jni/src-art/Main.java b/test/2030-core-platform-api-jni/src-art/Main.java
new file mode 100644
index 0000000..d6601db
--- /dev/null
+++ b/test/2030-core-platform-api-jni/src-art/Main.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+import dalvik.system.PathClassLoader;
+import java.io.File;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.util.Arrays;
+
+public class Main {
+ // This needs to be kept in sync with DexDomain in ChildClass.
+ enum DexDomain {
+ CorePlatform,
+ Platform,
+ Application
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
+ prepareNativeLibFileName(args[0]);
+
+ // Enable hidden API checks in case they are disabled by default.
+ init();
+
+ // TODO there are sequential depencies between these test cases, and bugs
+ // in the production code may lead to subsequent tests to erroneously pass,
+ // or test the wrong thing. We rely on not deduping hidden API warnings
+ // here for the same reasons), meaning the code under test and production
+ // code are running in different configurations. Each test should be run in
+ // a fresh process to ensure that they are working correctly and not
+ // accidentally interfering with each other.
+ // As a side effect, we also cannot test Platform->Platform and later
+ // Platform->CorePlatform as the former succeeds in verifying linkage usages
+ // that should fail in the latter.
+ // We also cannot use InMemoryDexClassLoader because it runs verification in
+ // a background thread and being able to dynamically change the configuration
+ // (like list of exemptions) would require proper thread synchronization.
+
+ // Run test with both parent and child dex files loaded with class loaders.
+ // The expectation is that hidden members in parent should be visible to
+ // the child.
+ doTest(DexDomain.Application, DexDomain.Application, false);
+ doUnloading();
+
+ // Now append parent dex file to boot class path and run again. This time
+ // the child dex file should not be able to access private APIs of the
+ // parent.
+ int parentIdx = appendToBootClassLoader(DEX_PARENT_BOOT, /* isCorePlatform */ false);
+ doTest(DexDomain.Platform, DexDomain.Application, false);
+ doUnloading();
+
+ // Now run the same test again, but with the blacklist exmemptions list set
+ // to "L" which matches everything.
+ doTest(DexDomain.Platform, DexDomain.Application, true);
+ doUnloading();
+
+ // Repeat the two tests above, only with parent being a core-platform dex file.
+ setDexDomain(parentIdx, /* isCorePlatform */ true);
+ doTest(DexDomain.CorePlatform, DexDomain.Application, false);
+ doUnloading();
+
+ doTest(DexDomain.CorePlatform, DexDomain.Application, true);
+ doUnloading();
+
+ // Append child to boot class path, first as a platform dex file.
+ // It should not be allowed to access non-public, non-core platform API members.
+ int childIdx = appendToBootClassLoader(DEX_CHILD, /* isCorePlatform */ false);
+ doTest(DexDomain.CorePlatform, DexDomain.Platform, false);
+ doUnloading();
+
+ // And finally change child to core-platform dex. With both in the boot classpath
+ // and both core-platform, access should be granted.
+ setDexDomain(childIdx, /* isCorePlatform */ true);
+ doTest(DexDomain.CorePlatform, DexDomain.CorePlatform, false);
+ doUnloading();
+ }
+
+ private static void doTest(DexDomain parentDomain, DexDomain childDomain,
+ boolean whitelistAllApis) throws Exception {
+ // Load parent dex if it is not in boot class path.
+ ClassLoader parentLoader = null;
+ if (parentDomain == DexDomain.Application) {
+ parentLoader = new PathClassLoader(DEX_PARENT, ClassLoader.getSystemClassLoader());
+ } else {
+ parentLoader = BOOT_CLASS_LOADER;
+ }
+
+ // Load child dex if it is not in boot class path.
+ ClassLoader childLoader = null;
+ if (childDomain == DexDomain.Application) {
+ childLoader = new PathClassLoader(DEX_CHILD, parentLoader);
+ } else {
+ if (parentLoader != BOOT_CLASS_LOADER) {
+ throw new IllegalStateException(
+ "DeclaringClass must be in parent class loader of CallingClass");
+ }
+ childLoader = BOOT_CLASS_LOADER;
+ }
+
+ // Create a unique copy of the native library. Each shared library can only
+ // be loaded once, but for some reason even classes from a class loader
+ // cannot register their native methods against symbols in a shared library
+ // loaded by their parent class loader.
+ String nativeLibCopy = createNativeLibCopy(parentDomain, childDomain, whitelistAllApis);
+
+ // Set exemptions to "L" (matches all classes) if we are testing whitelisting.
+ setWhitelistAll(whitelistAllApis);
+
+ // Invoke ChildClass.runTest
+ Class<?> childClass = Class.forName("ChildClass", true, childLoader);
+ Method runTestMethod = childClass.getDeclaredMethod(
+ "runTest", String.class, Integer.TYPE, Integer.TYPE, Integer.TYPE, Boolean.TYPE,
+ MethodHandle.class);
+
+ final MethodHandle nativeLibraryLoadHooks[] = {
+ OTHER_LIBRARY_LOAD_HOOK,
+ ART_MODULE_LOAD_HOOK,
+ };
+
+ for (MethodHandle mh : nativeLibraryLoadHooks) {
+ int nativeDomainOrdinal;
+ if (mh == OTHER_LIBRARY_LOAD_HOOK) {
+ nativeDomainOrdinal = DexDomain.Application.ordinal();
+ } else {
+ nativeDomainOrdinal = DexDomain.CorePlatform.ordinal();
+ }
+ runTestMethod.invoke(null, nativeLibCopy,
+ parentDomain.ordinal(), childDomain.ordinal(), nativeDomainOrdinal,
+ whitelistAllApis, mh);
+ }
+ }
+
+ // Routine which tries to figure out the absolute path of our native library.
+ private static void prepareNativeLibFileName(String arg) throws Exception {
+ String libName = System.mapLibraryName(arg);
+ Method libPathsMethod = Runtime.class.getDeclaredMethod("getLibPaths");
+ libPathsMethod.setAccessible(true);
+ String[] libPaths = (String[]) libPathsMethod.invoke(Runtime.getRuntime());
+ nativeLibFileName = null;
+ for (String p : libPaths) {
+ String candidate = p + libName;
+ if (new File(candidate).exists()) {
+ nativeLibFileName = candidate;
+ break;
+ }
+ }
+ if (nativeLibFileName == null) {
+ throw new IllegalStateException("Didn't find " + libName + " in " +
+ Arrays.toString(libPaths));
+ }
+ }
+
+ // Copy native library to a new file with a unique name so it does not
+ // conflict with other loaded instance of the same binary file.
+ private static String createNativeLibCopy(DexDomain parentDomain, DexDomain childDomain,
+ boolean whitelistAllApis) throws Exception {
+ String tempFileName = System.mapLibraryName(
+ "hiddenapitest_" + (parentDomain.ordinal()) + (childDomain.ordinal()) +
+ (whitelistAllApis ? "1" : "0"));
+ File tempFile = new File(System.getenv("DEX_LOCATION"), tempFileName);
+ Files.copy(new File(nativeLibFileName).toPath(), tempFile.toPath());
+ return tempFile.getAbsolutePath();
+ }
+
+ private static void doUnloading() {
+ // Do multiple GCs to prevent rare flakiness if some other thread is
+ // keeping the classloader live.
+ for (int i = 0; i < 5; ++i) {
+ Runtime.getRuntime().gc();
+ }
+ }
+
+ private static MethodHandle getLoadLibraryHook(String methodName) {
+ try {
+ return MethodHandles.publicLookup().findStatic(Main.class, methodName,
+ LOAD_HOOK_METHOD_TYPE);
+ } catch (Throwable t) {
+ System.err.println("Initialization error for " + methodName + " " + t);
+ return null;
+ }
+ }
+
+ private static String nativeLibFileName;
+
+ private static final MethodType LOAD_HOOK_METHOD_TYPE = MethodType.methodType(void.class,
+ String.class);
+ private static final MethodHandle OTHER_LIBRARY_LOAD_HOOK =
+ getLoadLibraryHook("treatAsOtherLibrary");
+ private static final MethodHandle ART_MODULE_LOAD_HOOK =
+ getLoadLibraryHook("treatAsArtModule");
+
+ private static final String DEX_PARENT =
+ new File(System.getenv("DEX_LOCATION"), "2030-core-platform-api-jni.jar").getAbsolutePath();
+ private static final String DEX_PARENT_BOOT =
+ new File(new File(System.getenv("DEX_LOCATION"), "res"), "boot.jar").getAbsolutePath();
+ private static final String DEX_CHILD =
+ new File(System.getenv("DEX_LOCATION"), "2030-core-platform-api-jni-ex.jar").getAbsolutePath();
+
+ private static ClassLoader BOOT_CLASS_LOADER = Object.class.getClassLoader();
+
+ private static native int appendToBootClassLoader(String dexPath, boolean isCorePlatform);
+ private static native void setDexDomain(int index, boolean isCorePlatform);
+ private static native void init();
+ private static native void setWhitelistAll(boolean value);
+
+ public static native void treatAsArtModule(String library);
+ public static native void treatAsOtherLibrary(String library);
+}
diff --git a/test/2030-core-platform-api-jni/src-ex/ChildClass.java b/test/2030-core-platform-api-jni/src-ex/ChildClass.java
new file mode 100644
index 0000000..75fed41
--- /dev/null
+++ b/test/2030-core-platform-api-jni/src-ex/ChildClass.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+import dalvik.system.VMRuntime;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodType;
+import java.util.function.Consumer;
+
+public class ChildClass {
+ enum PrimitiveType {
+ TInteger('I', Integer.TYPE, Integer.valueOf(0)),
+ TLong('J', Long.TYPE, Long.valueOf(0)),
+ TFloat('F', Float.TYPE, Float.valueOf(0)),
+ TDouble('D', Double.TYPE, Double.valueOf(0)),
+ TBoolean('Z', Boolean.TYPE, Boolean.valueOf(false)),
+ TByte('B', Byte.TYPE, Byte.valueOf((byte) 0)),
+ TShort('S', Short.TYPE, Short.valueOf((short) 0)),
+ TCharacter('C', Character.TYPE, Character.valueOf('0'));
+
+ PrimitiveType(char shorty, Class klass, Object value) {
+ mShorty = shorty;
+ mClass = klass;
+ mDefaultValue = value;
+ }
+
+ public char mShorty;
+ public Class mClass;
+ public Object mDefaultValue;
+ }
+
+ enum Hiddenness {
+ Whitelist(PrimitiveType.TShort),
+ LightGreylist(PrimitiveType.TBoolean),
+ DarkGreylist(PrimitiveType.TByte),
+ Blacklist(PrimitiveType.TCharacter),
+ BlacklistAndCorePlatformApi(PrimitiveType.TInteger);
+
+ Hiddenness(PrimitiveType type) { mAssociatedType = type; }
+ public PrimitiveType mAssociatedType;
+ }
+
+ enum Visibility {
+ Public(PrimitiveType.TInteger),
+ Package(PrimitiveType.TFloat),
+ Protected(PrimitiveType.TLong),
+ Private(PrimitiveType.TDouble);
+
+ Visibility(PrimitiveType type) { mAssociatedType = type; }
+ public PrimitiveType mAssociatedType;
+ }
+
+ enum Behaviour {
+ Granted,
+ Warning,
+ Denied,
+ }
+
+ // This needs to be kept in sync with DexDomain in Main.
+ enum DexDomain {
+ CorePlatform,
+ Platform,
+ Application
+ }
+
+ private static final boolean booleanValues[] = new boolean[] { false, true };
+
+ public static void runTest(String libFileName, int parentDomainOrdinal,
+ int childDomainOrdinal, int childNativeDomainOrdinal,
+ boolean everythingWhitelisted,
+ MethodHandle postSystemLoadHook) throws Throwable {
+ System.load(libFileName);
+
+ // Configure domain of native library.
+ postSystemLoadHook.invokeExact(libFileName);
+
+ parentDomain = DexDomain.values()[parentDomainOrdinal];
+ childDomain = DexDomain.values()[childDomainOrdinal];
+ childNativeDomain = DexDomain.values()[childNativeDomainOrdinal];
+
+ configMessage = "parentDomain=" + parentDomain.name() +
+ ", childDomain=" + childDomain.name() +
+ ", childNativeDomain=" + childNativeDomain.name() +
+ ", everythingWhitelisted=" + everythingWhitelisted;
+
+ // Check expectations about loading into boot class path.
+ boolean isParentInBoot = (ParentClass.class.getClassLoader().getParent() == null);
+ boolean expectedParentInBoot = (parentDomain != DexDomain.Application);
+ if (isParentInBoot != expectedParentInBoot) {
+ throw new RuntimeException("Expected ParentClass " +
+ (expectedParentInBoot ? "" : "not ") + "in boot class path");
+ }
+ boolean isChildInBoot = (ChildClass.class.getClassLoader().getParent() == null);
+ boolean expectedChildInBoot = (childDomain != DexDomain.Application);
+ if (isChildInBoot != expectedChildInBoot) {
+ throw new RuntimeException("Expected ChildClass " + (expectedChildInBoot ? "" : "not ") +
+ "in boot class path");
+ }
+ ChildClass.everythingWhitelisted = everythingWhitelisted;
+
+ boolean isSameBoot = (isParentInBoot == isChildInBoot);
+ boolean isDebuggable = VMRuntime.getRuntime().isJavaDebuggable();
+
+ // For compat reasons, meta-reflection should still be usable by apps if hidden api check
+ // hardening is disabled (i.e. target SDK is Q or earlier). The only configuration where this
+ // workaround used to work is for ChildClass in the Application domain and ParentClass in the
+ // Platform domain, so only test that configuration with hidden api check hardening disabled.
+ boolean testHiddenApiCheckHardeningDisabled =
+ (childDomain == DexDomain.Application) && (parentDomain == DexDomain.Platform);
+
+ // Run meaningful combinations of access flags.
+ for (Hiddenness hiddenness : Hiddenness.values()) {
+ Behaviour expected;
+ final boolean invokesMemberCallback;
+ // Warnings are now disabled whenever access is granted, even for
+ // greylisted APIs. This is the behaviour for release builds.
+ if (everythingWhitelisted || hiddenness == Hiddenness.Whitelist) {
+ expected = Behaviour.Granted;
+ invokesMemberCallback = false;
+ } else if (parentDomain == DexDomain.CorePlatform && childDomain == DexDomain.Platform) {
+ expected = (hiddenness == Hiddenness.BlacklistAndCorePlatformApi)
+ ? Behaviour.Granted : Behaviour.Denied;
+ invokesMemberCallback = false;
+ } else if (isSameBoot) {
+ expected = Behaviour.Granted;
+ invokesMemberCallback = false;
+ } else if (hiddenness == Hiddenness.Blacklist ||
+ hiddenness == Hiddenness.BlacklistAndCorePlatformApi) {
+ expected = Behaviour.Denied;
+ invokesMemberCallback = true;
+ } else {
+ expected = Behaviour.Warning;
+ invokesMemberCallback = true;
+ }
+
+ if (childNativeDomain == DexDomain.CorePlatform) {
+ // Native code that is part of the Core Platform (it's in the ART module). This code is
+ // assumed to have access to all methods and fields.
+ expected = Behaviour.Granted;
+ }
+
+ for (boolean isStatic : booleanValues) {
+ String suffix = (isStatic ? "Static" : "") + hiddenness.name();
+
+ for (Visibility visibility : Visibility.values()) {
+ // Test methods and fields
+ for (Class klass : new Class<?>[] { ParentClass.class, ParentInterface.class }) {
+ String baseName = visibility.name() + suffix;
+ checkField(klass, "field" + baseName, isStatic, visibility, expected,
+ invokesMemberCallback, testHiddenApiCheckHardeningDisabled);
+ checkMethod(klass, "method" + baseName, isStatic, visibility, expected,
+ invokesMemberCallback, testHiddenApiCheckHardeningDisabled);
+ }
+
+ // Check whether one can use a class constructor.
+ checkConstructor(ParentClass.class, visibility, hiddenness, expected,
+ testHiddenApiCheckHardeningDisabled);
+ }
+ }
+ }
+ }
+
+ private static void checkField(Class<?> klass, String name, boolean isStatic,
+ Visibility visibility, Behaviour behaviour, boolean invokesMemberCallback,
+ boolean testHiddenApiCheckHardeningDisabled) throws Exception {
+
+ boolean isPublic = (visibility == Visibility.Public);
+ boolean canDiscover = (behaviour != Behaviour.Denied);
+
+ if (klass.isInterface() && (!isStatic || !isPublic)) {
+ // Interfaces only have public static fields.
+ return;
+ }
+
+ // Test discovery with JNI.
+
+ if (JNI.canDiscoverField(klass, name, isStatic) != canDiscover) {
+ throwDiscoveryException(klass, name, true, "JNI", canDiscover);
+ }
+
+ if (canDiscover) {
+ if (!JNI.canGetField(klass, name, isStatic)) {
+ throwAccessException(klass, name, true, "getIntField");
+ }
+ if (!JNI.canSetField(klass, name, isStatic)) {
+ throwAccessException(klass, name, true, "setIntField");
+ }
+ }
+ }
+
+ private static void checkMethod(Class<?> klass, String name, boolean isStatic,
+ Visibility visibility, Behaviour behaviour, boolean invokesMemberCallback,
+ boolean testHiddenApiCheckHardeningDisabled) throws Exception {
+
+ boolean isPublic = (visibility == Visibility.Public);
+ if (klass.isInterface() && !isPublic) {
+ // All interface members are public.
+ return;
+ }
+
+ boolean canDiscover = (behaviour != Behaviour.Denied);
+
+ // Test discovery with JNI.
+
+ if (JNI.canDiscoverMethod(klass, name, isStatic) != canDiscover) {
+ throwDiscoveryException(klass, name, false, "JNI", canDiscover);
+ }
+
+ // Finish here if we could not discover the method.
+
+ if (canDiscover) {
+ // Test whether we can invoke the method. This skips non-static interface methods.
+ if (!klass.isInterface() || isStatic) {
+ if (!JNI.canInvokeMethodA(klass, name, isStatic)) {
+ throwAccessException(klass, name, false, "CallMethodA");
+ }
+ if (!JNI.canInvokeMethodV(klass, name, isStatic)) {
+ throwAccessException(klass, name, false, "CallMethodV");
+ }
+ }
+ }
+ }
+
+ private static void checkConstructor(Class<?> klass, Visibility visibility, Hiddenness hiddenness,
+ Behaviour behaviour, boolean testHiddenApiCheckHardeningDisabled) throws Exception {
+
+ boolean isPublic = (visibility == Visibility.Public);
+ String signature = "(" + visibility.mAssociatedType.mShorty +
+ hiddenness.mAssociatedType.mShorty + ")V";
+ String fullName = "<init>" + signature;
+ Class<?> args[] = new Class[] { visibility.mAssociatedType.mClass,
+ hiddenness.mAssociatedType.mClass };
+ Object initargs[] = new Object[] { visibility.mAssociatedType.mDefaultValue,
+ hiddenness.mAssociatedType.mDefaultValue };
+ MethodType methodType = MethodType.methodType(void.class, args);
+
+ boolean canDiscover = (behaviour != Behaviour.Denied);
+
+ // Test discovery with JNI.
+
+ if (JNI.canDiscoverConstructor(klass, signature) != canDiscover) {
+ throwDiscoveryException(klass, fullName, false, "JNI", canDiscover);
+ }
+ }
+
+ private static void throwDiscoveryException(Class<?> klass, String name, boolean isField,
+ String fn, boolean canAccess) {
+ throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() +
+ "." + name + " to " + (canAccess ? "" : "not ") + "be discoverable with " + fn + ". " +
+ configMessage);
+ }
+
+ private static void throwAccessException(Class<?> klass, String name, boolean isField,
+ String fn) {
+ throw new RuntimeException("Expected to be able to access " + (isField ? "field " : "method ") +
+ klass.getName() + "." + name + " using " + fn + ". " + configMessage);
+ }
+
+ private static void throwModifiersException(Class<?> klass, String name, boolean isField) {
+ throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() +
+ "." + name + " to not expose hidden modifiers");
+ }
+
+ private static DexDomain parentDomain;
+ private static DexDomain childDomain;
+ private static DexDomain childNativeDomain;
+ private static boolean everythingWhitelisted;
+
+ private static String configMessage;
+}
diff --git a/test/2030-core-platform-api-jni/src-ex/JNI.java b/test/2030-core-platform-api-jni/src-ex/JNI.java
new file mode 100644
index 0000000..5dfb296
--- /dev/null
+++ b/test/2030-core-platform-api-jni/src-ex/JNI.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+public class JNI {
+ public static native boolean canDiscoverField(Class<?> klass, String name, boolean isStatic);
+ public static native boolean canGetField(Class<?> klass, String name, boolean isStatic);
+ public static native boolean canSetField(Class<?> klass, String name, boolean isStatic);
+
+ public static native boolean canDiscoverMethod(Class<?> klass, String name, boolean isStatic);
+ public static native boolean canInvokeMethodA(Class<?> klass, String name, boolean isStatic);
+ public static native boolean canInvokeMethodV(Class<?> klass, String name, boolean isStatic);
+
+ public static native boolean canDiscoverConstructor(Class<?> klass, String signature);
+ public static native boolean canInvokeConstructorA(Class<?> klass, String signature);
+ public static native boolean canInvokeConstructorV(Class<?> klass, String signature);
+}
diff --git a/test/2030-core-platform-api-jni/src/DummyClass.java b/test/2030-core-platform-api-jni/src/DummyClass.java
new file mode 100644
index 0000000..afba747
--- /dev/null
+++ b/test/2030-core-platform-api-jni/src/DummyClass.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class DummyClass implements ParentInterface {
+ public int methodPublicWhitelist() { return 1; }
+ public int methodPublicLightGreylist() { return 2; }
+ public int methodPublicDarkGreylist() { return 3; }
+ public int methodPublicBlacklist() { return 4; }
+ public int methodPublicBlacklistAndCorePlatformApi() { return 5; }
+
+ public static ParentInterface getInterfaceInstance() {
+ return new DummyClass();
+ }
+}
diff --git a/test/2030-core-platform-api-jni/src/NullaryConstructorBlacklist.java b/test/2030-core-platform-api-jni/src/NullaryConstructorBlacklist.java
new file mode 100644
index 0000000..5bf6278
--- /dev/null
+++ b/test/2030-core-platform-api-jni/src/NullaryConstructorBlacklist.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class NullaryConstructorBlacklist {
+ public NullaryConstructorBlacklist() { x = 22; }
+ public NullaryConstructorBlacklist(int y) { x = y; }
+ protected int x;
+}
diff --git a/test/2030-core-platform-api-jni/src/NullaryConstructorBlacklistAndCorePlatformApi.java b/test/2030-core-platform-api-jni/src/NullaryConstructorBlacklistAndCorePlatformApi.java
new file mode 100644
index 0000000..86af29e
--- /dev/null
+++ b/test/2030-core-platform-api-jni/src/NullaryConstructorBlacklistAndCorePlatformApi.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+public class NullaryConstructorBlacklistAndCorePlatformApi {
+ public NullaryConstructorBlacklistAndCorePlatformApi() { x = 22; }
+ public NullaryConstructorBlacklistAndCorePlatformApi(int y) { x = y; }
+ protected int x;
+}
diff --git a/test/2030-core-platform-api-jni/src/NullaryConstructorDarkGreylist.java b/test/2030-core-platform-api-jni/src/NullaryConstructorDarkGreylist.java
new file mode 100644
index 0000000..c25a767
--- /dev/null
+++ b/test/2030-core-platform-api-jni/src/NullaryConstructorDarkGreylist.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class NullaryConstructorDarkGreylist {
+ public NullaryConstructorDarkGreylist() { x = 22; }
+ public NullaryConstructorDarkGreylist(int y) { x = y; }
+ protected int x;
+}
diff --git a/test/2030-core-platform-api-jni/src/NullaryConstructorLightGreylist.java b/test/2030-core-platform-api-jni/src/NullaryConstructorLightGreylist.java
new file mode 100644
index 0000000..d5dac8b
--- /dev/null
+++ b/test/2030-core-platform-api-jni/src/NullaryConstructorLightGreylist.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class NullaryConstructorLightGreylist {
+ public NullaryConstructorLightGreylist() { x = 22; }
+ public NullaryConstructorLightGreylist(int y) { x = y; }
+ protected int x;
+}
diff --git a/test/2030-core-platform-api-jni/src/NullaryConstructorWhitelist.java b/test/2030-core-platform-api-jni/src/NullaryConstructorWhitelist.java
new file mode 100644
index 0000000..d101907
--- /dev/null
+++ b/test/2030-core-platform-api-jni/src/NullaryConstructorWhitelist.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class NullaryConstructorWhitelist {
+ public NullaryConstructorWhitelist() { x = 22; }
+ public NullaryConstructorWhitelist(int y) { x = y; }
+ protected int x;
+}
diff --git a/test/2030-core-platform-api-jni/src/ParentClass.java b/test/2030-core-platform-api-jni/src/ParentClass.java
new file mode 100644
index 0000000..1442392
--- /dev/null
+++ b/test/2030-core-platform-api-jni/src/ParentClass.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class ParentClass {
+ public ParentClass() {}
+
+ // INSTANCE FIELD
+
+ public int fieldPublicWhitelist = 211;
+ int fieldPackageWhitelist = 212;
+ protected int fieldProtectedWhitelist = 213;
+ private int fieldPrivateWhitelist = 214;
+ public int fieldPublicWhitelistB = 215;
+
+ public int fieldPublicLightGreylist = 221;
+ int fieldPackageLightGreylist = 222;
+ protected int fieldProtectedLightGreylist = 223;
+ private int fieldPrivateLightGreylist = 224;
+ public int fieldPublicLightGreylistB = 225;
+
+ public int fieldPublicDarkGreylist = 231;
+ int fieldPackageDarkGreylist = 232;
+ protected int fieldProtectedDarkGreylist = 233;
+ private int fieldPrivateDarkGreylist = 234;
+ public int fieldPublicDarkGreylistB = 235;
+
+ public int fieldPublicBlacklist = 241;
+ int fieldPackageBlacklist = 242;
+ protected int fieldProtectedBlacklist = 243;
+ private int fieldPrivateBlacklist = 244;
+ public int fieldPublicBlacklistB = 245;
+
+ public int fieldPublicBlacklistAndCorePlatformApi = 251;
+ int fieldPackageBlacklistAndCorePlatformApi = 252;
+ protected int fieldProtectedBlacklistAndCorePlatformApi = 253;
+ private int fieldPrivateBlacklistAndCorePlatformApi = 254;
+ public int fieldPublicBlacklistAndCorePlatformApiB = 255;
+
+ // STATIC FIELD
+
+ public static int fieldPublicStaticWhitelist = 111;
+ static int fieldPackageStaticWhitelist = 112;
+ protected static int fieldProtectedStaticWhitelist = 113;
+ private static int fieldPrivateStaticWhitelist = 114;
+ public static int fieldPublicStaticWhitelistB = 115;
+
+ public static int fieldPublicStaticLightGreylist = 121;
+ static int fieldPackageStaticLightGreylist = 122;
+ protected static int fieldProtectedStaticLightGreylist = 123;
+ private static int fieldPrivateStaticLightGreylist = 124;
+ public static int fieldPublicStaticLightGreylistB = 125;
+
+ public static int fieldPublicStaticDarkGreylist = 131;
+ static int fieldPackageStaticDarkGreylist = 132;
+ protected static int fieldProtectedStaticDarkGreylist = 133;
+ private static int fieldPrivateStaticDarkGreylist = 134;
+ public static int fieldPublicStaticDarkGreylistB = 135;
+
+ public static int fieldPublicStaticBlacklist = 141;
+ static int fieldPackageStaticBlacklist = 142;
+ protected static int fieldProtectedStaticBlacklist = 143;
+ private static int fieldPrivateStaticBlacklist = 144;
+ public static int fieldPublicStaticBlacklistB = 145;
+
+ public static int fieldPublicStaticBlacklistAndCorePlatformApi = 151;
+ static int fieldPackageStaticBlacklistAndCorePlatformApi = 152;
+ protected static int fieldProtectedStaticBlacklistAndCorePlatformApi = 153;
+ private static int fieldPrivateStaticBlacklistAndCorePlatformApi = 154;
+ public static int fieldPublicStaticBlacklistAndCorePlatformApiB = 155;
+
+ // INSTANCE METHOD
+
+ public int methodPublicWhitelist() { return 411; }
+ int methodPackageWhitelist() { return 412; }
+ protected int methodProtectedWhitelist() { return 413; }
+ private int methodPrivateWhitelist() { return 414; }
+
+ public int methodPublicLightGreylist() { return 421; }
+ int methodPackageLightGreylist() { return 422; }
+ protected int methodProtectedLightGreylist() { return 423; }
+ private int methodPrivateLightGreylist() { return 424; }
+
+ public int methodPublicDarkGreylist() { return 431; }
+ int methodPackageDarkGreylist() { return 432; }
+ protected int methodProtectedDarkGreylist() { return 433; }
+ private int methodPrivateDarkGreylist() { return 434; }
+
+ public int methodPublicBlacklist() { return 441; }
+ int methodPackageBlacklist() { return 442; }
+ protected int methodProtectedBlacklist() { return 443; }
+ private int methodPrivateBlacklist() { return 444; }
+
+ public int methodPublicBlacklistAndCorePlatformApi() { return 451; }
+ int methodPackageBlacklistAndCorePlatformApi() { return 452; }
+ protected int methodProtectedBlacklistAndCorePlatformApi() { return 453; }
+ private int methodPrivateBlacklistAndCorePlatformApi() { return 454; }
+
+ // STATIC METHOD
+
+ public static int methodPublicStaticWhitelist() { return 311; }
+ static int methodPackageStaticWhitelist() { return 312; }
+ protected static int methodProtectedStaticWhitelist() { return 313; }
+ private static int methodPrivateStaticWhitelist() { return 314; }
+
+ public static int methodPublicStaticLightGreylist() { return 321; }
+ static int methodPackageStaticLightGreylist() { return 322; }
+ protected static int methodProtectedStaticLightGreylist() { return 323; }
+ private static int methodPrivateStaticLightGreylist() { return 324; }
+
+ public static int methodPublicStaticDarkGreylist() { return 331; }
+ static int methodPackageStaticDarkGreylist() { return 332; }
+ protected static int methodProtectedStaticDarkGreylist() { return 333; }
+ private static int methodPrivateStaticDarkGreylist() { return 334; }
+
+ public static int methodPublicStaticBlacklist() { return 341; }
+ static int methodPackageStaticBlacklist() { return 342; }
+ protected static int methodProtectedStaticBlacklist() { return 343; }
+ private static int methodPrivateStaticBlacklist() { return 344; }
+
+ public static int methodPublicStaticBlacklistAndCorePlatformApi() { return 351; }
+ static int methodPackageStaticBlacklistAndCorePlatformApi() { return 352; }
+ protected static int methodProtectedStaticBlacklistAndCorePlatformApi() { return 353; }
+ private static int methodPrivateStaticBlacklistAndCorePlatformApi() { return 354; }
+
+ // CONSTRUCTOR
+
+ // Whitelist
+ public ParentClass(int x, short y) {}
+ ParentClass(float x, short y) {}
+ protected ParentClass(long x, short y) {}
+ private ParentClass(double x, short y) {}
+
+ // Light greylist
+ public ParentClass(int x, boolean y) {}
+ ParentClass(float x, boolean y) {}
+ protected ParentClass(long x, boolean y) {}
+ private ParentClass(double x, boolean y) {}
+
+ // Dark greylist
+ public ParentClass(int x, byte y) {}
+ ParentClass(float x, byte y) {}
+ protected ParentClass(long x, byte y) {}
+ private ParentClass(double x, byte y) {}
+
+ // Blacklist
+ public ParentClass(int x, char y) {}
+ ParentClass(float x, char y) {}
+ protected ParentClass(long x, char y) {}
+ private ParentClass(double x, char y) {}
+
+ // Blacklist and CorePlatformApi
+ public ParentClass(int x, int y) {}
+ ParentClass(float x, int y) {}
+ protected ParentClass(long x, int y) {}
+ private ParentClass(double x, int y) {}
+
+ // HELPERS
+
+ public int callMethodPublicWhitelist() { return methodPublicWhitelist(); }
+ public int callMethodPackageWhitelist() { return methodPackageWhitelist(); }
+ public int callMethodProtectedWhitelist() { return methodProtectedWhitelist(); }
+
+ public int callMethodPublicLightGreylist() { return methodPublicLightGreylist(); }
+ public int callMethodPackageLightGreylist() { return methodPackageLightGreylist(); }
+ public int callMethodProtectedLightGreylist() { return methodProtectedLightGreylist(); }
+
+ public int callMethodPublicDarkGreylist() { return methodPublicDarkGreylist(); }
+ public int callMethodPackageDarkGreylist() { return methodPackageDarkGreylist(); }
+ public int callMethodProtectedDarkGreylist() { return methodProtectedDarkGreylist(); }
+
+ public int callMethodPublicBlacklist() { return methodPublicBlacklist(); }
+ public int callMethodPackageBlacklist() { return methodPackageBlacklist(); }
+ public int callMethodProtectedBlacklist() { return methodProtectedBlacklist(); }
+
+ public int callMethodPublicBlacklistAndCorePlatformApi() {
+ return methodPublicBlacklistAndCorePlatformApi();
+ }
+
+ public int callMethodPackageBlacklistAndCorePlatformApi() {
+ return methodPackageBlacklistAndCorePlatformApi();
+ }
+
+ public int callMethodProtectedBlacklistAndCorePlatformApi() {
+ return methodProtectedBlacklistAndCorePlatformApi();
+ }
+}
diff --git a/test/2030-core-platform-api-jni/src/ParentInterface.java b/test/2030-core-platform-api-jni/src/ParentInterface.java
new file mode 100644
index 0000000..1c5b58f
--- /dev/null
+++ b/test/2030-core-platform-api-jni/src/ParentInterface.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public interface ParentInterface {
+ // STATIC FIELD
+ static int fieldPublicStaticWhitelist = 11;
+ static int fieldPublicStaticLightGreylist = 12;
+ static int fieldPublicStaticDarkGreylist = 13;
+ static int fieldPublicStaticBlacklist = 14;
+ static int fieldPublicStaticBlacklistAndCorePlatformApi = 15;
+
+ // INSTANCE METHOD
+ int methodPublicWhitelist();
+ int methodPublicLightGreylist();
+ int methodPublicDarkGreylist();
+ int methodPublicBlacklist();
+ int methodPublicBlacklistAndCorePlatformApi();
+
+ // STATIC METHOD
+ static int methodPublicStaticWhitelist() { return 21; }
+ static int methodPublicStaticLightGreylist() { return 22; }
+ static int methodPublicStaticDarkGreylist() { return 23; }
+ static int methodPublicStaticBlacklist() { return 24; }
+ static int methodPublicStaticBlacklistAndCorePlatformApi() { return 25; }
+
+ // DEFAULT METHOD
+ default int methodPublicDefaultWhitelist() { return 31; }
+ default int methodPublicDefaultLightGreylist() { return 32; }
+ default int methodPublicDefaultDarkGreylist() { return 33; }
+ default int methodPublicDefaultBlacklist() { return 34; }
+ default int methodPublicDefaultBlacklistAndCorePlatformApi() { return 35; }
+}
diff --git a/test/Android.bp b/test/Android.bp
index 7182513..53f76c6 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -601,6 +601,7 @@
"1972-jni-id-swap-indices/jni_id.cc",
"1985-structural-redefine-stack-scope/stack_scope.cc",
"2011-stack-walk-concurrent-instrument/stack_walk_concurrent.cc",
+ "2030-core-platform-api-jni/core-platform-api-jni.cc",
"common/runtime_state.cc",
"common/stack_inspect.cc",
],
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 80904db..bb80531 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -490,7 +490,8 @@
"607-daemon-stress",
"674-hiddenapi",
"687-deopt",
- "904-object-allocation"
+ "904-object-allocation",
+ "2030-core-platform-api-jni"
],
"description": ["Tests that sometimes fail when run with jvmti-stress for unknown reasons."],
"bug": "b/120995005",
@@ -544,7 +545,8 @@
"690-hiddenapi-same-name-methods",
"804-class-extends-itself",
"921-hello-failure",
- "999-redefine-hiddenapi"
+ "999-redefine-hiddenapi",
+ "2030-core-platform-api-jni"
],
"description": [
"Tests that use illegal dex files or otherwise break dexter assumptions"
@@ -566,7 +568,8 @@
"692-vdex-inmem-loader",
"693-vdex-inmem-loader-evict",
"944-transform-classloaders",
- "999-redefine-hiddenapi"
+ "999-redefine-hiddenapi",
+ "2030-core-platform-api-jni"
],
"description": [
"Tests that use custom class loaders or other features not supported ",
@@ -1204,7 +1207,8 @@
"2004-double-virtual-structural-abstract",
"2005-pause-all-redefine-multithreaded",
"2006-virtual-structural-finalizing",
- "2007-virtual-structural-finalizable"
+ "2007-virtual-structural-finalizable",
+ "2030-core-platform-api-jni"
],
"variant": "jvm",
"description": ["Doesn't run on RI."]