diff options
author | 2021-05-19 16:22:46 +0100 | |
---|---|---|
committer | 2021-06-02 19:07:01 +0000 | |
commit | a6b3b297bdd70ad3e915a48b02a7c7cf5f204e05 (patch) | |
tree | a721f273d8776a349dafc2ee496a76d401765074 | |
parent | 9c7b1e7073f3c309e8b0023974aa9136b22f4b2e (diff) |
Add flag for clamping hidden api list
Allow clamping the hidden api list to a max value; if an API is
restricted for a newer SDK, transform it into a regular unsupported API.
This change is laying out the groundwork to ensure that older platforms
do not receive dex code updated that has unknown hidden api flags - e.g.
if the hidden api encode step uses '--max-hiddenapi-level=max-target-q',
then the resulting dex code will not exhibit undefined behavior with respect to
hidden api access when running on R devices, and any newer 'max-target-r' APIs
will be marked as 'unsupported'.
Test: mma test-art-host-gtest-art_hiddenapi_tests
Bug: 172453495
Change-Id: I710c440c8429081096d8a417f164c0e01c97ecd9
-rw-r--r-- | libartbase/base/hiddenapi_flags.h | 17 | ||||
-rw-r--r-- | tools/hiddenapi/hiddenapi.cc | 23 | ||||
-rw-r--r-- | tools/hiddenapi/hiddenapi_test.cc | 56 |
3 files changed, 94 insertions, 2 deletions
diff --git a/libartbase/base/hiddenapi_flags.h b/libartbase/base/hiddenapi_flags.h index 375bf88abb..3ece966266 100644 --- a/libartbase/base/hiddenapi_flags.h +++ b/libartbase/base/hiddenapi_flags.h @@ -254,9 +254,26 @@ class ApiList { return true; } + // Clamp a max-target-* up to the given maxSdk; if the given api list is higher than + // maxSdk, return unsupported instead. + static std::string CoerceAtMost(const std::string& name, const std::string& maxSdk) { + const auto apiListToClamp = FromName(name); + // If the api list is a domain instead, return it unmodified. + if (!apiListToClamp.IsValid()) { + return name; + } + const auto maxApiList = FromName(maxSdk); + CHECK(maxApiList.IsValid()) << "invalid api list name " << maxSdk; + if (apiListToClamp > maxApiList) { + return kValueNames[Unsupported().GetIntValue()]; + } + return name; + } + bool operator==(const ApiList& other) const { return dex_flags_ == other.dex_flags_; } bool operator!=(const ApiList& other) const { return !(*this == other); } bool operator<(const ApiList& other) const { return dex_flags_ < other.dex_flags_; } + bool operator>(const ApiList& other) const { return dex_flags_ > other.dex_flags_; } // Returns true if combining this ApiList with `other` will succeed. bool CanCombineWith(const ApiList& other) const { diff --git a/tools/hiddenapi/hiddenapi.cc b/tools/hiddenapi/hiddenapi.cc index c249a7a3cd..d1bb3fd6d8 100644 --- a/tools/hiddenapi/hiddenapi.cc +++ b/tools/hiddenapi/hiddenapi.cc @@ -82,6 +82,11 @@ NO_RETURN static void Usage(const char* fmt, ...) { UsageError(" --api-flags=<filename>:"); UsageError(" CSV file with signatures of methods/fields and their respective flags"); UsageError(""); + UsageError(" --max-hiddenapi-level=<max-target-*>:"); + UsageError(" the maximum hidden api level for APIs. If an API was originally restricted"); + UsageError(" to a newer sdk, turn it into a regular unsupported API instead."); + UsageError(" instead. The full list of valid values is in hiddenapi_flags.h"); + UsageError(""); UsageError(" --no-force-assign-all:"); UsageError(" Disable check that all dex entries have been assigned a flag"); UsageError(""); @@ -908,6 +913,8 @@ class HiddenApi final { api_flags_path_ = std::string(option.substr(strlen("--api-flags="))); } else if (option == "--no-force-assign-all") { force_assign_all_ = false; + } else if (StartsWith(option, "--max-hiddenapi-level=")) { + max_hiddenapi_level_ = std::string(option.substr(strlen("--max-hiddenapi-level="))); } else { Usage("Unknown argument '%s'", raw_option); } @@ -1022,12 +1029,23 @@ class HiddenApi final { const std::string& signature = values[0]; + // Skip signature + std::vector<std::string>::iterator apiListBegin = values.begin() + 1; + std::vector<std::string>::iterator apiListEnd = values.end(); + if (!max_hiddenapi_level_.empty()) { + auto clamp_fn = [this](const std::string& apiListName) { + return ApiList::CoerceAtMost(apiListName, + max_hiddenapi_level_); + }; + std::transform(apiListBegin, apiListEnd, apiListBegin, clamp_fn); + } + CHECK(api_flag_map.find(signature) == api_flag_map.end()) << path << ":" << line_number << ": Duplicate entry: " << signature << kErrorHelp; ApiList membership; - bool success = ApiList::FromNames(values.begin() + 1, values.end(), &membership); + bool success = ApiList::FromNames(apiListBegin, apiListEnd, &membership); CHECK(success) << path << ":" << line_number << ": Some flags were not recognized: " << line << kErrorHelp; CHECK(membership.IsValid()) << path << ":" << line_number @@ -1156,6 +1174,9 @@ class HiddenApi final { // Path to CSV file containing the list of API members and their flags. // This could be both an input and output path. std::string api_flags_path_; + + // Override limit for sdk-max-* hidden APIs. + std::string max_hiddenapi_level_; }; } // namespace hiddenapi diff --git a/tools/hiddenapi/hiddenapi_test.cc b/tools/hiddenapi/hiddenapi_test.cc index 81e3f16047..2593edd938 100644 --- a/tools/hiddenapi/hiddenapi_test.cc +++ b/tools/hiddenapi/hiddenapi_test.cc @@ -66,8 +66,8 @@ class HiddenApiTest : public CommonRuntimeTest { std::vector<std::string> argv_str; argv_str.push_back(GetHiddenApiCmd()); - argv_str.insert(argv_str.end(), extra_args.begin(), extra_args.end()); argv_str.push_back("encode"); + argv_str.insert(argv_str.end(), extra_args.begin(), extra_args.end()); argv_str.push_back("--input-dex=" + in_dex.GetFilename()); argv_str.push_back("--output-dex=" + out_dex.GetFilename()); argv_str.push_back("--api-flags=" + flags_csv.GetFilename()); @@ -724,6 +724,60 @@ TEST_F(HiddenApiTest, InstanceFieldUnknownFlagMatch) { ASSERT_EQ(dex_file.get(), nullptr); } +TEST_F(HiddenApiTest, InstanceFieldMaxSdkHigherThanMaxHiddenApiLevel) { + ScratchFile dex, flags_csv; + OpenStream(flags_csv) + << "LMain;->ifield:I,max-target-r" << std::endl; + auto dex_file = RunHiddenapiEncode(flags_csv, {"--max-hiddenapi-level=max-target-q"}, dex); + ASSERT_NE(dex_file.get(), nullptr); + ASSERT_EQ(hiddenapi::ApiList::Unsupported(), GetIFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceFieldMaxSdkEqualsMaxHiddenApiLevel) { + ScratchFile dex, flags_csv; + OpenStream(flags_csv) + << "LMain;->ifield:I,max-target-r" << std::endl; + auto dex_file = RunHiddenapiEncode(flags_csv, {"--max-hiddenapi-level=max-target-r"}, dex); + ASSERT_NE(dex_file.get(), nullptr); + ASSERT_EQ(hiddenapi::ApiList::MaxTargetR(), GetIFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceFieldMaxSdkLowerThanMaxHiddenApiLevel) { + ScratchFile dex, flags_csv; + OpenStream(flags_csv) + << "LMain;->ifield:I,max-target-q" << std::endl; + auto dex_file = RunHiddenapiEncode(flags_csv, {"--max-hiddenapi-level=max-target-r"}, dex); + ASSERT_NE(dex_file.get(), nullptr); + ASSERT_EQ(hiddenapi::ApiList::MaxTargetQ(), GetIFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceFieldBlockedUnchangedByMaxHiddenApiLevel) { + ScratchFile dex, flags_csv; + OpenStream(flags_csv) + << "LMain;->ifield:I,blocked" << std::endl; + auto dex_file = RunHiddenapiEncode(flags_csv, {"--max-hiddenapi-level=max-target-r"}, dex); + ASSERT_NE(dex_file.get(), nullptr); + ASSERT_EQ(hiddenapi::ApiList::Blocked(), GetIFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceFieldUnsupportedUnchangedByMaxHiddenApiLevel) { + ScratchFile dex, flags_csv; + OpenStream(flags_csv) + << "LMain;->ifield:I,unsupported" << std::endl; + auto dex_file = RunHiddenapiEncode(flags_csv, {"--max-hiddenapi-level=max-target-r"}, dex); + ASSERT_NE(dex_file.get(), nullptr); + ASSERT_EQ(hiddenapi::ApiList::Unsupported(), GetIFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceFieldSdkUnchangedByMaxHiddenApiLevel) { + ScratchFile dex, flags_csv; + OpenStream(flags_csv) + << "LMain;->ifield:I,sdk" << std::endl; + auto dex_file = RunHiddenapiEncode(flags_csv, {"--max-hiddenapi-level=max-target-r"}, dex); + ASSERT_NE(dex_file.get(), nullptr); + ASSERT_EQ(hiddenapi::ApiList::Sdk(), GetIFieldHiddenFlags(*dex_file)); +} + // The following tests use this class hierarchy: // // AbstractPackageClass PublicInterface |