前言
本文主要針對(duì)一下幾個(gè)問(wèn)題伪货,通過(guò)查詢資料,和一些自己的思考著摔,梳理的一篇記錄
1爽待、什么是應(yīng)用沙盒? 為什么沙盒可以隔離保護(hù)app的數(shù)據(jù)
2腐魂、Android中 權(quán)限(網(wǎng)絡(luò)權(quán)限咧最、讀寫存儲(chǔ)權(quán)限) 控制的基礎(chǔ)是什么捂人?
3、看不見(jiàn)的權(quán)限管理(selinux)
一矢沿、Linux的用戶和用戶組
Linux有用戶和用戶組的概念先慷。在Linux系統(tǒng)中,每次登錄系統(tǒng)都必須以一個(gè)用戶的身份登錄咨察,并且登錄后的權(quán)限也會(huì)根據(jù)用戶身份來(lái)確定论熙。 每一個(gè)進(jìn)程在執(zhí)行時(shí),也會(huì)有其用戶摄狱,該用戶也和進(jìn)程所能控制的資源有關(guān)脓诡。Linux系統(tǒng)下的每一個(gè)目錄、文件媒役,都會(huì)有其屬于的用戶和用戶組祝谚,我們稱其為屬主和屬組。
在Linux系統(tǒng)中酣衷,每個(gè)用戶都有自己的用戶ID交惯,稱為UID,每個(gè)用戶組也有自己的用戶組ID穿仪,稱為GID席爽,UID和GID在Linux系統(tǒng)中是不可重復(fù)的。Linux系統(tǒng)就是通過(guò)UID和GID來(lái)對(duì)用戶和組進(jìn)行管理的啊片,而對(duì)于管理員來(lái)說(shuō)只锻,往往會(huì)設(shè)置用戶名和組名,這樣使得用戶和用戶組的使用管理更人性化紫谷。
在Linux系統(tǒng)中齐饮,一共有三種類型的用戶組捐寥。
- root用戶
root用戶時(shí)UID和GID都等于0的用戶,是Linux系統(tǒng)中的“上帝”祖驱,擁有最大的權(quán)限握恳。比如:無(wú)視Linux對(duì)權(quán)限的設(shè)置而強(qiáng)行讀、寫捺僻、執(zhí)行文件睡互,切換其他用戶登錄不需要密碼,可以強(qiáng)行切換到已經(jīng)所用的用戶陵像,只有root可以為普通用戶修改密碼等等。 - 系統(tǒng)用戶
系統(tǒng)用戶通常用于運(yùn)行服務(wù)寇壳,但是此用戶無(wú)home目錄醒颖,也不能用于登錄系統(tǒng)。例如壳炎,在yum安裝apache泞歉、nginx等服務(wù)后,就會(huì)自動(dòng)創(chuàng)建apache和nginx的用戶和同名用戶組匿辩。 - 普通用戶
普通用戶只能由root用戶創(chuàng)建腰耙,該用戶擁有home目錄,并且可以登錄铲球,該用戶的權(quán)限由root分配挺庞。普通用戶擁有指定的shell環(huán)境。
ls -ls 可以查看某個(gè)文件的讀寫執(zhí)行權(quán)限,共分為三組權(quán)限:owner的權(quán)限稼病、Group的權(quán)限选侨、和其他用戶的權(quán)限。如下圖所示:
二然走、Android 沙盒機(jī)制
android集成了Linux用戶和組的概念援制。Linux中通過(guò)用戶和用戶組將文件的權(quán)限進(jìn)行了隔離。Android中為每一個(gè)應(yīng)用賦予了一個(gè)用戶(owner)和用戶組(group),以實(shí)現(xiàn)應(yīng)用各自的文件彼此隔離的目錄芍瑞。這就是應(yīng)用的沙箱隔離機(jī)制晨仑。
android 應(yīng)用程序是沙箱隔離的,每個(gè)應(yīng)用都有一個(gè)只有自己具有讀寫權(quán)限的專用數(shù)據(jù)目錄拆檬,應(yīng)用只能訪問(wèn)自己的文件和一些設(shè)備上全局可訪問(wèn)的資源洪己。
apk安裝之后,會(huì)被賦予一個(gè)uid和gid。uid和gid 通常相同竟贯。
通過(guò)/data/system/packages.list 查看對(duì)應(yīng)的包名,可以確定apk分配的uid
adb shell cat /data/system/packages.list | grep zappstore
com.zuoyebang.iot.watch.zappstore 10045 0 /data/user/0/com.zuoyebang.iot.watch.zappstore platform:targetSdkVersion=27 3003,1007,3006
zappstore的uid為10045,10045-10000=45,uid的字符串形式為u0_a45
查看/data/data目錄的zappstore的沙盒文件的訪問(wèn)權(quán)限,可以發(fā)現(xiàn)
com.zuoyebang.iot.watch.zappstore 目錄所屬的用戶為u0_a45,用戶組為u0_a45, owner用戶u0_a45 對(duì)此目錄具有讀寫執(zhí)行權(quán)限,用戶組中其他用戶沒(méi)有任何權(quán)限,用戶組之外的其他用戶也沒(méi)有任何權(quán)限码泛。
sl8541e_1h10_go:/data/data # ls -ls | grep zappstore
3 drwx------ 4 u0_a45 u0_a45 3488 2023-03-27 00:00 com.zuoyebang.iot.watch.zappstore
因此com.zuoyebang.iot.watch.zappstore沙盒目錄,只有zappstore的進(jìn)程才有訪問(wèn)權(quán)限,其他的app 無(wú)權(quán)此沙盒目錄的文件。
除了沙箱目錄,應(yīng)用進(jìn)程相關(guān)虛擬文件,也被限制為僅自己的uid可訪問(wèn)
共享UID
兩個(gè)應(yīng)用的具有相同的簽名文件澄耍,并且指令了shareUid為同一個(gè)uid,則這兩個(gè)應(yīng)用可以方便的訪問(wèn)彼此的私有目錄噪珊。
比較常見(jiàn)的例子是:
Android中所有的系統(tǒng)應(yīng)用都指定了shareUid 為android.uid.system
android:sharedUserId="android.uid.system"
系統(tǒng)應(yīng)用apk 分配的uid都是1000,用戶名都是system
應(yīng)用沙盒的所屬的owner和group 都是system晌缘,因此系統(tǒng)應(yīng)用之間可以互相訪問(wèn)彼此的應(yīng)用沙盒。
這就是ShareUid機(jī)制
值得注意的是:Android 不支持將一個(gè)已安裝的應(yīng)用痢站,從非共享 UID 切換到共享狀態(tài)磷箕,因?yàn)楦淖兞艘寻惭b應(yīng)用的 UID,會(huì)導(dǎo)致應(yīng)用失去對(duì)自己文件的訪問(wèn)權(quán)限(在一些早期 Android 版本中)阵难,所以如果使用共享 UID 必須從一開(kāi)始就設(shè)計(jì)好岳枷。
三、權(quán)限的授予
幾個(gè)重要的配置文件:
- /data/system/packages.xml
- /data/system/users/0/runtime-permissions.xml呜叫。
- /etc/permission/platform.xml
- /system/core/include/private/android_filesystem_config.h
3.1空繁、權(quán)限管理
在每個(gè)應(yīng)用安裝時(shí),權(quán)限就已經(jīng)賦予了朱庆,系統(tǒng)使用包管理服務(wù)來(lái)管理權(quán)限盛泡。打開(kāi)我們系統(tǒng)目錄下的 /data/system/packages.xml,可以看到文件包含了所有已定義的權(quán)限列表和所有 apk 的包信息娱颊,這可以看做是包管理服務(wù)維護(hù)的一個(gè)已安裝程序的核心數(shù)據(jù)庫(kù)傲诵,這個(gè)數(shù)據(jù)庫(kù),隨著每次應(yīng)用安裝箱硕、升級(jí)或卸載而進(jìn)行更新拴竹。
- permissions標(biāo)簽內(nèi),定義了目前系統(tǒng)中的所有權(quán)限剧罩,分為系統(tǒng)內(nèi)置的(package 屬性為 android 的)和 apk 自定義的(package 屬性為 apk 的包名)
<permissions>
<item name="android.permission.REAL_GET_TASKS" package="android" protection="18" />
<item name="android.permission.ACCESS_CACHE_FILESYSTEM" package="android" protection="18" />
<item name="android.permission.REMOTE_AUDIO_PLAYBACK" package="android" protection="2" />
<item name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" package="com.android.providers.downloads" />
<item name="android.permission.REGISTER_WINDOW_MANAGER_LISTENERS" package="android" protection="2" />
<item name="android.permission.INTENT_FILTER_VERIFICATION_AGENT" package="android" protection="18" />
<item name="android.permission.BIND_INCALL_SERVICE" package="android" protection="18" />
<item name="android.permission.PROVIDE_RESOLVER_RANKER_SERVICE" package="android" protection="18" />
...
</permissions>
- package標(biāo)簽栓拜, 包含了每個(gè)apk 的核心屬性。
Android 6.0 及以上 packages.xml
<package name="com.zuoyebang.iot.watch.zappstore" codePath="/system/app/zappstore" nativeLibraryPath="/system/app/zappstore/lib" primaryCpuAbi="armeabi-v7a" publicFlags="675855941" privateFlags="0" ft="11e8f7d4c00" it="11e8f7d4c00" ut="11e8f7d4c00" version="1" userId="10045" isOrphaned="true">
<sigs count="1">
<cert index="0" />
</sigs>
<perms>
<item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
<item name="android.permission.RESTART_PACKAGES" granted="true" flags="0" />
<item name="android.permission.MODIFY_AUDIO_SETTINGS" granted="true" flags="0" />
<item name="android.permission.SYSTEM_ALERT_WINDOW" granted="true" flags="0" />
<item name="android.permission.INSTALL_PACKAGES" granted="true" flags="0" />
<item name="android.permission.CHANGE_NETWORK_STATE" granted="true" flags="0" />
<item name="android.permission.WRITE_SYNC_SETTINGS" granted="true" flags="0" />
<item name="android.permission.RECEIVE_BOOT_COMPLETED" granted="true" flags="0" />
<item name="android.permission.INTERNET" granted="true" flags="0" />
<item name="android.permission.STOP_APP_SWITCHES" granted="true" flags="0" />
<item name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" granted="true" flags="0" />
<item name="android.permission.WRITE_SECURE_SETTINGS" granted="true" flags="0" />
<item name="android.permission.CHANGE_WIFI_STATE" granted="true" flags="0" />
<item name="android.permission.DELETE_CACHE_FILES" granted="true" flags="0" />
<item name="android.permission.ACCESS_NETWORK_STATE" granted="true" flags="0" />
<item name="android.permission.READ_LOGS" granted="true" flags="0" />
<item name="android.permission.READ_NETWORK_USAGE_HISTORY" granted="true" flags="0" />
<item name="android.permission.VIBRATE" granted="true" flags="0" />
<item name="android.permission.ACCESS_WIFI_STATE" granted="true" flags="0" />
<item name="android.permission.MODIFY_PHONE_STATE" granted="true" flags="0" />
<item name="android.permission.WAKE_LOCK" granted="true" flags="0" />
<item name="android.permission.DELETE_PACKAGES" granted="true" flags="0" />
</perms>
<proper-signing-keyset identifier="1" />
</package>
Android 6.0以上normal權(quán)限 安裝之后自動(dòng)授予, 會(huì)枚舉在在perms列表中; 被標(biāo)記為dangrous的權(quán)限并不會(huì)列舉在 <perms>中,如:READ_SMS惠昔、READ_EXTERNAL_STORAGE菱属、CALL_PHONE 等。
Android 6.0 之前舰罚,權(quán)限都是在安裝時(shí)自動(dòng)賦予的纽门,不卸載應(yīng)用的情況下,不能更改或撤銷营罢。而 Android 6.0 版本對(duì) permission 的管理做了部分改動(dòng)赏陵,針對(duì) dangerous 級(jí)別,不再安裝的時(shí)候賦予權(quán)限饲漾,而是在運(yùn)行時(shí)動(dòng)態(tài)申請(qǐng)蝙搔。
/data/system/packages.xml 里保留的是安裝后不會(huì)再變更的權(quán)限(normal)
運(yùn)行時(shí)權(quán)限(dangrous)另外單獨(dú)地維護(hù)在
/data/system/users/0/runtime-permissions.xml中。
<pkg name="com.example.testpermission">
<item name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="0" />
<item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="0" />
</pkg>
3.2考传、權(quán)限授予
Android 應(yīng)用安裝時(shí)吃型,會(huì)被分配一個(gè)唯一的 UID,應(yīng)用啟動(dòng)時(shí)會(huì)設(shè)置新建進(jìn)程的 UID 和 GID 為應(yīng)用程序的 UID僚楞。如果應(yīng)用已經(jīng)被賦予了額外的權(quán)限勤晚,就把這些權(quán)限映射成一組 GID枉层,作為補(bǔ)充 GID 分配給進(jìn)程。底層就可以依賴于進(jìn)程的 UID赐写、GID 和補(bǔ)充 GID 來(lái)決定是否賦予權(quán)限了鸟蜡。
上面流程的重點(diǎn):
- 權(quán)限是如何映射到 OS 層的 UID、GID 上的呢挺邀?
- 映射完是怎么分配給進(jìn)程的揉忘?
- 低層是怎么判斷是否賦予權(quán)限的?
/etc/permission/platform.xml
3.2.1端铛、權(quán)限和補(bǔ)充Gid的映射
內(nèi)置權(quán)限到 GID 的映射是定義在/etc/permission/platform.xml中
<permissions>
···
<permission name="android.permission.READ_EXTERNAL_STORAGE" >
<group gid="sdcard_r" />
</permission>
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" >
<group gid="sdcard_r" />
<group gid="sdcard_rw" />
</permission>
<permission name="android.permission.INTERNET" >
<group gid="inet" />
</permission>
···
</permissions>
READ_EXTERNAL_STORAGE 這種運(yùn)行時(shí)權(quán)限泣矛,在 Android 6.0 之后已經(jīng)不會(huì)映射到 gid 了。動(dòng)態(tài)賦予禾蚕,動(dòng)態(tài)申請(qǐng)您朽,也就不需要映射了。
<permission name="android.permission.READ_EXTERNAL_STORAGE" />
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" />
3.2.2夕膀、查看進(jìn)程授予的補(bǔ)充GID
adb shell ps 查看進(jìn)程的PID
adb shell ps | grep zappstore
u0_a45 10732 245 1230560 131600 SyS_epoll_wait b0cce590 S com.zuoyebang.iot.watch.zappstore
adb shell cat /proc/10732/status 查看進(jìn)程的狀態(tài)
sl8541e_1h10_go:/proc/10732 # cat status
Name: watch.zappstore
State: S (sleeping)
Tgid: 10732
Ngid: 0
Pid: 10732
PPid: 245
TracerPid: 0
Uid: 10045 10045 10045 10045
Gid: 10045 10045 10045 10045
FDSize: 256
Groups: 1007 3003 3006 9997 20045 50045
可以看到Uid和Gid 為10045
Groups: 1007 3003 3006 9997 20045 50045
為此app所屬的輔助組,通過(guò)輔助組可以查看,該進(jìn)程被賦予了哪些權(quán)限
Android源碼android_filesystem_config.h 有輔助組的權(quán)限定義
\system\core\include\private\android_filesystem_config.h
#define AID_NET_BT_ADMIN 3001 /* bluetooth: create any socket */
#define AID_NET_BT 3002 /* bluetooth: create sco, rfcomm or l2cap sockets */
#define AID_INET 3003 /* can create AF_INET and AF_INET6 sockets */
3.2.3、進(jìn)程賦予Gid權(quán)限組
當(dāng)我們安裝應(yīng)用完成美侦,啟動(dòng)應(yīng)用产舞,應(yīng)用的進(jìn)程是如何啟動(dòng)并被賦予進(jìn)程屬性
每個(gè)應(yīng)用都會(huì)運(yùn)行在自己的 Dalvik 虛擬機(jī)進(jìn)程中,但是為了提高啟動(dòng)效率菠剩,Android 不會(huì)為每個(gè)應(yīng)用都新建一個(gè) Dalvik 進(jìn)程易猫,而是采用 fork 的形式。每個(gè)進(jìn)程都 fork form zygote 進(jìn)程具壮。
當(dāng) zygote 收到啟動(dòng)新進(jìn)程的請(qǐng)求時(shí)准颓,它會(huì) fork 自身出一個(gè)子進(jìn)程,并對(duì)該子進(jìn)程做特殊化處理棺妓。其源代碼位于 dalvik/vm/native/dalvik_system_Zygote.c 中攘已。forkAndSpecializeCommon() 的
static pid_t forkAndSpecializeCommon(const u4* args, boolisSystemServer)
{
...
pid = fork(); //創(chuàng)建新進(jìn)程
if (pid == 0) //判斷是否是root,有沒(méi)有權(quán)限修改自己的進(jìn)程屬性
{
setgroupsIntarray(gids); //設(shè)置進(jìn)程的所有組
setrlimitsFromArray(rlimits);
setgid(gid); //設(shè)置進(jìn)程的組ID
setuid(uid); //設(shè)置進(jìn)程的用戶ID
}
...
}
這里設(shè)置進(jìn)程的組 ID 和用戶 ID怜跑,通過(guò) fork 創(chuàng)建的子進(jìn)程調(diào)用 setgroups Intarray 設(shè)置該進(jìn)程所屬的組样勃,這樣應(yīng)用程序就擁有了該組的權(quán)限,并且可以通過(guò) setgid() 及 setuid() 確定應(yīng)用程序的 GID 及 UID 值性芬。
3.2.4峡眶、權(quán)限檢查(權(quán)限執(zhí)行)
1). 系統(tǒng)內(nèi)核層權(quán)限檢查
以 android.permission.INTERNET 網(wǎng)絡(luò)權(quán)限為例,Android系統(tǒng)是如何檢查一個(gè)應(yīng)用是否有網(wǎng)絡(luò)訪問(wèn)權(quán)限的
Android 的訪問(wèn)控制植锉,和 Linux 是一樣的辫樱,但 Android 增加了個(gè)特有的網(wǎng)絡(luò)訪問(wèn)安全控制機(jī)制,也就是說(shuō)俊庇,創(chuàng)建網(wǎng)絡(luò)套接字的進(jìn)程狮暑,必須屬于 inet
組鸡挠。
我們看下kernel中的相關(guān)代碼,kernel/apv4/af_inet.c
#ifdef CONFIG_ANDROID_PARANOID_NETWORK
#include <linux/android_aid.h>
static inline int current_has_network(void)
{
return in_egroup_p(AID_INET) || capable(CAP_NET_RAW);
}
#else
static inline int current_has_network(void)
{
return 1;
}
#endif
/*
* Create an inet socket.
*/
static int inet_create(struct net *net, struct socket *sock, int protocol,
int kern)
{
...
//判斷當(dāng)前進(jìn)程是否具有網(wǎng)絡(luò)權(quán)限;沒(méi)有權(quán)限則直接返回
if (!current_has_network())
return -EACCES;
...
}
如上內(nèi)核代碼心例,current_has_network(void)
方法檢查了進(jìn)程的所在組宵凌。如果不在 inet
組,則直接返回錯(cuò)誤止后。所以為了使我們的應(yīng)用具有訪問(wèn)網(wǎng)絡(luò)的能力瞎惫,我們需要在 AndroidManifest.xml
中申請(qǐng) INTERNET
權(quán)限,經(jīng)過(guò)解析译株,逐步映射到內(nèi)核層的組 ID 和用戶 ID瓜喇,最終才能通過(guò)內(nèi)核層的檢查。
2)非內(nèi)核層的其他 C/C++ 層歉糜,如何拿到進(jìn)程的所在組信息
在 PMS 初始化所有包信息之后,就會(huì)調(diào)用 mSettings.writePackageListLPr()將mPackages 中保存的所有包的信息保存到 /data/system/packages.list
乘寒,
frameworks/base/services/core/java/com/android/server/pm/Settings
void writePackageListLPr(int creatingUserId) {
// Only derive GIDs for active users (not dying)
final List<UserInfo> users = UserManagerService.getInstance().getUsers(true);
int[] userIds = new int[users.size()];
for (int i = 0; i < userIds.length; i++) {
userIds[i] = users.get(i).id;
}
if (creatingUserId != -1) {
userIds = ArrayUtils.appendInt(userIds, creatingUserId);
}
// Write package list file now, use a JournaledFile.
File tempFile = new File(mPackageListFilename.getAbsolutePath() + ".tmp");
JournaledFile journal = new JournaledFile(mPackageListFilename, tempFile);
final File writeTarget = journal.chooseForWrite();
FileOutputStream fstr;
BufferedWriter writer = null;
try {
fstr = new FileOutputStream(writeTarget);
writer = new BufferedWriter(new OutputStreamWriter(fstr, Charset.defaultCharset()));
FileUtils.setPermissions(fstr.getFD(), 0640, SYSTEM_UID, PACKAGE_INFO_GID);
StringBuilder sb = new StringBuilder();
for (final PackageSetting pkg : mPackages.values()) {
if (pkg.pkg == null || pkg.pkg.applicationInfo == null
|| pkg.pkg.applicationInfo.dataDir == null) {
if (!"android".equals(pkg.name)) {
Slog.w(TAG, "Skipping " + pkg + " due to missing metadata");
}
continue;
}
final ApplicationInfo ai = pkg.pkg.applicationInfo;
final String dataPath = ai.dataDir;
final boolean isDebug = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
final int[] gids = pkg.getPermissionsState().computeGids(userIds);
// Avoid any application that has a space in its path.
if (dataPath.indexOf(' ') >= 0)
continue;
// we store on each line the following information for now:
//
// pkgName - package name
// userId - application-specific user id
// debugFlag - 0 or 1 if the package is debuggable.
// dataPath - path to package's data path
// seinfo - seinfo label for the app (assigned at install time)
// gids - supplementary gids this app launches with
//
// NOTE: We prefer not to expose all ApplicationInfo flags for now.
//
// DO NOT MODIFY THIS FORMAT UNLESS YOU CAN ALSO MODIFY ITS USERS
// FROM NATIVE CODE. AT THE MOMENT, LOOK AT THE FOLLOWING SOURCES:
// frameworks/base/libs/packagelistparser
// system/core/run-as/run-as.c
//
sb.setLength(0);
sb.append(ai.packageName);
sb.append(" ");
sb.append(ai.uid);
sb.append(isDebug ? " 1 " : " 0 ");
sb.append(dataPath);
sb.append(" ");
sb.append(ai.seInfo);
sb.append(" ");
if (gids != null && gids.length > 0) {
sb.append(gids[0]);
for (int i = 1; i < gids.length; i++) {
sb.append(",");
sb.append(gids[i]);
}
} else {
sb.append("none");
}
sb.append("\n");
writer.append(sb);
}
writer.flush();
FileUtils.sync(fstr);
writer.close();
journal.commit();
} catch (Exception e) {
Slog.wtf(TAG, "Failed to write packages.list", e);
IoUtils.closeQuietly(writer);
journal.rollback();
}
}
packages.list中保存了所有應(yīng)用申請(qǐng)的權(quán)限,C代碼只要讀這個(gè)文件就能判斷某個(gè)應(yīng)用是否申請(qǐng)了我們要求的權(quán)限匪补。
/data/system/packages.list
com.example.testpermission 10056 1 /data/user/0/com.example.testpermission default:targetSdkVersion=32 3003
com.android.bluetoothmidiservice 10022 0 /data/user/0/com.android.bluetoothmidiservice platform:targetSdkVersion=27 3002
plugin.sprd.vodafonefeatures 10024 0 /data/user/0/plugin.sprd.vodafonefeatures platform:targetSdkVersion=27 none
3) 框架層 PackageManagerService 系統(tǒng)包管理器會(huì)負(fù)責(zé)記錄組件的權(quán)限.
使用 Binder.getCallingUid()
和 Binder.getCallingPid()
獲取調(diào)用者的 UID 和 PID伞辛,通過(guò) UID 在包管理器中查詢到對(duì)應(yīng)應(yīng)用的權(quán)限。
android.content.Context 類中有 checkPermission(String permission, int pid, int uid)
方法 實(shí)質(zhì)上會(huì)調(diào)用到 PMS 中的 checkUidPermission(String perName, int uid)
-
Android 6.0 以下 PMS 中的
checkUidPermission(String perName, int uid)
public int checkUidPermission(String permName, int uid) { synchronized (mPackages) { Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid)); if (obj != null) { GrantedPermissions gp = (GrantedPermissions)obj; if (gp.grantedPermissions.contains(permName)) { return PackageManager.PERMISSION_GRANTED; } } else { HashSet<String> perms = mSystemPermissions.get(uid); if (perms != null && perms.contains(permName)) { return PackageManager.PERMISSION_GRANTED; } } } return PackageManager.PERMISSION_DENIED; }
Android 6.0 以下的 checkUidPermission()
方法比較簡(jiǎn)單夯缺,首先蚤氏,基于入?yún)?uid 獲取應(yīng)用的 appId,拿到權(quán)限列表對(duì)象(也就是 packages.xml
里的 <package>
映射)踊兜,如果 GrantedPermissions
類中的 grantedPermissions
集合包含目標(biāo)權(quán)限竿滨,則檢查通過(guò)。
- Android 6.0 及以上 PMS 中的
checkUidPermission(String perName, int uid)
@Override
public int checkUidPermission(String permName, int uid) {
final int callingUid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(callingUid);
final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
final boolean isUidInstantApp = getInstantAppPackageName(uid) != null;
final int userId = UserHandle.getUserId(uid);
if (!sUserManager.exists(userId)) {
return PackageManager.PERMISSION_DENIED;
}
synchronized (mPackages) {
Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
if (obj != null) {
...
final SettingBase settingBase = (SettingBase) obj;
final PermissionsState permissionsState = settingBase.getPermissionsState();
if (permissionsState.hasPermission(permName, userId)) {
if (isUidInstantApp) {
BasePermission bp = mSettings.mPermissions.get(permName);
if (bp != null && bp.isInstant()) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
return PackageManager.PERMISSION_GRANTED;
}
}
...
} else {
ArraySet<String> perms = mSystemPermissions.get(uid);
if (perms != null) {
if (perms.contains(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
...
}
}
}
return PackageManager.PERMISSION_DENIED;
}
6.0 之后 checkPermission()
方法有所改變捏境。多了從 mSettings.mPermissions
去查詢權(quán)限列表于游。
關(guān)鍵就在于這個(gè) mSettings 里面保存的這個(gè) SettingBase 對(duì)象,它記錄了 PermissionsState 也就是權(quán)限的授予情況垫言。
// PermissionsState.java
public boolean hasPermission(String name, int userId) {
enforceValidUserId(userId);
if (mPermissions == null) {
return false;
}
PermissionData permissionData = mPermissions.get(name);
return permissionData != null && permissionData.isGranted(userId);
}
3.3贰剥、小結(jié)
-
/data/system/packages.xml
安裝即授予的權(quán)限(normal權(quán)限) 會(huì)記錄在packages.xml 對(duì)應(yīng)package包名下的perms節(jié)點(diǎn) 。管理普通權(quán)限筷频,且默認(rèn)granted.
<package name="com.example.testpermission" codePath="/data/app/com.example.testpermission-JVGWMzpTAQE0xVKRmwYOuw==" nativeLibraryPath="/data/app/com.example.testpermission-JVGWMzpTAQE0xVKRmwYOuw==/lib" publicFlags="944291654" privateFlags="0" ft="1879dad5090" it="1879d8dde0b" ut="1879dad52af" version="1" userId="10056">
<sigs count="1">
<cert index="8" />
</sigs>
<perms>
<item name="android.permission.INTERNET" granted="true" flags="0" />
</perms>
<proper-signing-keyset identifier="12" />
</package>
- /data/system/users/0/runtime-permissions.xml鸠澈。
管理運(yùn)行時(shí)權(quán)限,未requestPermissions()不會(huì)有對(duì)應(yīng)app的pkg標(biāo)簽截驮。
<pkg name="com.example.testpermission">
<item name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="0" />
<item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="0" />
</pkg>
- /etc/permission/platform.xml
記錄了權(quán)限和Gid的映射關(guān)系(normal 權(quán)限有效)笑陈,僅普通權(quán)限會(huì)記錄在此處
<permissions>
···
<permission name="android.permission.INTERNET" >
<group gid="inet" />
</permission>
···
</permissions>
-
/system/core/include/private/android_filesystem_config.h
android_filesystem_config.h 定義了AID的具體內(nèi)容 和權(quán)限組gid相對(duì)應(yīng)。
#define AID_NET_BT_ADMIN 3001 /* bluetooth: create any socket */ #define AID_NET_BT 3002 /* bluetooth: create sco, rfcomm or l2cap sockets */ #define AID_INET 3003 /* can create AF_INET and AF_INET6 sockets */
/data/system/packages.list packages.list中保存了所有應(yīng)用申請(qǐng)的權(quán)限
packages.list保存了應(yīng)用安裝后,分配的uid葵袭、gid 和補(bǔ)充組gid
com.android.emergency 10010 0 /data/user_de/0/com.android.emergency platform:privapp:targetSdkVersion=27 none
com.android.location.fused 1000 0 /data/user_de/0/com.android.location.fused platform:privapp:targetSdkVersion=27 2001,1013,3002,1023,1015,3003,3001,1007,3006
com.example.testpermission 10056 1 /data/user/0/com.example.testpermission default:targetSdkVersion=32 3003
四涵妥、SELinux
4.1、基本概念
SELinux 全稱 Security Enhanced Linux (安全強(qiáng)化 Linux)坡锡,是MAC (Mandatory Access Control蓬网,強(qiáng)制訪問(wèn)控制系統(tǒng))的一個(gè)實(shí)現(xiàn)窒所。其目的在于明確的指明某個(gè)進(jìn)程可以訪問(wèn)哪些資源(文件、網(wǎng)絡(luò)端口等)帆锋。Android系統(tǒng)基于Linux實(shí)現(xiàn)吵取。針對(duì)傳統(tǒng)Linux系統(tǒng),NSA開(kāi)發(fā)了一套安全機(jī)制SELinux锯厢,用來(lái)加強(qiáng)安全性皮官。然而,由于Android系 統(tǒng)有著獨(dú)特的用戶空間運(yùn)行時(shí)实辑,因此SELinux不能完全適用于Android系統(tǒng)捺氢。為此,NSA同Google一起針對(duì)Android系統(tǒng)剪撬,在SELinux基礎(chǔ)上開(kāi)發(fā)了 SEAndroid摄乒。
1) DAC和MAC的區(qū)別:
DAC核心思想:進(jìn)程理論上所擁有的權(quán)限與執(zhí)行它的用戶的權(quán)限相同。比如残黑,以root用戶啟動(dòng)Browser馍佑,那么Browser就有root用戶的權(quán)限,在Linux系統(tǒng)上能干任何事情梨水。
MAC核心思想:即任何進(jìn)程想在SELinux系統(tǒng)中干任何事情拭荤,都必須先在安全策略配置文件中賦予權(quán)限。凡是沒(méi)有出現(xiàn)在安全策略配置文件中的權(quán)限冰木,進(jìn)程就沒(méi)有該權(quán)限穷劈。
2) DAC和MAC的聯(lián)系:
SELinux 通過(guò)MAC的方式來(lái)控管程序,控制的主題是進(jìn)程笼恰,客體是該程序能否讀寫的資源(文件)
主體(subject):進(jìn)程
目標(biāo)(Object):被訪問(wèn)的資源(可以是文件踊沸、目錄、端口社证、設(shè)備等)
政策(policy):訪問(wèn)限制條件
安全上下文(security context)
主體能不能存取目標(biāo)除了政策之外逼龟,主體與目標(biāo)的安全性文本必須一致才能夠順利獲取
3)DAC、MAC 驗(yàn)證順序:
Android系統(tǒng) 會(huì)首先驗(yàn)證DAC權(quán)限 驗(yàn)證通過(guò)后,會(huì)驗(yàn)證MAC是否授予權(quán)限
4.2追葡、Security Context 安全上下文
SEAndroid是一種基于安全策略的MAC 安全機(jī)制腺律。這種安全策略又是建立在對(duì)象的安全上下文的基礎(chǔ)上的。這里所說(shuō)的對(duì)象分為兩種類型宜肉,一種稱 主體(Subject)匀钧,一種稱為客體(Object)。主體通常就是指進(jìn)程谬返,而客體就是指進(jìn)程所要訪問(wèn)的資源之斯,例如文件、系統(tǒng)屬性等遣铝。
4.2.1佑刷、什么安全上下文
安全上下文實(shí)際上就是一個(gè)附加在對(duì)象上的標(biāo)簽(label)莉擒。
進(jìn)程A 對(duì)文件B進(jìn)行讀操作,系統(tǒng)會(huì)檢查A的安全上下文標(biāo)簽,對(duì)B的安全上下文標(biāo)簽 是否具有讀權(quán)限瘫絮。
這個(gè)標(biāo)簽實(shí)際上就是一個(gè)字符串涨冀,它由四部分內(nèi)容組成,分別是SELinux用戶麦萤、SELinux 角色鹿鳖、類型、安全級(jí)別频鉴,每一個(gè)部分都通過(guò)一個(gè)冒號(hào)來(lái)分隔栓辜,格式為“user:role:type:rank”。
SELinux中 每種東西都被賦予了一個(gè)安全屬性,稱為Security Context (安全上下文),它是一個(gè)字符串垛孔。 Scontext分為:進(jìn)程(主體)的scontext 和 文件(客體)的tcontext藕甩。兩者匹配時(shí),才會(huì)允許進(jìn)程訪問(wèn)文件,若不匹配時(shí)則不允許訪問(wèn)周荐。
4.2.2狭莱、查看進(jìn)程的安全上下文:
進(jìn)程的 Security Context 可通過(guò) ps -(A)Z 命令查看
adb shell ps -Z
u:r:hal_wifi_supplicant_default:s0 wifi 1033 1 11488 7592 0 0 S wpa_supplicant
u:r:radio:s0 radio 1044 245 1174504 114696 0 0 S com.android.phone
u:r:system_app:s0 system 1189 245 1179352 103264 0 0 S
4.2.3、查看文件的安全上下文:
文件的 Secure Context 可以通過(guò) ls (file) -Z 來(lái)查看:
# adb shell ls /data/data/ -Z
u:object_r:system_app_data_file:s0 com.android.inputdevices u:object_r:system_app_data_file:s0 com.zuoyebang.iot.watch.zalarm
u:object_r:system_app_data_file:s0
u :user概作,SEAndroid 中定義了一個(gè)用戶
r : role腋妙,一個(gè) u 可以屬于多個(gè) role,不同的 role 具有不同的權(quán)限讯榕。在SEAndroid中的role有兩個(gè)骤素,分別為 r 和 object_r
object_r:role,文件是死的東西愚屁,在 SELinux 中济竹,死的東西都用 object_r 來(lái)表示它的 role
platform_app :type,表示該進(jìn)程所屬的 type 為 platform_app
adb_data_file :type霎槐,表示 adb 文件夾所屬的 type 為 adb_data_file
s0:c512 : sensitivity送浊,category ,是 SELinux 為了滿足軍用和教育行業(yè)而設(shè)計(jì)的 Multi-Level Security(MLS)機(jī)制丘跌。MLS 將系統(tǒng)的進(jìn)程和文件進(jìn)行了分級(jí)袭景,不同級(jí)別的資源需要對(duì)應(yīng)級(jí)別的進(jìn)程才能訪問(wèn)
在安全上下文中,只有類型(Type)才是最重要的闭树,SELinux用戶耸棒、SELinux角色和安全級(jí)別都幾乎可以忽略不計(jì)的。正因?yàn)槿绱吮ㄈ瑁琒EAndroid安全機(jī)制又稱為是基于TE(Type Enforcement)策略的安全機(jī)制与殃。
4.3、安全策略 TE
安全策略是在安全上下文的基礎(chǔ)上進(jìn)行描述的,它通過(guò)主體和客體的安全上下文,定義主體是否有權(quán)限訪問(wèn)客體。SEAndroid安全機(jī)制主要是使用安全上下文中的類型來(lái)定義安全策略奈籽,這種安全策略稱為Type EnforceMent,簡(jiǎn)稱TE饥侵。所有以.te為后綴的文件經(jīng)過(guò)編譯后都會(huì)生成一個(gè)sepolicy文件。這個(gè)policy文件會(huì)打包在ROM中,并且保存在設(shè)備上衣屏。
根據(jù)Selinux規(guī)范躏升,完整的allow相關(guān)語(yǔ)句格式為:
rule_name:source_type:target_type:class perm_set
source_type和target_type 都是查看進(jìn)程和文件的安全上下文得來(lái)的。
備注:
SEAndroid 將app分了幾類:
- platform_app.te (具有android platfrom簽名,但是沒(méi)有system權(quán)限)
- system_app.te(具有android platfrom簽名 與system權(quán)限)
- untrusted_app.te (第三方app,不具有android platform簽名和系統(tǒng)權(quán)限)
allow 語(yǔ)句的例子:
- 允許zygote domain 中的進(jìn)程向init type的進(jìn)程 (Object class 為process) 發(fā)送sigchld信號(hào)
allow zygote init\:procss sigchld;
- 允許zygote域中的進(jìn)程search 或getattr 類型為appdomain的目錄狼忱。注意 多個(gè)perm\_set用{}括起來(lái)
allow zygote appdomain\:dir{getattr search};
4.4膨疏、SELinux 相關(guān)設(shè)置
強(qiáng)制執(zhí)行等級(jí)
熟悉以下術(shù)語(yǔ),了解如何按不同的強(qiáng)制執(zhí)行級(jí)別實(shí)現(xiàn) SELinux
- 寬容模式(permissive) - 僅記錄但不強(qiáng)制執(zhí)行 SELinux 安全政策钻弄。
- 強(qiáng)制模式(enforcing) - 強(qiáng)制執(zhí)行并記錄安全政策佃却。如果失敗,則顯示為 EPERM 錯(cuò)誤窘俺。
4.4.1饲帅、 關(guān)閉 SELinux
臨時(shí)關(guān)閉
(1) setenforce
setenforce [ Enforcing | Permissive | 1 | 0 ]
setenforce 命令修改的是 /sys/fs/selinux/enforce 節(jié)點(diǎn)的值,是 kernel 意義上的修改 selinux 的策略瘤泪。斷電之后灶泵,節(jié)點(diǎn)值會(huì)復(fù)位
(2) getenforce
返回結(jié)果有兩種:Enforcing和Permissive. Permissive 代表SELinux關(guān)閉,不會(huì)阻止進(jìn)程違反SELinux策略訪問(wèn)資源的行為对途。Enforcing 代表SELinux處于開(kāi)啟狀態(tài)赦邻,會(huì)阻止進(jìn)程違反SELinux策略訪問(wèn)資源的行為。
永久關(guān)閉
(1) kernel 關(guān)閉 selinux
SECURITY_SELINUX 設(shè)置為 false实檀,重新編譯 kernel1
4.5惶洲、自定義te文件
場(chǎng)景: 為 app_permission.sh 腳本賦予 相應(yīng)的權(quán)限,使其可以開(kāi)機(jī)自動(dòng)運(yùn)行
定義apppermission.te 定義apppermission進(jìn)程type和app_
4.5.1、定義app_permission和app_permission_exec
- 定義app_permission 和app_permission_exec 的Type類型
android8go_watch_os/device/sprd/sharkle/common/platsepolicy/public/app_permission.te
##定義app_perssion 進(jìn)程域
type app_permission, domain, mlstrustedsubject;
##定義app_permission_exec 可執(zhí)行文件類型
type app_permission_exec, exec_type,file_type;
- 定義app_permission 屬性膳犹、init_daemon_domain切換進(jìn)程域
android8go_watch_os/device/sprd/sharkle/common/plat_sepolicy/private/app_permission.te
typeattribute app_permission coredomain;
##切換app_permission 進(jìn)程域
init_daemon_domain(app_permission)
-
賦予app_permission進(jìn)程域類型 相應(yīng)的selinux權(quán)限
android8go_watch_os/device/sprd/sharkle/common/sepolicy/app_permission.te
#fanlongjun add for aplog! #binder_use(app_permission) allow app_permission shell_exec:file {read open execute getattr}; allow app_permission toolbox_exec:file {read open execute getattr execute_no_trans}; allow app_permission logcat_exec:file { getattr execute read open execute_no_trans }; #call "logcat" #allow app_permission vendor_toolbox_exec:file {read getattr execute}; allow app_permission storage_file:dir {search getattr}; allow app_permission app_permission:capability {chown sys_admin dac_override net_raw sys_nice setuid setgid sys_nice fsetid}; allow app_permission sdcardfs:dir {create write search add_name read remove_name open rmdir rename reparent setattr}; allow app_permission sdcardfs:file {read append create open getattr write unlink rename setattr}; allow app_permission media_rw_data_file:dir {create write search add_name read remove_name open rmdir rename reparent setattr getattr}; allow app_permission media_rw_data_file:file {read append create open getattr write unlink rename setattr}; allow app_permission logdr_socket:sock_file {write}; allow app_permission logd:unix_stream_socket {connectto}; allow app_permission system_file:file {read open execute getattr execute_no_trans}; allow app_permission storage_file:lnk_file {read getattr}; allow app_permission mnt_user_file:dir { search read open write add_name remove_name }; allow app_permission mnt_user_file:lnk_file { read open write }; allow app_permission system_app_data_file:dir {getattr read}; allow app_permission system_data_file:file {getattr read}; allow app_permission app_data_file:dir { read setattr getattr}; allow app_permission self:capability fowner;
4.5.2恬吕、為app_permission.sh 文件實(shí)體 綁定app_permission_exec 安全上下文標(biāo)簽
android8go_watch_os/device/sprd/sharkle/common/plat_sepolicy/private/file_contexts
#為app_permission.sh 綁定安全上下文
/system/bin/app_permission.sh u:object_r:app_permission_exec:s0
五、參考文章
http://www.reibang.com/p/5284fd388394
https://blog.csdn.net/weixin_40366279/article/details/121281882
SELinux用audio2allow生成添加權(quán)限的格式及neverallow解決方法