Android10 Framework—Init进程-7.服务端属性安全上下文序列化

本章主要讲"属性安全上下文序列化",现给出完整数据流程图

c 复制代码
void CreateSerializedPropertyInfo() {
    auto property_infos = std::vector<PropertyInfoEntry>();
    //读取属性相关安全上下文文件内容,构建成vector<PropertyInfoEntry>数组
    if (access("/system/etc/selinux/plat_property_contexts", R_OK) != -1) {
        if (!LoadPropertyInfoFromFile("/system/etc/selinux/plat_property_contexts",
                                      &property_infos)) {
            return;
        }
        // Don't check for failure here, so we always have a sane list of properties.
        // E.g. In case of recovery, the vendor partition will not have mounted and we
        // still need the system / platform properties to function.
        if (!LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",
                                      &property_infos)) {
            // Fallback to nonplat_* if vendor_* doesn't exist.
            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)) {
            // Fallback to nonplat_* if vendor_* doesn't exist.
            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();
    //将vector<PropertyInfoEntry>再转化为TrieBuilder数据结构
    //使用TrieSerializer类对TrieBuilder数据结构序列化为字符串
    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";
    //将字符串写入/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模拟器为例

属性安全上下文文件有如下文件

bash 复制代码
/system/etc/selinux/plat_property_contexts
/vendor/etc/selinux/vendor_property_contexts

文件部分内容如下:

c 复制代码
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

文件部分内容如下:

c 复制代码
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中内容结构如下:

c 复制代码
属性名                   属性安全上下文(与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的初始化不在在这个阶段实现的

c 复制代码
//system/core/property_service/libpropertyinfoserializer/property_info_file.cpp
    
void ParsePropertyInfoFile(const std::string& file_contents,
                           std::vector<PropertyInfoEntry>* property_infos,
                           std::vector<std::string>* errors) {
  // Do not clear property_infos to allow this function to be called on multiple files, with
  // their results concatenated.
  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{};
    //对一行数据进行解析,然后将数据填充到property_info_entry
    if (!ParsePropertyInfoLine(trimmed_line, &property_info_entry, &parse_error)) {
      errors->emplace_back(parse_error);
      continue;
    }
    //添加到数组中
    property_infos->emplace_back(property_info_entry);
  }
}    

PropertyInfoEntry数组转化为TrieBuilder

这一过程是在如下调用中实现

c 复制代码
//调用    
BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts,
                   &error)
    
//实现
bool BuildTrie(const std::vector<PropertyInfoEntry>& property_info,
               const std::string& default_context, const std::string& default_type,
               std::string* serialized_trie, std::string* error) {
  //将vector<PropertyInfoEntry>转化为TrieBuilder数据结构
  // Check that names are legal first
  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;
    }
  }

  //使用TrieSerializer类对TrieBuilder中数据按照一定的结构写入内存,然后序列化为字符串
  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中的数据信息
c 复制代码
//调用
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_中
c 复制代码
//调用
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_中

c 复制代码
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();
  }

  // Move us to the final node that we care about, adding incremental nodes if necessary.
  //构建字典树
  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的第一项插入到字典树,因此去除第一项
    name_pieces.erase(name_pieces.begin());
  }

  return true;
}

以下面属性为例

c 复制代码
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等节点,然后调用如下代码进一步构建出完整字典树。

c 复制代码
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_;

  // Store our context based on what type of match it is.
  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标志的属性(称为精准属性),都会放到此数组中,上图中如下属性就是此类情况

      c 复制代码
      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标志的属性,都会放到此数组中,上图中如下属性就是此类情况

      c 复制代码
      vendor.qemu             u:object_r:qemu_prop:s0
      vendor.network          u:object_r:vendor_net:s0

有了上面这些知识后,读者分析上面这段代码就会变得简单许多(读者自行分析一下)。这里重点说一下.结尾的属性

c 复制代码
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序列化

c 复制代码
//调用    
BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts,
                   &error)
    
//实现
bool BuildTrie(const std::vector<PropertyInfoEntry>& property_info,
               const std::string& default_context, const std::string& default_type,
               std::string* serialized_trie, std::string* error) {
  //将vector<PropertyInfoEntry>转化为TrieBuilder数据结构
  // Check that names are legal first
  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;
    }
  }

  //使用TrieSerializer类对TrieBuilder中数据按照一定的结构写入内存,然后序列化为字符串
  auto trie_serializer = TrieSerializer();
  *serialized_trie = trie_serializer.SerializeTrie(trie_builder);
  return true;
}

接着看看SerializeTrie是如何实现序列化的

c 复制代码
//system/core/property_service/libpropertyinfoserializer/trie_serializer.cpp
    
std::string TrieSerializer::SerializeTrie(const TrieBuilder& trie_builder) {
  arena_.reset(new TrieNodeArena());

  //arena_指针分配了一个header空间
  auto header = arena_->AllocateObject<PropertyInfoAreaHeader>(nullptr);
  //header写入数据
  header->current_version = 1;
  header->minimum_supported_version = 1;

  // Store where we're about to write the contexts.
  header->contexts_offset = arena_->size();
  //将TrieBuilder->contexts_序列化写入header的后面
  SerializeStrings(trie_builder.contexts());

  // Store where we're about to write the types.
  header->types_offset = arena_->size();
  //将TrieBuilder->types_序列化写入TrieBuilder->contexts_的后面
  SerializeStrings(trie_builder.types());

  // We need to store size() up to this point now for Find*Offset() to work.
  header->size = arena_->size();
  //将TrieBuilder->builder_root整颗字典树写入TrieBuilder->types_的后面
  uint32_t root_trie_offset = WriteTrieNode(trie_builder.builder_root());
  header->root_offset = root_trie_offset;

  // Record the real size now that we've written everything
  header->size = arena_->size();

  return arena_->truncated_data();
}

经过上面 4 部分写入后,内存中的数据排列如下:

最后回到BuildTrie调用的地方

vbnet 复制代码
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"中

到此属性安全上下文序列化玩成。

相关推荐
千里马学框架4 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台4 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone4 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc4 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo4 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077004 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼4 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone4 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen4 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone4 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui