SDK-stub controlled dex2oat verification
Allow dex2oat to further limit the resolved (boot classpath) symbols
during verification according to an additional list of public SDK files.
The additional SDKs can be specified as regular classpath (a list
of dex files) and has the effect of limiting what can be resolved from
the boot classpath. The extra checks are performed by comparing the
symbol descriptors and do not replace common verification access-checks
flow.
Bug: 111442216
Test: test-art-host
Change-Id: Idc13722f34b591d7f858ebeb94bd6f568102b458
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index efe12aa..498ee95 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -810,6 +810,8 @@
self._checker.check_art_test_data('art-gtest-jars-IMTA.jar')
self._checker.check_art_test_data('art-gtest-jars-ImageLayoutA.jar')
self._checker.check_art_test_data('art-gtest-jars-MainEmptyUncompressed.jar')
+ self._checker.check_art_test_data('art-gtest-jars-Dex2oatVdexTestDex.jar')
+ self._checker.check_art_test_data('art-gtest-jars-Dex2oatVdexPublicSdkDex.dex')
class NoSuperfluousBinariesChecker:
diff --git a/dex2oat/Android.bp b/dex2oat/Android.bp
index a859d6e..76ca050 100644
--- a/dex2oat/Android.bp
+++ b/dex2oat/Android.bp
@@ -434,6 +434,8 @@
":art-gtest-jars-AbstractMethod",
":art-gtest-jars-DefaultMethods",
":art-gtest-jars-DexToDexDecompiler",
+ ":art-gtest-jars-Dex2oatVdexPublicSdkDex",
+ ":art-gtest-jars-Dex2oatVdexTestDex",
":art-gtest-jars-ImageLayoutA",
":art-gtest-jars-ImageLayoutB",
":art-gtest-jars-LinkageTest",
@@ -457,6 +459,7 @@
],
srcs: [
"dex2oat_test.cc",
+ "dex2oat_vdex_test.cc",
"dex2oat_image_test.cc",
"dex/dex_to_dex_decompiler_test.cc",
"driver/compiler_driver_test.cc",
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index dcc9de4..9e1aebc 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -1064,6 +1064,7 @@
AssignIfExists(args, M::CompilationReason, &compilation_reason_);
AssignTrueIfExists(args, M::CheckLinkageConditions, &check_linkage_conditions_);
AssignTrueIfExists(args, M::CrashOnLinkageViolation, &crash_on_linkage_violation_);
+ AssignIfExists(args, M::PublicSdk, &public_sdk_);
AssignIfExists(args, M::Backend, &compiler_kind_);
parser_options->requested_specific_compiler = args.Exists(M::Backend);
@@ -1883,6 +1884,21 @@
}
if (!IsBootImage()) {
callbacks_->SetDexFiles(&dex_files);
+
+ // We need to set this after we create the class loader so that the runtime can access
+ // the hidden fields of the well known class loaders.
+ if (!public_sdk_.empty()) {
+ std::string error_msg;
+ std::unique_ptr<SdkChecker> sdk_checker(SdkChecker::Create(public_sdk_, &error_msg));
+ if (sdk_checker != nullptr) {
+ AotClassLinker* aot_class_linker = down_cast<AotClassLinker*>(class_linker);
+ aot_class_linker->SetSdkChecker(std::move(sdk_checker));
+ } else {
+ LOG(FATAL) << "Failed to create SdkChecker with dex files "
+ << public_sdk_ << " Error: " << error_msg;
+ UNREACHABLE();
+ }
+ }
}
// Register dex caches and key them to the class loader so that they only unload when the
@@ -2834,6 +2850,9 @@
// Whether to force individual compilation.
bool compile_individually_;
+ // The classpath that determines if a given symbol should be resolved at compile time or not.
+ std::string public_sdk_;
+
DISALLOW_IMPLICIT_CONSTRUCTORS(Dex2Oat);
};
diff --git a/dex2oat/dex2oat_options.cc b/dex2oat/dex2oat_options.cc
index c7c62d7..31dab34 100644
--- a/dex2oat/dex2oat_options.cc
+++ b/dex2oat/dex2oat_options.cc
@@ -409,7 +409,10 @@
.Define("--compile-individually")
.WithHelp("Compiles dex files individually, unloading classes in between compiling each"
" file.")
- .IntoKey(M::CompileIndividually);
+ .IntoKey(M::CompileIndividually)
+ .Define("--public-sdk=_")
+ .WithType<std::string>()
+ .IntoKey(M::PublicSdk);;
AddCompilerOptionsArgumentParserOptions<Dex2oatArgumentMap>(*parser_builder);
diff --git a/dex2oat/dex2oat_options.def b/dex2oat/dex2oat_options.def
index 8b018bf..db9da9f 100644
--- a/dex2oat/dex2oat_options.def
+++ b/dex2oat/dex2oat_options.def
@@ -95,6 +95,6 @@
DEX2OAT_OPTIONS_KEY (Unit, CheckLinkageConditions)
DEX2OAT_OPTIONS_KEY (Unit, CrashOnLinkageViolation)
DEX2OAT_OPTIONS_KEY (Unit, CompileIndividually)
-
+DEX2OAT_OPTIONS_KEY (std::string, PublicSdk)
#undef DEX2OAT_OPTIONS_KEY
diff --git a/dex2oat/dex2oat_vdex_test.cc b/dex2oat/dex2oat_vdex_test.cc
new file mode 100644
index 0000000..7eb66af
--- /dev/null
+++ b/dex2oat/dex2oat_vdex_test.cc
@@ -0,0 +1,202 @@
+/*
+ * 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 <string>
+#include <vector>
+
+#include "common_runtime_test.h"
+#include "dex2oat_environment_test.h"
+
+#include "vdex_file.h"
+#include "verifier/verifier_deps.h"
+#include "ziparchive/zip_writer.h"
+
+namespace art {
+
+using verifier::VerifierDeps;
+
+class Dex2oatVdexTest : public Dex2oatEnvironmentTest {
+ public:
+ void TearDown() override {
+ Dex2oatEnvironmentTest::TearDown();
+
+ output_ = "";
+ error_msg_ = "";
+ opened_vdex_files_.clear();
+ }
+
+ protected:
+ bool RunDex2oat(const std::string& dex_location,
+ const std::string& odex_location,
+ const std::string* public_sdk,
+ const std::vector<std::string>& extra_args = {}) {
+ std::vector<std::string> args;
+ args.push_back("--dex-file=" + dex_location);
+ args.push_back("--oat-file=" + odex_location);
+ if (public_sdk != nullptr) {
+ args.push_back("--public-sdk=" + *public_sdk);
+ }
+ args.push_back("--compiler-filter=" +
+ CompilerFilter::NameOfFilter(CompilerFilter::Filter::kVerify));
+ args.push_back("--runtime-arg");
+ args.push_back("-Xnorelocate");
+ args.push_back("--copy-dex-files=false");
+ args.push_back("--runtime-arg");
+ args.push_back("-verbose:verifier");
+ // Use a single thread to facilitate debugging. We only compile tiny dex files.
+ args.push_back("-j1");
+
+ args.insert(args.end(), extra_args.begin(), extra_args.end());
+
+ return Dex2Oat(args, &output_, &error_msg_) == 0;
+ }
+
+ std::unique_ptr<VerifierDeps> GetVerifierDeps(
+ const std::string& vdex_location, const DexFile* dex_file) {
+ // Verify the vdex file content: only the classes using public APIs should be verified.
+ std::unique_ptr<VdexFile> vdex(VdexFile::Open(vdex_location.c_str(),
+ /*writable=*/ false,
+ /*low_4gb=*/ false,
+ /*unquicken=*/ false,
+ &error_msg_));
+ // Check the vdex doesn't have dex.
+ if (vdex->HasDexSection()) {
+ ::testing::AssertionFailure() << "The vdex should not contain dex code";
+ }
+
+ // Verify the deps.
+ VdexFile::VerifierDepsHeader vdex_header = vdex->GetVerifierDepsHeader();
+ if (!vdex_header.IsValid()) {
+ ::testing::AssertionFailure() << "Invalid vdex header";
+ }
+
+ std::vector<const DexFile*> dex_files;
+ dex_files.push_back(dex_file);
+ std::unique_ptr<VerifierDeps> deps(new VerifierDeps(dex_files, /*output_only=*/ false));
+
+ if (!deps->ParseStoredData(dex_files, vdex->GetVerifierDepsData())) {
+ ::testing::AssertionFailure() << error_msg_;
+ }
+
+ opened_vdex_files_.push_back(std::move(vdex));
+ return deps;
+ }
+
+ uint16_t GetClassDefIndex(const std::string& cls, const DexFile& dex_file) {
+ const dex::TypeId* type_id = dex_file.FindTypeId(cls.c_str());
+ DCHECK(type_id != nullptr);
+ dex::TypeIndex type_idx = dex_file.GetIndexForTypeId(*type_id);
+ const dex::ClassDef* class_def = dex_file.FindClassDef(type_idx);
+ DCHECK(class_def != nullptr);
+ return dex_file.GetIndexForClassDef(*class_def);
+ }
+
+ bool HasVerifiedClass(const std::unique_ptr<VerifierDeps>& deps,
+ const std::string& cls,
+ const DexFile& dex_file) {
+ uint16_t class_def_idx = GetClassDefIndex(cls, dex_file);
+ return deps->GetVerifiedClasses(dex_file)[class_def_idx];
+ }
+
+ void CreateDexMetadata(const std::string& vdex, const std::string& out_dm) {
+ // Read the vdex bytes.
+ std::unique_ptr<File> vdex_file(OS::OpenFileForReading(vdex.c_str()));
+ std::vector<uint8_t> data(vdex_file->GetLength());
+ ASSERT_TRUE(vdex_file->ReadFully(data.data(), data.size()));
+
+ // Zip the content.
+ FILE* file = fopen(out_dm.c_str(), "wb");
+ ZipWriter writer(file);
+ writer.StartEntry("primary.vdex", ZipWriter::kAlign32);
+ writer.WriteBytes(data.data(), data.size());
+ writer.FinishEntry();
+ writer.Finish();
+ fflush(file);
+ fclose(file);
+ }
+
+ std::string output_;
+ std::string error_msg_;
+ std::vector<std::unique_ptr<VdexFile>> opened_vdex_files_;
+};
+
+// Validates verification against public API stubs:
+// - create a vdex file contraints by a predefined list of public API (passed as separate dex)
+// - compile with the above vdex file as input to validate the compilation flow
+TEST_F(Dex2oatVdexTest, VerifyPublicSdkStubs) {
+ std::string error_msg;
+ const std::string out_dir = GetScratchDir();
+ const std::string odex_location = out_dir + "/base.oat";
+ const std::string vdex_location = out_dir + "/base.vdex";
+
+ // Dex2oatVdexTestDex is the subject app using normal APIs found in the boot classpath.
+ std::unique_ptr<const DexFile> dex_file(OpenTestDexFile("Dex2oatVdexTestDex"));
+ const std::string dex_location = dex_file->GetLocation();
+ // Dex2oatVdexPublicSdkDex serves as the public API-stubs, restricting what can be verified.
+ const std::string api_dex_location = GetTestDexFileName("Dex2oatVdexPublicSdkDex");
+
+ // Compile the subject app using the predefined API-stubs
+ ASSERT_TRUE(RunDex2oat(dex_location, odex_location, &api_dex_location));
+
+ std::unique_ptr<VerifierDeps> deps = GetVerifierDeps(vdex_location, dex_file.get());
+
+ // Verify public API usage. The classes should be verified.
+ ASSERT_TRUE(HasVerifiedClass(deps, "LAccessPublicCtor;", *dex_file));
+ ASSERT_TRUE(HasVerifiedClass(deps, "LAccessPublicMethod;", *dex_file));
+ ASSERT_TRUE(HasVerifiedClass(deps, "LAccessPublicMethodFromParent;", *dex_file));
+ ASSERT_TRUE(HasVerifiedClass(deps, "LAccessPublicStaticMethod;", *dex_file));
+ ASSERT_TRUE(HasVerifiedClass(deps, "LAccessPublicStaticField;", *dex_file));
+
+ // Verify NON public API usage. The classes should not ve verified.
+ ASSERT_FALSE(HasVerifiedClass(deps, "LAccessNonPublicCtor;", *dex_file));
+ ASSERT_FALSE(HasVerifiedClass(deps, "LAccessNonPublicMethod;", *dex_file));
+ ASSERT_FALSE(HasVerifiedClass(deps, "LAccessNonPublicMethodFromParent;", *dex_file));
+ ASSERT_FALSE(HasVerifiedClass(deps, "LAccessNonPublicStaticMethod;", *dex_file));
+
+ // Accessing unresolved static fields do not lead to class verification
+ // failures.
+ // The linker will just throw NoSuchFieldError at runtime.
+ ASSERT_TRUE(HasVerifiedClass(deps, "LAccessNonPublicStaticField;", *dex_file));
+
+ // Compile again without public API stubs but with the previously generated vdex.
+ // This simulates a normal install where the apk has its code pre-verified.
+ // The results should be the same.
+
+ std::string dm_file = out_dir + "/base.dm";
+ CreateDexMetadata(vdex_location, dm_file);
+ std::vector<std::string> extra_args;
+ extra_args.push_back("--dm-file=" + dm_file);
+ const std::string odex2_location = out_dir + "/base2.oat";
+ const std::string vdex2_location = out_dir + "/base2.vdex";
+ output_ = "";
+ ASSERT_TRUE(RunDex2oat(dex_location, odex2_location, nullptr, extra_args));
+
+ std::unique_ptr<VerifierDeps> deps2 = GetVerifierDeps(vdex_location, dex_file.get());
+
+ ASSERT_TRUE(HasVerifiedClass(deps2, "LAccessPublicCtor;", *dex_file));
+ ASSERT_TRUE(HasVerifiedClass(deps2, "LAccessPublicMethod;", *dex_file));
+ ASSERT_TRUE(HasVerifiedClass(deps2, "LAccessPublicMethodFromParent;", *dex_file));
+ ASSERT_TRUE(HasVerifiedClass(deps2, "LAccessPublicStaticMethod;", *dex_file));
+ ASSERT_TRUE(HasVerifiedClass(deps2, "LAccessPublicStaticField;", *dex_file));
+
+ ASSERT_FALSE(HasVerifiedClass(deps2, "LAccessNonPublicCtor;", *dex_file)) << output_;
+ ASSERT_FALSE(HasVerifiedClass(deps2, "LAccessNonPublicMethod;", *dex_file));
+ ASSERT_FALSE(HasVerifiedClass(deps2, "LAccessNonPublicMethodFromParent;", *dex_file));
+ ASSERT_FALSE(HasVerifiedClass(deps2, "LAccessNonPublicStaticMethod;", *dex_file));
+ ASSERT_TRUE(HasVerifiedClass(deps2, "LAccessNonPublicStaticField;", *dex_file));
+}
+
+} // namespace art
diff --git a/runtime/Android.bp b/runtime/Android.bp
index baeecd7..ed2c5ae 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -64,6 +64,7 @@
srcs: [
"aot_class_linker.cc",
"art_field.cc",
+ "sdk_checker.cc",
"art_method.cc",
"backtrace_helper.cc",
"barrier.cc",
diff --git a/runtime/aot_class_linker.cc b/runtime/aot_class_linker.cc
index a2f450b..b11c08d 100644
--- a/runtime/aot_class_linker.cc
+++ b/runtime/aot_class_linker.cc
@@ -242,5 +242,24 @@
}
return false;
}
+void AotClassLinker::SetSdkChecker(std::unique_ptr<SdkChecker>&& sdk_checker) {
+ sdk_checker_ = std::move(sdk_checker);
+}
+
+const SdkChecker* AotClassLinker::GetSdkChecker() const {
+ return sdk_checker_.get();
+}
+
+bool AotClassLinker::DenyAccessBasedOnPublicSdk(ArtMethod* art_method) const
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ return sdk_checker_ != nullptr && sdk_checker_->ShouldDenyAccess(art_method);
+}
+bool AotClassLinker::DenyAccessBasedOnPublicSdk(ArtField* art_field) const
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ return sdk_checker_ != nullptr && sdk_checker_->ShouldDenyAccess(art_field);
+}
+bool AotClassLinker::DenyAccessBasedOnPublicSdk(const char* type_descriptor) const {
+ return sdk_checker_ != nullptr && sdk_checker_->ShouldDenyAccess(type_descriptor);
+}
} // namespace art
diff --git a/runtime/aot_class_linker.h b/runtime/aot_class_linker.h
index 76984bd..30a79aa 100644
--- a/runtime/aot_class_linker.h
+++ b/runtime/aot_class_linker.h
@@ -17,6 +17,7 @@
#ifndef ART_RUNTIME_AOT_CLASS_LINKER_H_
#define ART_RUNTIME_AOT_CLASS_LINKER_H_
+#include "sdk_checker.h"
#include "class_linker.h"
namespace art {
@@ -32,11 +33,20 @@
explicit AotClassLinker(InternTable *intern_table);
~AotClassLinker();
- static bool CanReferenceInBootImageExtension(ObjPtr<mirror::Class> klass, gc::Heap* heap)
+static bool CanReferenceInBootImageExtension(ObjPtr<mirror::Class> klass, gc::Heap* heap)
REQUIRES_SHARED(Locks::mutator_lock_);
bool SetUpdatableBootClassPackages(const std::vector<std::string>& packages);
+ void SetSdkChecker(std::unique_ptr<SdkChecker>&& sdk_checker_);
+ const SdkChecker* GetSdkChecker() const;
+
+ bool DenyAccessBasedOnPublicSdk(ArtMethod* art_method ATTRIBUTE_UNUSED) const override
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ bool DenyAccessBasedOnPublicSdk(ArtField* art_field ATTRIBUTE_UNUSED) const override
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ bool DenyAccessBasedOnPublicSdk(const char* type_descriptor ATTRIBUTE_UNUSED) const override;
+
protected:
// Overridden version of PerformClassVerification allows skipping verification if the class was
// previously verified but unloaded.
@@ -66,6 +76,8 @@
private:
std::vector<std::string> updatable_boot_class_path_descriptor_prefixes_;
+
+ std::unique_ptr<SdkChecker> sdk_checker_;
};
} // namespace art
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index ee64eda..7595699 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -3140,6 +3140,21 @@
return sdc.Finish(nullptr);
}
+ // For AOT-compilation of an app, we may use only a public SDK to resolve symbols. If the SDK
+ // checks are configured (a non null SdkChecker) and the descriptor is not in the provided
+ // public class path then we prevent the definition of the class.
+ //
+ // NOTE that we only do the checks for the boot classpath APIs. Anything else, like the app
+ // classpath is not checked.
+ if (class_loader == nullptr &&
+ Runtime::Current()->IsAotCompiler() &&
+ DenyAccessBasedOnPublicSdk(descriptor)) {
+ ObjPtr<mirror::Throwable> pre_allocated =
+ Runtime::Current()->GetPreAllocatedNoClassDefFoundError();
+ self->SetException(pre_allocated);
+ return sdc.Finish(nullptr);
+ }
+
// This is to prevent the calls to ClassLoad and ClassPrepare which can cause java/user-supplied
// code to be executed. We put it up here so we can avoid all the allocations associated with
// creating the class. This can happen with (eg) jit threads.
@@ -9792,6 +9807,26 @@
UNREACHABLE();
}
+bool ClassLinker::DenyAccessBasedOnPublicSdk(ArtMethod* art_method ATTRIBUTE_UNUSED) const
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+bool ClassLinker::DenyAccessBasedOnPublicSdk(ArtField* art_field ATTRIBUTE_UNUSED) const
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+bool ClassLinker::DenyAccessBasedOnPublicSdk(const char* type_descriptor ATTRIBUTE_UNUSED) const {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
// Instantiate ClassLinker::ResolveMethod.
template ArtMethod* ClassLinker::ResolveMethod<ClassLinker::ResolveMode::kCheckICCEAndIAE>(
uint32_t method_idx,
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index df9c209..3d1d5fd 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -58,6 +58,7 @@
template<class T> class ObjectLock;
class Runtime;
class ScopedObjectAccessAlreadyRunnable;
+class SdkChecker;
template<size_t kNumReferences> class PACKED(4) StackHandleScope;
class Thread;
@@ -825,6 +826,15 @@
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Locks::dex_lock_, !Roles::uninterruptible_);
+ // Verifies if the method is accesible according to the SdkChecker (if installed).
+ virtual bool DenyAccessBasedOnPublicSdk(ArtMethod* art_method ATTRIBUTE_UNUSED) const
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // Verifies if the field is accesible according to the SdkChecker (if installed).
+ virtual bool DenyAccessBasedOnPublicSdk(ArtField* art_field ATTRIBUTE_UNUSED) const
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // Verifies if the descriptor is accesible according to the SdkChecker (if installed).
+ virtual bool DenyAccessBasedOnPublicSdk(const char* type_descriptor ATTRIBUTE_UNUSED) const;
+
protected:
virtual bool InitializeClass(Thread* self,
Handle<mirror::Class> klass,
diff --git a/runtime/hidden_api.h b/runtime/hidden_api.h
index 7565869..568906c 100644
--- a/runtime/hidden_api.h
+++ b/runtime/hidden_api.h
@@ -398,6 +398,23 @@
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(member != nullptr);
+ // First check if we have an explicit sdk checker installed that should be used to
+ // verify access. If so, make the decision based on it.
+ //
+ // This is used during off-device AOT compilation which may want to generate verification
+ // metadata only for a specific list of public SDKs. Note that the check here is made
+ // based on descriptor equality and it's aim to further restrict a symbol that would
+ // otherwise be resolved.
+ //
+ // The check only applies to boot classpaths dex files.
+ Runtime* runtime = Runtime::Current();
+ if (UNLIKELY(runtime->IsAotCompiler())) {
+ if (member->GetDeclaringClass()->GetClassLoader() == nullptr &&
+ runtime->GetClassLinker()->DenyAccessBasedOnPublicSdk(member)) {
+ return true;
+ }
+ }
+
// Get the runtime flags encoded in member's access flags.
// Note: this works for proxy methods because they inherit access flags from their
// respective interface methods.
@@ -430,7 +447,7 @@
DCHECK(!callee_context.IsApplicationDomain());
// Exit early if access checks are completely disabled.
- EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy();
+ EnforcementPolicy policy = runtime->GetHiddenApiEnforcementPolicy();
if (policy == EnforcementPolicy::kDisabled) {
return false;
}
diff --git a/runtime/sdk_checker.cc b/runtime/sdk_checker.cc
new file mode 100644
index 0000000..22c4305
--- /dev/null
+++ b/runtime/sdk_checker.cc
@@ -0,0 +1,146 @@
+/*
+ * 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 "sdk_checker.h"
+
+#include "art_method-inl.h"
+#include "base/utils.h"
+#include "dex/art_dex_file_loader.h"
+#include "mirror/class-inl.h"
+
+namespace art {
+
+SdkChecker::SdkChecker() {}
+
+SdkChecker* SdkChecker::Create(
+ const std::string& public_sdk, std::string* error_msg) {
+ std::vector<std::string> dex_file_paths;
+ Split(public_sdk, ':', &dex_file_paths);
+
+ ArtDexFileLoader dex_loader;
+
+ std::unique_ptr<SdkChecker> sdk_checker(new SdkChecker());
+ for (const std::string& path : dex_file_paths) {
+ if (!dex_loader.Open(path.c_str(),
+ path,
+ /*verify=*/ true,
+ /*verify_checksum*/ false,
+ error_msg,
+ &sdk_checker->sdk_dex_files_)) {
+ return nullptr;
+ }
+ }
+ return sdk_checker.release();
+}
+
+bool SdkChecker::ShouldDenyAccess(ArtMethod* art_method) const {
+ bool found = false;
+ for (const std::unique_ptr<const DexFile>& dex_file : sdk_dex_files_) {
+ const dex::TypeId* declaring_type_id =
+ dex_file->FindTypeId(art_method->GetDeclaringClassDescriptor());
+ if (declaring_type_id == nullptr) {
+ continue;
+ }
+ const dex::StringId* name_id = dex_file->FindStringId(art_method->GetName());
+ if (name_id == nullptr) {
+ continue;
+ }
+
+ dex::TypeIndex return_type_idx;
+ std::vector<dex::TypeIndex> param_type_idxs;
+ if (!dex_file->CreateTypeList(
+ art_method->GetSignature().ToString().c_str(),
+ &return_type_idx,
+ ¶m_type_idxs)) {
+ continue;
+ }
+ const dex::ProtoId* proto_id = dex_file->FindProtoId(return_type_idx, param_type_idxs);
+ if (proto_id == nullptr) {
+ continue;
+ }
+
+ const dex::MethodId* method_id =
+ dex_file->FindMethodId(*declaring_type_id, *name_id, *proto_id);
+ if (method_id != nullptr) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ VLOG(verifier) << "Deny for " << art_method->PrettyMethod(true);
+ }
+
+ // Deny access if we didn't find the descriptor in the public api dex files.
+ return !found;
+}
+
+bool SdkChecker::ShouldDenyAccess(ArtField* art_field) const {
+ bool found = false;
+ for (const std::unique_ptr<const DexFile>& dex_file : sdk_dex_files_) {
+ std::string declaring_class;
+
+ const dex::TypeId* declaring_type_id = dex_file->FindTypeId(
+ art_field->GetDeclaringClass()->GetDescriptor(&declaring_class));
+ if (declaring_type_id == nullptr) {
+ continue;
+ }
+ const dex::StringId* name_id = dex_file->FindStringId(art_field->GetName());
+ if (name_id == nullptr) {
+ continue;
+ }
+ const dex::TypeId* type_id = dex_file->FindTypeId(art_field->GetTypeDescriptor());
+ if (type_id == nullptr) {
+ continue;
+ }
+
+ const dex::FieldId* field_id = dex_file->FindFieldId(*declaring_type_id, *name_id, *type_id);
+ if (field_id != nullptr) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ VLOG(verifier) << "Deny for " << ArtField::PrettyField(art_field, true);
+ }
+
+ // Deny access if we didn't find the descriptor in the public api dex files.
+ return !found;
+}
+
+bool SdkChecker::ShouldDenyAccess(const char* descriptor) const {
+ bool found = false;
+ for (const std::unique_ptr<const DexFile>& dex_file : sdk_dex_files_) {
+ const dex::TypeId* type_id = dex_file->FindTypeId(descriptor);
+ if (type_id != nullptr) {
+ dex::TypeIndex type_idx = dex_file->GetIndexForTypeId(*type_id);
+ if (dex_file->FindClassDef(type_idx) != nullptr) {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ VLOG(verifier) << "Deny for " << descriptor;
+ }
+
+ // Deny access if we didn't find the descriptor in the public api dex files.
+ return !found;
+}
+
+} // namespace art
diff --git a/runtime/sdk_checker.h b/runtime/sdk_checker.h
new file mode 100644
index 0000000..29cb98d
--- /dev/null
+++ b/runtime/sdk_checker.h
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+#ifndef ART_RUNTIME_SDK_CHECKER_H_
+#define ART_RUNTIME_SDK_CHECKER_H_
+
+#include "art_field.h"
+#include "art_method.h"
+#include "base/locks.h"
+#include "dex/dex_file.h"
+
+namespace art {
+
+/**
+ * The SdkChecker verifies if a given symbol is present in a given classpath.
+ *
+ * For convenience and future extensibility the classpath is given as set of
+ * dex files, simillar to a regular classpath the APKs use.
+ *
+ * The symbol (method, field, class) is checked based on its descriptor and not
+ * according the any access check semantic.
+ *
+ * This class is intended to be used during off-device AOT verification when
+ * only some predefined symbols should be resolved (e.g. belonging to some public
+ * API classpath).
+ */
+class SdkChecker {
+ public:
+ // Constructs and SDK Checker from the given public sdk paths. The public_sdk
+ // format is the same as the classpath format (e.g. `dex1:dex2:dex3`). The
+ // method will attempt to open the dex files and if there are errors it will
+ // return a nullptr and set the error_msg appropriately.
+ static SdkChecker* Create(const std::string& public_sdk, std::string* error_msg);
+
+ // Verify if it should deny access to the given methods.
+ // The decision is based on whether or not any of the API dex files declares a method
+ // with the same signature.
+ //
+ // NOTE: This is an expensive check as it searches the dex files for the necessary type
+ // and string ids. This is OK because the functionality here is indended to be used
+ // only in AOT verification.
+ bool ShouldDenyAccess(ArtMethod* art_method) const REQUIRES_SHARED(Locks::mutator_lock_);
+
+ // Similar to ShouldDenyAccess(ArtMethod* art_method).
+ bool ShouldDenyAccess(ArtField* art_field) const REQUIRES_SHARED(Locks::mutator_lock_);
+
+ // Similar to ShouldDenyAccess(ArtMethod* art_method).
+ bool ShouldDenyAccess(const char* type_descriptor) const;
+
+ private:
+ SdkChecker();
+
+ std::vector<std::unique_ptr<const DexFile>> sdk_dex_files_;
+};
+
+} // namespace art
+
+#endif // ART_RUNTIME_SDK_CHECKER_H_
diff --git a/test/Android.bp b/test/Android.bp
index 6ecc198..f87aaee 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -1351,6 +1351,12 @@
defaults: ["art-gtest-jars-defaults"],
}
+java_library {
+ name: "art-gtest-jars-Dex2oatVdexTestDex",
+ srcs: ["Dex2oatVdexTestDex/**/*.java"],
+ defaults: ["art-gtest-jars-defaults"],
+}
+
// The following cases are non-trivial.
// Uncompress classes.dex files in the jar file.
@@ -1497,3 +1503,10 @@
srcs: ["LinkageTest/*.smali"],
out: ["art-gtest-jars-LinkageTest.dex"],
}
+
+genrule {
+ name: "art-gtest-jars-Dex2oatVdexPublicSdkDex",
+ defaults: ["art-gtest-jars-smali-defaults"],
+ srcs: ["Dex2oatVdexPublicSdkDex/*.smali"],
+ out: ["art-gtest-jars-Dex2oatVdexPublicSdkDex.dex"],
+}
diff --git a/test/Dex2oatVdexPublicSdkDex/Class.smali b/test/Dex2oatVdexPublicSdkDex/Class.smali
new file mode 100644
index 0000000..212631f
--- /dev/null
+++ b/test/Dex2oatVdexPublicSdkDex/Class.smali
@@ -0,0 +1,22 @@
+# 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.
+
+
+.class public Ljava/lang/Class;
+.super Ljava/lang/Object;
+
+.method static constructor <clinit>()V
+ .registers 0
+ return-void
+.end method
diff --git a/test/Dex2oatVdexPublicSdkDex/Integer.smali b/test/Dex2oatVdexPublicSdkDex/Integer.smali
new file mode 100644
index 0000000..6a26224
--- /dev/null
+++ b/test/Dex2oatVdexPublicSdkDex/Integer.smali
@@ -0,0 +1,47 @@
+# 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.
+
+
+.class public Ljava/lang/Integer;
+.super Ljava/lang/Number;
+
+.field public static final BYTES:I
+
+.method static constructor <clinit>()V
+ .registers 0
+ return-void
+.end method
+
+.method public constructor <init>(I)V
+ .registers 2
+ invoke-static {v0}, Ljava/lang/Integer;->throw()V
+.end method
+
+.method public static getInteger(Ljava/lang/String;)Ljava/lang/Integer;
+ .registers 1
+ invoke-static {v0}, Ljava/lang/Integer;->throw()V
+.end method
+
+.method public doubleValue()D
+ .registers 1
+ invoke-static {v0}, Ljava/lang/Integer;->throw()V
+.end method
+
+.method public static throw()V
+.registers 2
+ new-instance v0, Ljava/lang/RuntimeException;
+ const-string v1, "This is an error message"
+ invoke-direct {v0, v1}, Ljava/lang/RuntimeException;-><init>(Ljava/lang/String;)V
+ throw v0
+.end method
diff --git a/test/Dex2oatVdexPublicSdkDex/Number.smali b/test/Dex2oatVdexPublicSdkDex/Number.smali
new file mode 100644
index 0000000..340469a
--- /dev/null
+++ b/test/Dex2oatVdexPublicSdkDex/Number.smali
@@ -0,0 +1,35 @@
+# 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.
+
+
+.class public Ljava/lang/Number;
+.super Ljava/lang/Object;
+
+.method static constructor <clinit>()V
+ .registers 0
+ return-void
+.end method
+
+.method public constructor <init>()V
+ .registers 2
+ invoke-static {v0}, Ljava/lang/Number;->throw()V
+.end method
+
+.method public static throw()V
+.registers 2
+ new-instance v0, Ljava/lang/RuntimeException;
+ const-string v1, "This is an error message"
+ invoke-direct {v0, v1}, Ljava/lang/RuntimeException;-><init>(Ljava/lang/String;)V
+ throw v0
+.end method
diff --git a/test/Dex2oatVdexPublicSdkDex/Object.smali b/test/Dex2oatVdexPublicSdkDex/Object.smali
new file mode 100644
index 0000000..295a9e3
--- /dev/null
+++ b/test/Dex2oatVdexPublicSdkDex/Object.smali
@@ -0,0 +1,37 @@
+# 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.
+
+
+.class public Ljava/lang/Object;
+
+.method static constructor <clinit>()V
+ .registers 0
+ return-void
+.end method
+
+.method public constructor <init>()V
+ .registers 2
+ invoke-static {v0}, Ljava/lang/Object;->throw()V
+.end method
+
+.method public native notify()V
+.end method
+
+.method public static throw()V
+.registers 2
+ new-instance v0, Ljava/lang/RuntimeException;
+ const-string v1, "This is an error message"
+ invoke-direct {v0, v1}, Ljava/lang/RuntimeException;-><init>(Ljava/lang/String;)V
+ throw v0
+.end method
diff --git a/test/Dex2oatVdexPublicSdkDex/Readme.md b/test/Dex2oatVdexPublicSdkDex/Readme.md
new file mode 100644
index 0000000..5b3567a
--- /dev/null
+++ b/test/Dex2oatVdexPublicSdkDex/Readme.md
@@ -0,0 +1,6 @@
+This dex file redefines some popular java.lang classes in order to assist
+with testing sdk-based verification.
+
+Each of these classes only defines the stubs for a limited number of methods
+and fields. They are compiled into a dex file and passed to dex2oat to simulate
+a proper SDK-stub jars. The methods not declared here should not be resolved.
diff --git a/test/Dex2oatVdexTestDex/Dex2oatVdexTestDex.java b/test/Dex2oatVdexTestDex/Dex2oatVdexTestDex.java
new file mode 100644
index 0000000..e4862bd
--- /dev/null
+++ b/test/Dex2oatVdexTestDex/Dex2oatVdexTestDex.java
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+/**
+ * Check that the classes using publicly listed APIS are verified.
+ */
+
+class AccessPublicCtor {
+ public Integer foo() {
+ return new Integer(1);
+ }
+}
+
+class AccessPublicMethod {
+ public double foo(Integer i) {
+ return i.doubleValue();
+ }
+}
+
+class AccessPublicMethodFromParent {
+ public void foo(Integer i) {
+ i.notify();
+ }
+}
+
+class AccessPublicStaticMethod {
+ public Integer foo() {
+ return Integer.getInteger("1");
+ }
+}
+
+class AccessPublicStaticField {
+ public int foo() {
+ return Integer.BYTES;
+ }
+}
+
+/**
+ * Check that the classes using non publicly listed APIS are not verified.
+ */
+
+class AccessNonPublicCtor {
+ public Integer foo() {
+ return new Integer("1");
+ }
+}
+
+class AccessNonPublicMethod {
+ public float foo(Integer i) {
+ return i.floatValue();
+ }
+}
+
+class AccessNonPublicMethodFromParent {
+ public void foo(Integer i) {
+ i.notifyAll();
+ }
+}
+
+class AccessNonPublicStaticMethod {
+ public Integer foo() {
+ return Integer.getInteger("1", 0);
+ }
+}
+
+class AccessNonPublicStaticField {
+ public Class foo() {
+ return Integer.TYPE;
+ }
+}