[踩坑][Android][SELinux]native进程调用HIDL接口时需要hwservicemanager_prop的map权限

[踩坑][Android][SELinux]native进程调用HIDL接口时需要hwservicemanager_prop的map权限

背景

在MTK的某Android 10基线上,我们有一个自研的native daemon在运行后出现不工作,且无限输出avc denied的信息,大体格式如下:

05-05 19:51:10.188   426   426 W ServiceManagement: Waited for hwservicemanager.ready for a second, waiting another...
05-05 19:51:10.186   426   426 W cyclonehubd: type=1400 audit(0.0:122025): avc: denied { map } for path="/dev/__properties__/u:object_r:hwservicemanager_prop:s0" dev="tmpfs" ino=10774 scontext=u:r:cyclonehubd:s0 tcontext=u:object_r:hwservicemanager_prop:s0 tclass=file permissive=0
05-05 19:51:10.190   426   426 E libc    : Access denied finding property "hwservicemanager.ready"
05-05 19:51:10.186   426   426 W cyclonehubd: type=1400 audit(0.0:122026): avc: denied { map } for path="/dev/__properties__/u:object_r:hwservicemanager_prop:s0" dev="tmpfs" ino=10774 scontext=u:r:cyclonehubd:s0 tcontext=u:object_r:hwservicemanager_prop:s0 tclass=file permissive=0

看似很简单,无论是直接手动添加,亦或是使用audit2allow,结果都是一样的,叫我添加map权限:

allow cyclonehubd hwservicemanager_prop:file map;

加上之前的读权限,该条规则的完整写法应该是:

allow cyclonehubd hwservicemanager_prop:file { read getattr map open };

编译push进去验证也是OK的,问题似乎就这么解决了,如果只是看解决方案的,本文看到这里即可;

思考

但是问题也随之而来了,这条基线上曾今跑过其他项目,而没有添加这条map权限也没有输出上述信息,功能本身也是正常的。

为了确认根本原因,我开始了一上午的深挖之路;

排除嫌疑

首先,基于现有信息,我们首先想到了如下两个可能的原因:

  1. MTK针对不同配置项目有逻辑区分(本项目是GMO低内存项目,之前那个不是);
  2. SELinux内核规则变更(本项目采用kernel-4.14,之前那个是kernel-4.9)

Step 1

为了一一排查,首先使用userdebug版本的debuggerd抓取了一下出问题的进程的调用栈:

$ adb shell debuggerd 426 //需要root权限,可在pid之前加上--backtrace 只显示backtrace

输出结果可以重定向到一个文件中,打开查看,如下是部分有效信息:

