本章主要讲“属性安全上下文序列化”,现给出完整数据流程图
void CreateSerializedPropertyInfo() {
auto property_infos = std::vector();
if (access("/system/etc/selinux/plat_property_contexts", R_OK) != -1) {
if (!LoadPropertyInfoFromFile("/system/etc/selinux/plat_property_contexts",
&property_infos)) {
return;
}
if (!LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",
&property_infos)) {
LoadPropertyInfoFromFile("/vendor/etc/selinux/nonplat_property_contexts",
&property_infos);
}
if (access("/product/etc/selinux/product_property_contexts", R_OK) != -1) {
LoadPropertyInfoFromFile("/product/etc/selinux/product_property_contexts",
&property_infos);
}
if (access("/odm/etc/selinux/odm_property_contexts", R_OK) != -1) {
LoadPropertyInfoFromFile("/odm/etc/selinux/odm_property_contexts", &property_infos);
}
} else {
if (!LoadPropertyInfoFromFile("/plat_property_contexts", &property_infos)) {
return;
}
if (!LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos)) {
LoadPropertyInfoFromFile("/nonplat_property_contexts", &property_infos);
}
LoadPropertyInfoFromFile("/product_property_contexts", &property_infos);
LoadPropertyInfoFromFile("/odm_property_contexts", &property_infos);
}
auto serialized_contexts = std::string();
auto error = std::string();
if (!BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts,
&error)) {
LOG(ERROR) << "Unable to serialize property contexts: " << error;
return;
}
constexpr static const char kPropertyInfosPath[] = "/dev/__properties__/property_info";
if (!WriteStringToFile(serialized_contexts, kPropertyInfosPath, 0444, 0, 0, false)) {
PLOG(ERROR) << "Unable to write serialized property infos to file";
}
selinux_android_restorecon(kPropertyInfosPath, 0);
}
这张图左侧将CreateSerializedPropertyInfo函数功能分为如下几部分:
- 读取属性相关安全上下文文件内容,构建成vector数组
- 将vector转化为TrieBuilder数据结构
- 使用TrieSerializer类对TrieBuilder中数据按照一定的结构写入内存,然后序列化为字符串
- 将字符串写入/dev/properties/property_info文件中
构建PropertyInfoEntry数组
属性安全上下文文件是在编译系统源码时产生的(这个过程就不详细讨论了),以AS上的如下google模拟器为例
属性安全上下文文件有如下文件
/system/etc/selinux/plat_property_contexts
/vendor/etc/selinux/vendor_property_contexts
文件部分内容如下:
audio.camerasound.force u:object_r:exported_audio_prop:s0 exact bool
audio.deep_buffer.media u:object_r:exported3_default_prop:s0 exact bool
audio.offload.video u:object_r:exported3_default_prop:s0 exact bool
audio.offload.min.duration.secs u:object_r:exported3_default_prop:s0 exact int
camera.disable_zsl_mode u:object_r:exported3_default_prop:s0 exact bool
camera.fifo.disable u:object_r:exported3_default_prop:s0 exact int
dalvik.vm.appimageformat u:object_r:exported_dalvik_prop:s0 exact string
dalvik.vm.backgroundgctype u:object_r:exported_dalvik_prop:s0 exact string
dalvik.vm.boot-dex2oat-threads u:object_r:exported_dalvik_prop:s0 exact int
dalvik.vm.boot-image u:object_r:exported_dalvik_prop:s0 exact string
dalvik.vm.checkjni u:object_r:exported_dalvik_prop:s0 exact bool
dalvik.vm.dex2oat-Xms u:object_r:exported_dalvik_prop:s0 exact string
dalvik.vm.dex2oat-Xmx u:object_r:exported_dalvik_prop:s0 exact string
文件部分内容如下:
qemu. u:object_r:qemu_prop:s0
qemu.cmdline u:object_r:qemu_cmdline:s0
vendor.qemu u:object_r:qemu_prop:s0
vendor.network u:object_r:vendor_net:s0
ro.emu. u:object_r:qemu_prop:s0
ro.emulator. u:object_r:qemu_prop:s0
ro.radio.noril u:object_r:radio_noril_prop:s0
net.eth0. u:object_r:net_eth0_prop:s0
net.radio0. u:object_r:net_radio0_prop:s0
net.shared_net_ip u:object_r:net_share_prop:s0
ro.zygote.disable_gl_preload u:object_r:qemu_prop:s0
#line 1 "system/sepolicy/reqd_mask/property_contexts"
# empty property_contexts file - this file is used to generate an empty
# non-platform property context for devices without any property_contexts
# customizations.
plat_property_contexts中内容结构如下:
属性名 属性安全上下文(与selinux有关) 标志 属性值的类型
audio.camerasound.force u:object_r:exported_audio_prop:s0 exact bool
vendor_property_contexts内容与plat_property_contexts有以下几点差异:
- vendor_property_contexts中没有类似exact string等字段
- vendor_property_contexts中属性key有的是以.结尾的(这个后续分析代码会用到)
LoadPropertyInfoFromFile函数的作用就很清晰了,就是解析这些文件的内容保存到vector数组中(函数实现不复杂,大家自行看一下就可以了)。
注意:属性安全上下文文件中是没有属性value的,因为属性value的初始化不在在这个阶段实现的
void ParsePropertyInfoFile(const std::string& file_contents,
std::vector* property_infos,
std::vector<std::string>* errors) {
errors->clear();
for (const auto& line : Split(file_contents, "n")) {
auto trimmed_line = Trim(line);
if (trimmed_line.empty() || StartsWith(trimmed_line, "#")) {
continue;
}
auto property_info_entry = PropertyInfoEntry{};
auto parse_error = std::string{};
if (!ParsePropertyInfoLine(trimmed_line, &property_info_entry, &parse_error)) {
errors->emplace_back(parse_error);
continue;
}
property_infos->emplace_back(property_info_entry);
}
}
PropertyInfoEntry数组转化为TrieBuilder
这一过程是在如下调用中实现
BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts,
&error)
bool BuildTrie(const std::vector& property_info,
const std::string& default_context, const std::string& default_type,
std::string* serialized_trie, std::string* error) {
auto trie_builder = TrieBuilder(default_context, default_type);
for (const auto& [name, context, type, is_exact] : property_info) {
if (!trie_builder.AddToTrie(name, context, type, is_exact, error)) {
return false;
}
}
auto trie_serializer = TrieSerializer();
*serialized_trie = trie_serializer.SerializeTrie(trie_builder);
return true;
}
将vector转化为TrieBuilder数据结构过程大致做了如下工作:
- 将vector所有PropertyInfoEntry->context保存到TrieBuilder->contexts_中
- 将vector所有PropertyInfoEntry->type保存到TrieBuilder->types_中
- 将vector构建成一棵字典树(自行查阅字典树结构)
- 字典树的根节点是TrieBuilder->builder_root_
- 字典树每个节点是TrieBuilderNode,它里面会保存PropertyInfoEntry中的数据信息
auto trie_builder = TrieBuilder("u:object_r:default_prop:s0", "string");
TrieBuilder::TrieBuilder(const std::string& default_context, const std::string& default_type)
: builder_root_("root") {
auto* context_pointer = StringPointerFromContainer(default_context, &contexts_);
builder_root_.set_context(context_pointer);
auto* type_pointer = StringPointerFromContainer(default_type, &types_);
builder_root_.set_type(type_pointer);
}
- 调用TrieBuilder构造函数,同时创建root节点builder_root_,并设置root->property_entry_的name、context、type分别为”root”、”u:object_r:default_prop:s0″、”string”
- StringPointerFromContainer将root节点默认的default_context和default_type分别保存到TrieBuilder->contexts_和TrieBuilder->types_中
for (const auto& [name, context, type, is_exact] : property_info) {
if (!trie_builder.AddToTrie(name, context, type, is_exact, error)) {
return false;
}
}
bool TrieBuilder::AddToTrie(const std::string& name, const std::string& context,
const std::string& type, bool exact, std::string* error) {
auto* context_pointer = StringPointerFromContainer(context, &contexts_);
auto* type_pointer = StringPointerFromContainer(type, &types_);
return AddToTrie(name, context_pointer, type_pointer, exact, error);
}
这里就是循环遍历vector数组,StringPointerFromContainer将PropertyInfoEntry的context和type保存到TrieBuilder->contexts_和TrieBuilder->types_中
bool TrieBuilder::AddToTrie(const std::string& name, const std::string* context,
const std::string* type, bool exact, std::string* error) {
TrieBuilderNode* current_node = &builder_root_;
auto name_pieces = Split(name, ".");
bool ends_with_dot = false;
if (name_pieces.back().empty()) {
ends_with_dot = true;
name_pieces.pop_back();
}
while (name_pieces.size() > 1) {
auto child = current_node->FindChild(name_pieces.front());
if (child == nullptr) {
child = current_node->AddChild(name_pieces.front());
}
if (child == nullptr) {
*error = "Unable to allocate Trie node";
return false;
}
current_node = child;
name_pieces.erase(name_pieces.begin());
}
return true;
}
以下面属性为例
dalvik.vm.checkjni u:object_r:exported_dalvik_prop:s0 exact bool
dalvik.vm.dex2oat-Xms u:object_r:exported_dalvik_prop:s0 exact string
dalvik.vm.dex2oat-Xmx u:object_r:exported_dalvik_prop:s0 exact string
ro.emulator. u:object_r:qemu_prop:s0
vendor.qemu u:object_r:qemu_prop:s0
vendor.network u:object_r:vendor_net:s0
会构建出如下字典树的一部分
在上图所示字典树构建过程中current_node会依次指向vm、ro、qemu等节点,然后调用如下代码进一步构建出完整字典树。
bool TrieBuilder::AddToTrie(const std::string& name, const std::string* context,
const std::string* type, bool exact, std::string* error) {
TrieBuilderNode* current_node = &builder_root_;
if (exact) {
if (!current_node->AddExactMatchContext(name_pieces.front(), context, type)) {
*error = "Duplicate exact match detected for '" + name + "'";
return false;
}
} else if (!ends_with_dot) {
if (!current_node->AddPrefixContext(name_pieces.front(), context, type)) {
*error = "Duplicate prefix match detected for '" + name + "'";
return false;
}
} else {
auto child = current_node->FindChild(name_pieces.front());
if (child == nullptr) {
child = current_node->AddChild(name_pieces.front());
}
if (child == nullptr) {
*error = "Unable to allocate Trie node";
return false;
}
if (child->context() != nullptr || child->type() != nullptr) {
*error = "Duplicate prefix match detected for '" + name + "'";
return false;
}
child->set_context(context);
child->set_type(type);
}
return true;
}
而这段代码的逻辑处理依据就是前面将的vendor_property_contexts内容与plat_property_contexts有以下几点差异,总结如下:
- 有exact关键字属性处理
- 没有exact关键字属性处理
- .结尾s属性处理
- 非.结尾s属性处理
根据这些特性处理后,构建的完整字典树如下
这里需要对TrieBuilderNode和PropertyEntryBuilder做一下详细说明
- PropertyEntryBuilder
- name:当前节点的名字,例如图中vm节点
- context、type:如果该节点是整个属性的最后一个节点,那么值存在(例如emulator节点,因为ro.emulator没有定义type,所以没有值);否则值为空
- TrieBuilderNode
-
property_entry_:记录当前节点的name、context、type信息
-
children_:子节点,例如dalvik.vm.checkjni,vm是dalvik的子节点
-
exact_matches_:具有相同前缀且都有exact标志的属性(称为精准属性),都会放到此数组中,上图中如下属性就是此类情况
dalvik.vm.checkjni u:object_r:exported_dalvik_prop:s0 exact bool dalvik.vm.dex2oat-Xms u:object_r:exported_dalvik_prop:s0 exact string dalvik.vm.dex2oat-Xmx u:object_r:exported_dalvik_prop:s0 exact string
-
prefixes_:具有相同前缀且没有exact标志的属性,都会放到此数组中,上图中如下属性就是此类情况
vendor.qemu u:object_r:qemu_prop:s0 vendor.network u:object_r:vendor_net:s0
-
有了上面这些知识后,读者分析上面这段代码就会变得简单许多(读者自行分析一下)。这里重点说一下.结尾的属性
auto child = current_node->FindChild(name_pieces.front());
if (child == nullptr) {
child = current_node->AddChild(name_pieces.front());
}
if (child == nullptr) {
*error = "Unable to allocate Trie node";
return false;
}
if (child->context() != nullptr || child->type() != nullptr) {
*error = "Duplicate prefix match detected for '" + name + "'";
return false;
}
child->set_context(context);
child->set_type(type);
与其它类型属性不同,它会创建一个节点然后添加到字典树中,因为这个节点是整个属性最后的节点,所以会设置context和type。
TrieBuilder序列化
BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts,
&error)
bool BuildTrie(const std::vector& property_info,
const std::string& default_context, const std::string& default_type,
std::string* serialized_trie, std::string* error) {
auto trie_builder = TrieBuilder(default_context, default_type);
for (const auto& [name, context, type, is_exact] : property_info) {
if (!trie_builder.AddToTrie(name, context, type, is_exact, error)) {
return false;
}
}
auto trie_serializer = TrieSerializer();
*serialized_trie = trie_serializer.SerializeTrie(trie_builder);
return true;
}
接着看看SerializeTrie是如何实现序列化的
std::string TrieSerializer::SerializeTrie(const TrieBuilder& trie_builder) {
arena_.reset(new TrieNodeArena());
auto header = arena_->AllocateObject(nullptr);
header->current_version = 1;
header->minimum_supported_version = 1;
header->contexts_offset = arena_->size();
SerializeStrings(trie_builder.contexts());
header->types_offset = arena_->size();
SerializeStrings(trie_builder.types());
header->size = arena_->size();
uint32_t root_trie_offset = WriteTrieNode(trie_builder.builder_root());
header->root_offset = root_trie_offset;
header->size = arena_->size();
return arena_->truncated_data();
}
经过上面 4 部分写入后,内存中的数据排列如下:
最后回到BuildTrie调用的地方
void CreateSerializedPropertyInfo() {
auto serialized_contexts = std::string();
auto error = std::string();
if (!BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts,
&error)) {
LOG(ERROR) << "Unable to serialize property contexts: " << error;
return;
}
constexpr static const char kPropertyInfosPath[] = "/dev/__properties__/property_info";
if (!WriteStringToFile(serialized_contexts, kPropertyInfosPath, 0444, 0, 0, false)) {
PLOG(ERROR) << "Unable to write serialized property infos to file";
}
selinux_android_restorecon(kPropertyInfosPath, 0);
}
- serialized_contexts为序列化返回的字符串
- WriteStringToFile将serialized_contexts写入到”/dev/properties/property_info”中
到此属性安全上下文序列化玩成。
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=21083,转载请注明出处。
评论0