Signature policy for overlayable items

Add encoding/decoding of new policy for overlays. Signature enforces
that an overlay package is signed with the same key as the actor of
the target resource, so that an overlay can be installed by the user
as a normal app but restricted to those built by the author of the
actor (which can be the same as the target).

This also enforces that a valid policy is specified.

This doesn't implement the actors nor the signature check.

Bug: 119402606

Test: ResourceParserTest ParseOverlayablePolicy
Test: ProtoSerializerTest SerializeAndDeserializeOverlayable
Test: aapt2_tests

Change-Id: I8495ad790c2ebd51759bc6eba81149680c209475
This commit is contained in:
Winson
2019-02-04 16:32:43 -08:00
parent c20de15455
commit b2d7f5343d
11 changed files with 122 additions and 48 deletions

View File

@@ -1647,6 +1647,10 @@ struct ResTable_overlayable_policy_header
// The overlay must reside of the product partition or must have existed on the product
// partition before an upgrade to overlay these resources.
POLICY_PRODUCT_PARTITION = 0x00000008,
// The overlay must be signed with the same signature as the actor of the target resource,
// which can be separate or the same as the target package with the resource.
POLICY_SIGNATURE = 0x00000010,
};
uint32_t policy_flags;

View File

@@ -1113,6 +1113,13 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource
const std::string& element_name = parser->element_name();
const std::string& element_namespace = parser->element_namespace();
if (element_namespace.empty() && element_name == "item") {
if (current_policies == OverlayableItem::Policy::kNone) {
diag_->Error(DiagMessage(element_source)
<< "<item> within an <overlayable> must be inside a <policy> block");
error = true;
continue;
}
// Items specify the name and type of resource that should be overlayable
Maybe<StringPiece> item_name = xml::FindNonEmptyAttribute(parser, "name");
if (!item_name) {
@@ -1169,6 +1176,8 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource
current_policies |= OverlayableItem::Policy::kSystem;
} else if (trimmed_part == "vendor") {
current_policies |= OverlayableItem::Policy::kVendor;
} else if (trimmed_part == "signature") {
current_policies |= OverlayableItem::Policy::kSignature;
} else {
diag_->Error(DiagMessage(element_source)
<< "<policy> has unsupported type '" << trimmed_part << "'");
@@ -1176,6 +1185,11 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource
continue;
}
}
} else {
diag_->Error(DiagMessage(element_source)
<< "<policy> must have a 'type' attribute");
error = true;
continue;
}
} else if (!ShouldIgnoreElement(element_namespace, element_name)) {
diag_->Error(DiagMessage(element_source) << "invalid element <" << element_name << "> "

View File

@@ -894,8 +894,10 @@ TEST_F(ResourceParserTest, ParsePlatformIndependentNewline) {
TEST_F(ResourceParserTest, ParseOverlayable) {
std::string input = R"(
<overlayable name="Name" actor="overlay://theme">
<item type="string" name="foo" />
<item type="drawable" name="bar" />
<policy type="signature">
<item type="string" name="foo" />
<item type="drawable" name="bar" />
</policy>
</overlayable>)";
ASSERT_TRUE(TestParse(input));
@@ -906,7 +908,7 @@ TEST_F(ResourceParserTest, ParseOverlayable) {
OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name"));
EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme"));
EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kNone));
EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kSignature));
search_result = table_.FindResource(test::ParseNameOrDie("drawable/bar"));
ASSERT_TRUE(search_result);
@@ -915,7 +917,7 @@ TEST_F(ResourceParserTest, ParseOverlayable) {
result_overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name"));
EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme"));
EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kNone));
EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kSignature));
}
TEST_F(ResourceParserTest, ParseOverlayableRequiresName) {
@@ -931,7 +933,6 @@ TEST_F(ResourceParserTest, ParseOverlayableBadActorFail) {
TEST_F(ResourceParserTest, ParseOverlayablePolicy) {
std::string input = R"(
<overlayable name="Name">
<item type="string" name="foo" />
<policy type="product">
<item type="string" name="bar" />
</policy>
@@ -944,23 +945,18 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) {
<policy type="public">
<item type="string" name="faz" />
</policy>
<policy type="signature">
<item type="string" name="foz" />
</policy>
</overlayable>)";
ASSERT_TRUE(TestParse(input));
auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo"));
auto search_result = table_.FindResource(test::ParseNameOrDie("string/bar"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
ASSERT_TRUE(search_result.value().entry->overlayable_item);
OverlayableItem result_overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name"));
EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kNone));
search_result = table_.FindResource(test::ParseNameOrDie("string/bar"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
ASSERT_TRUE(search_result.value().entry->overlayable_item);
result_overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name"));
EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProduct));
search_result = table_.FindResource(test::ParseNameOrDie("string/fiz"));
@@ -986,6 +982,30 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) {
result_overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name"));
EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kPublic));
search_result = table_.FindResource(test::ParseNameOrDie("string/foz"));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
ASSERT_TRUE(search_result.value().entry->overlayable_item);
result_overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name"));
EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kSignature));
}
TEST_F(ResourceParserTest, ParseOverlayableNoPolicyError) {
std::string input = R"(
<overlayable name="Name">
<item type="string" name="foo" />
</overlayable>)";
EXPECT_FALSE(TestParse(input));
input = R"(
<overlayable name="Name">
<policy>
<item name="foo" />
</policy>
</overlayable>)";
EXPECT_FALSE(TestParse(input));
}
TEST_F(ResourceParserTest, ParseOverlayableBadPolicyError) {

View File

@@ -92,6 +92,9 @@ struct OverlayableItem {
// The resource can be overlaid by any overlay on the product partition.
kProduct = 0x08,
// The resource can be overlaid by any overlay signed with the same signature as its actor.
kSignature = 0x010,
};
std::shared_ptr<Overlayable> overlayable;

View File

@@ -138,10 +138,10 @@ message AllowNew {
// Represents a set of overlayable resources.
message Overlayable {
// The name of the <overlyabale>.
// The name of the <overlayable>.
string name = 1;
// The location of the <overlyabale> declaration in the source.
// The location of the <overlayable> declaration in the source.
Source source = 2;
// The component responsible for enabling and disabling overlays targeting this <overlayable>.

View File

@@ -473,6 +473,10 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) {
& ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION) {
policies |= OverlayableItem::Policy::kProduct;
}
if (policy_header->policy_flags
& ResTable_overlayable_policy_header::POLICY_SIGNATURE) {
policies |= OverlayableItem::Policy::kSignature;
}
const ResTable_ref* const ref_begin = reinterpret_cast<const ResTable_ref*>(
((uint8_t *)policy_header) + util::DeviceToHost32(policy_header->header.headerSize));

View File

@@ -274,7 +274,9 @@ class PackageFlattener {
FlattenLibrarySpec(buffer);
}
FlattenOverlayable(buffer);
if (!FlattenOverlayable(buffer)) {
return false;
}
pkg_writer.Finish();
return true;
@@ -468,23 +470,29 @@ class PackageFlattener {
overlayable_chunk = &chunk;
}
if (item.policies == 0) {
context_->GetDiagnostics()->Error(DiagMessage(item.overlayable->source)
<< "overlayable "
<< entry->name
<< " does not specify policy");
return false;
}
uint32_t policy_flags = 0;
if (item.policies == OverlayableItem::Policy::kNone) {
// Encode overlayable entries defined without a policy as publicly overlayable
if (item.policies & OverlayableItem::Policy::kPublic) {
policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC;
} else {
if (item.policies & OverlayableItem::Policy::kPublic) {
policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC;
}
if (item.policies & OverlayableItem::Policy::kSystem) {
policy_flags |= ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION;
}
if (item.policies & OverlayableItem::Policy::kVendor) {
policy_flags |= ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION;
}
if (item.policies & OverlayableItem::Policy::kProduct) {
policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION;
}
}
if (item.policies & OverlayableItem::Policy::kSystem) {
policy_flags |= ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION;
}
if (item.policies & OverlayableItem::Policy::kVendor) {
policy_flags |= ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION;
}
if (item.policies & OverlayableItem::Policy::kProduct) {
policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION;
}
if (item.policies & OverlayableItem::Policy::kSignature) {
policy_flags |= ResTable_overlayable_policy_header::POLICY_SIGNATURE;
}
auto policy = overlayable_chunk->policy_ids.find(policy_flags);

View File

@@ -671,9 +671,6 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) {
overlayable_item_two.policies |= OverlayableItem::Policy::kSystem;
overlayable_item_two.policies |= OverlayableItem::Policy::kVendor;
std::string name_three = "com.app.test:integer/overlayable_three_item";
OverlayableItem overlayable_item_three(overlayable);
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
.SetPackageId("com.app.test", 0x7f)
@@ -683,8 +680,6 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) {
.SetOverlayable(name_one, overlayable_item_one)
.AddSimple(name_two, ResourceId(0x7f020002))
.SetOverlayable(name_two, overlayable_item_two)
.AddSimple(name_three, ResourceId(0x7f020003))
.SetOverlayable(name_three, overlayable_item_three)
.Build();
ResourceTable output_table;
@@ -713,16 +708,6 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) {
EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kSystem
| OverlayableItem::Policy::kProduct
| OverlayableItem::Policy::kVendor);
search_result = output_table.FindResource(test::ParseNameOrDie(name_three));
ASSERT_TRUE(search_result);
ASSERT_THAT(search_result.value().entry, NotNull());
ASSERT_TRUE(search_result.value().entry->overlayable_item);
overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kPublic);
EXPECT_EQ(overlayable_item.overlayable->name, "TestName");
EXPECT_EQ(overlayable_item.overlayable->actor, "overlay://theme");
EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kPublic);
}
TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) {
@@ -745,6 +730,8 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) {
std::string name_three = "com.app.test:integer/overlayable_three";
OverlayableItem overlayable_item_three(group_one);
overlayable_item_three.policies |= OverlayableItem::Policy::kSignature;
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
.SetPackageId("com.app.test", 0x7f)
@@ -793,7 +780,22 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) {
result_overlayable = search_result.value().entry->overlayable_item.value();
EXPECT_EQ(result_overlayable.overlayable->name, "OtherName");
EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://customization");
EXPECT_EQ(result_overlayable.policies, OverlayableItem::Policy::kPublic);
EXPECT_EQ(result_overlayable.policies, OverlayableItem::Policy::kSignature);
}
TEST_F(TableFlattenerTest, FlattenOverlayableNoPolicyFails) {
auto group = std::make_shared<Overlayable>("TestName", "overlay://theme");
std::string name_zero = "com.app.test:integer/overlayable_zero";
OverlayableItem overlayable_item_zero(group);
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
.SetPackageId("com.app.test", 0x7f)
.AddSimple(name_zero, ResourceId(0x7f020000))
.SetOverlayable(name_zero, overlayable_item_zero)
.Build();
ResourceTable output_table;
ASSERT_FALSE(Flatten(context_.get(), {}, table.get(), &output_table));
}
} // namespace aapt