backtrace:
      #00 pc 0005a7bc  /apex/com.android.runtime/lib/bionic/libc.so (syscall+32) (BuildId: 868238a82fc92126d62dea5a3bb1611c)
      #01 pc 0005f027  /apex/com.android.runtime/lib/bionic/libc.so (SystemProperties::Wait(prop_info const*, unsigned int, unsigned int*, timespec const*)+46) (BuildId: 868238a82fc92126d62dea5a3bb1611c)
      #02 pc 00069497  /apex/com.android.runtime/lib/bionic/libc.so (__system_property_wait+18) (BuildId: 868238a82fc92126d62dea5a3bb1611c)
      #03 pc 00009f5d  /system/lib/libbase.so (_ZN7android4base15WaitForPropertyERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEES9_NS1_6chrono8durationIxNS1_5ratioILx1ELx1000EEEEE+84) (BuildId: a28585ee446ea17e3e6fcf9c907fff2a)
      #04 pc 0003def5  /system/lib/libhidlbase.so (android::hardware::defaultServiceManager1_2()+320) (BuildId: 628474fe755e6f8657005ae90819d29c)
      #05 pc 0003f2d9  /system/lib/libhidlbase.so (android::hardware::details::getRawServiceInternal(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, bool, bool)+36) (BuildId: 628474fe755e6f8657005ae90819d29c)
      #06 pc 00014f1f  /system/lib/vendor.mediatek.hardware.mtkpower@1.0.so (_ZN7android8hardware7details18getServiceInternalIN6vendor8mediatek8hardware8mtkpower4V1_012BpHwMtkPowerENS7_9IMtkPowerEvvEENS_2spIT0_EERKNSt3__112basic_stringIcNSD_11char_traitsIcEENSD_9allocatorIcEEEEbb+130) (BuildId: 3ae311b72bbeae94e3cf238decab2468)
      #07 pc 00015037  /system/lib/vendor.mediatek.hardware.mtkpower@1.0.so (vendor::mediatek::hardware::mtkpower::V1_0::IMtkPower::getService(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, bool)+6) (BuildId: 3ae311b72bbeae94e3cf238decab2468)
      #08 pc 00002181  /system/lib/libpowerhalwrap.so (getMtkPowerHal()+132) (BuildId: d40a0ba2aed3d829d8ef57ac3dadfe10)
      #09 pc 00002425  /system/lib/libpowerhalwrap.so (PowerHal_Wrap_mtkCusPowerHint+28) (BuildId: d40a0ba2aed3d829d8ef57ac3dadfe10)
      #10 pc 000015c5  /system/bin/cyclonehubd (ctrl_data_handler(int, unsigned int)+240) (BuildId: 8afa71f55d9329ef20ec05ca49c76cea)
      #11 pc 0000127f  /system/bin/cyclonehubd (mainloop()+194) (BuildId: 8afa71f55d9329ef20ec05ca49c76cea)
      #12 pc 0000115d  /system/bin/cyclonehubd (main+280) (BuildId: 8afa71f55d9329ef20ec05ca49c76cea)
...

可以看到卡在了SystemProperties::Wait这里,我们通过查看代码可以发现,其只是一个等待器,而为何等待,结合代码与之前的log,不难看出是因为:

static void waitForHwServiceManager() {
    using std::literals::chrono_literals::operator""s;
	
	//kHwServicemanagerReadyProperty = "hwservicemanager.ready"
    while (!WaitForProperty(kHwServicemanagerReadyProperty, "true", 1s)) { 
        LOG(WARNING) << "Waited for hwservicemanager.ready for a second, waiting another...";
    }
}

这部分代码是在system/libhidl/transport/ServiceManagement.cpp中的,属于HIDL调用前的部分。

这部分涉及到bionic/libc/system_properties/system_properties.cpp 的逻辑,由于无论是MTK还是我们,都没有对这部分代码做过项目区分,因此深挖这里对此题没有帮助。

那么,问题到这里,最大的疑点就落在了SELinux规则本身上面。

Step 2

追踪函数WaitForProperty的实现,可以顺利跟到:

android::base::WaitForPropertyCreation
		|- android::base::__system_property_find
				|- SystemProperties::Find
		|- android::base::__system_property_wait
				|- SystemProperties::Wait

其中Wait仅仅做的是等待若干时间(此处为1s),然后确认获得到的值是否符合预期;与本题关系不大,就不展开了;
那么问题就处在SystemProperties::Find这个函数上了,结合之前的log,我们不难发现问题出在这里挖:

05-05 19:51:10.190   426   426 E libc    : Access denied finding property "hwservicemanager.ready"
const prop_info* SystemProperties::Find(const char* name) {
  ...
  prop_area* pa = contexts_->GetPropAreaForName(name);
  if (!pa) {
    async_safe_format_log(ANDROID_LOG_ERROR, "libc", "Access denied finding property \"%s\"", name);
    return nullptr;
  }
  ...
}

到这里,已经定位到GetPropAreaForName函数了;
其实现有两个类,根据init的参数传递情况,我们可以确定本机走的逻辑:

