[踩坑][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/[email protected] (_ZN7android8hardware7details18getServiceInternalIN6vendor8mediatek8hardware8mtkpower4V1_012BpHwMtkPowerENS7_9IMtkPowerEvvEENS_2spIT0_EERKNSt3__112basic_stringIcNSD_11char_traitsIcEENSD_9allocatorIcEEEEbb+130) (BuildId: 3ae311b72bbeae94e3cf238decab2468)
      #07 pc 00015037  /system/lib/[email protected] (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 <[email protected]>
    Signed-off-by: Paul Moore <[email protected]>

簡單翻譯以下:由於文件打開並映射到內存以後,後續的讀寫均是直接操作內存內的數據,如果此時上下文、策略發生改變,已有的讀寫權限控制無法作用於已經映射到內存中的文件,因此在mmap的系統調用處添加map權限檢查,以提升安全性;

總結

至此,真相大白,主要原因就是內核升級後添加了mmap的權限檢查,導致之前的規則需要對應添加map權限。
此外,使用預定義的宏get_prop可以做到針對不同版本的兼容:

get_prop(cyclonehubd, hwservicemanager_prop)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章