View File

@@ -390,6 +390,9 @@ bool DeserializeOverlayableItemFromPb(const pb::OverlayableItem& pb_overlayable,
case pb::OverlayableItem::PRODUCT:
out_overlayable->policies |= OverlayableItem::Policy::kProduct;
break;
case pb::OverlayableItem::SIGNATURE:
out_overlayable->policies |= OverlayableItem::Policy::kSignature;
break;
default:
*out_error = "unknown overlayable policy";
return false;

View File

@@ -309,6 +309,9 @@ static void SerializeOverlayableItemToPb(const OverlayableItem& overlayable_item
if (overlayable_item.policies & OverlayableItem::Policy::kVendor) {
pb_overlayable_item->add_policy(pb::OverlayableItem::VENDOR);
}
if (overlayable_item.policies & OverlayableItem::Policy::kSignature) {
pb_overlayable_item->add_policy(pb::OverlayableItem::SIGNATURE);
}
SerializeSourceToPb(overlayable_item.source, source_pool,
pb_overlayable_item->mutable_source());

View File

@@ -526,6 +526,10 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) {
"FontPack", "overlay://theme"));
overlayable_item_baz.policies |= OverlayableItem::Policy::kPublic;
OverlayableItem overlayable_item_boz(std::make_shared<Overlayable>(
"IconPack", "overlay://theme"));
overlayable_item_boz.policies |= OverlayableItem::Policy::kSignature;
OverlayableItem overlayable_item_biz(std::make_shared<Overlayable>(
"Other", "overlay://customization"));
overlayable_item_biz.comment ="comment";
@@ -536,6 +540,7 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) {
.SetOverlayable("com.app.a:bool/foo", overlayable_item_foo)
.SetOverlayable("com.app.a:bool/bar", overlayable_item_bar)
.SetOverlayable("com.app.a:bool/baz", overlayable_item_baz)
.SetOverlayable("com.app.a:bool/boz", overlayable_item_boz)
.SetOverlayable("com.app.a:bool/biz", overlayable_item_biz)
.AddValue("com.app.a:bool/fiz", ResourceUtils::TryParseBool("true"))
.Build();
@@ -576,6 +581,14 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) {
EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://theme"));
EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kPublic));
search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/boz"));
ASSERT_TRUE(search_result);
ASSERT_TRUE(search_result.value().entry->overlayable_item);
overlayable_item = search_result.value().entry->overlayable_item.value();
EXPECT_THAT(overlayable_item.overlayable->name, Eq("IconPack"));
EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://theme"));
EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kSignature));
search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/biz"));
ASSERT_TRUE(search_result);
ASSERT_TRUE(search_result.value().entry->overlayable_item);