//filename始终为"/dev/__properties__"
bool SystemProperties::Init(const char* filename) {
  ...
  strcpy(property_filename_, filename);

  if (is_dir(property_filename_)) {//true
    if (access("/dev/__properties__/property_info", R_OK) == 0) {//true
      contexts_ = new (contexts_data_) ContextsSerialized();//因此context为ContextsSerialized
      if (!contexts_->Initialize(false, property_filename_, nullptr)) {
        return false;
      }
    } else {
      contexts_ = new (contexts_data_) ContextsSplit();
      if (!contexts_->Initialize(false, property_filename_, nullptr)) {
        return false;
      }
    }
  } else {
    contexts_ = new (contexts_data_) ContextsPreSplit();
    if (!contexts_->Initialize(false, property_filename_, nullptr)) {
      return false;
    }
  }
  initialized_ = true;
  return true;
}

因此GetPropAreaForName函数指向的是ContextsSerialized::GetPropAreaForName实现;

ContextsSerialized::GetPropAreaForName()
		|- ContextNode::Open()
				|- prop_area::map_prop_area()
						|- map_fd_ro()
								|- mmap()
										|
									(syscall)
										|- sys_mmap_pgoff()
												|- vm_mmap_pgoff()
														|- security_mmap_file()
																	|- call_int_hook() -> selinux_mmap_file()
																									

整个逻辑涉及比较多的函数指针跳转,感兴趣的可以自己再跟一下,看看有没有不同的地方。
此时,对比selinux_mmap_file()函数在不同kernel版本上的实现,立马可以发现,kernel-4.14新增了一段代码:

代码路径:kernel-4.14/security/selinux/hooks.c:

static int selinux_mmap_file(struct file *file, unsigned long reqprot,
			     unsigned long prot, unsigned long flags)
{
	struct common_audit_data ad;
	int rc;
	//以下部分为kernel-4.14较4.9新增部分
	if (file) {
		ad.type = LSM_AUDIT_DATA_FILE;
		ad.u.file = file;
		rc = inode_has_perm(current_cred(), file_inode(file),
				    FILE__MAP, &ad);
		if (rc)
			return rc;
	}
	//以上部分为kernel-4.14较4.9新增部分
	if (selinux_checkreqprot)
		prot = reqprot;

	return file_map_prot_check(file, prot,
				   (flags & MAP_TYPE) == MAP_SHARED);
}

追踪提交信息,可以发现这笔改动的CommitID为3ba4bf5f1e2c58bddd84ba27c5aeaf8ca1d36bff
如下为作者关于此提交的描述:

    selinux: add a map permission check for mmap
    
    Add a map permission check on mmap so that we can distinguish memory mapped
    access (since it has different implications for revocation). When a file
    is opened and then read or written via syscalls like read(2)/write(2),
    we revalidate access on each read/write operation via
    selinux_file_permission() and therefore can revoke access if the
    process context, the file context, or the policy changes in such a
    manner that access is no longer allowed. When a file is opened and then
    memory mapped via mmap(2) and then subsequently read or written directly
    in memory, we presently have no way to revalidate or revoke access.
    The purpose of a separate map permission check on mmap(2) is to permit
    policy to prohibit memory mapping of specific files for which we need
    to ensure that every access is revalidated, particularly useful for
    scenarios where we expect the file to be relabeled at runtime in order
    to reflect state changes (e.g. cross-domain solution, assured pipeline
    without data copying).
    
    Signed-off-by: Stephen Smalley <sds@tycho.nsa.gov>
    Signed-off-by: Paul Moore <paul@paul-moore.com>

简单翻译以下:由于文件打开并映射到内存以后,后续的读写均是直接操作内存内的数据,如果此时上下文、策略发生改变,已有的读写权限控制无法作用于已经映射到内存中的文件,因此在mmap的系统调用处添加map权限检查,以提升安全性;

总结

至此,真相大白,主要原因就是内核升级后添加了mmap的权限检查,导致之前的规则需要对应添加map权限。
此外,使用预定义的宏get_prop可以做到针对不同版本的兼容:

get_prop(cyclonehubd, hwservicemanager_prop)
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值