Android 使用者組許可權,SELinux心得總結
這裡要分兩個部分來說,一個是Linux許可權組的設定,一個是SELinux。
我們先不考慮SELinux。先單獨來說Linux許可權組。
因為要想對某個檔案進行操作(read,write,execute等),必須先滿足Linux許可權組的規則,然後再滿足SeLinux的allow條件,才能成功操作。
我們最好找一臺userdebug軟體的手機來進行除錯學習。我這邊是在Android O上進行示範。
Linux許可權組:
我們知道每開一個程序,都對應一個虛擬機器例項。它有一個對應uid。
adb shell
EDA51-1:/ # ps -A |grep com
system 26267 899 4378628 82036 SyS_epoll_wait 6ffcd7b420 S com.android.settings
u0_a30 1669 899 4418756 118956 SyS_epoll_wait 6ffcd7b420 S com.android.systemui
system 26333 899 4338916 63952 SyS_epoll_wait 6ffcd7b420 S com.potter.honeysigapk
nfc 2456 899 4345896 56824 SyS_epoll_wait 6ffcd7b420 S com.android.nfc
radio 3856 899 4317736 37988 SyS_epoll_wait 6ffcd7b420 S com.qualcomm.simcontacts
這裡的system,u0_a30,nfc等等就是uid。
首先,這些uid是怎麼來的,在哪定義的?
system/core/include/private/android_filesystem_config.h
#define AID_ROOT 0 /* traditional unix root user */
#define AID_SYSTEM 1000 /* system server */
#define AID_RADIO 1001 /* telephony subsystem, RIL */
#define AID_BLUETOOTH 1002 /* bluetooth subsystem */
…
值的關注的有:
#define AID_ROOT 0
#define AID_SYSTEM 1000
#define AID_APP 10000 /* TODO: switch users over to AID_APP_START */
#define AID_APP_START 10000 /* first app user */
#define AID_APP_END 19999 /* last app user */
#define AID_USER 100000
這裡只有大寫的,你可以會問,應該是一一對應的,比如init,rc裡面也有用到root,system等,要對應小寫的才對呀?
上面那個.h檔案底部有以下注釋。在Android O上已經是自動生成的了。在早些版本是宣告的。
/*
* android_ids has moved to pwd/grp functionality.
* If you need to add one, the structure is now
* auto-generated based on the AID_ constraints
* documented at the top of this header file.
* Also see build/tools/fs_config for more details.
*/
早些版本宣告類似:
static const struct android_id_info android_ids[] = {
{ "root", AID_ROOT, },
{ "system", AID_SYSTEM, },
{ "radio", AID_RADIO, },
{ "bluetooth", AID_BLUETOOTH, },
….
如果你和我一樣好奇,可能還會問
u0_a30 1669 899 4418756 118956 SyS_epoll_wait 6ffcd7b420 S com.android.systemui
這個u0_a30,還有其他的u0_axx都是怎麼來的,不可能全部定義吧,這個規則在哪?
上一個問題裡,我們知道其實android_ids是自動生成的。詳細步驟參考:
app的uid/100000的結果為userid,填到ux的x處。
app的uid減去10000為appid,填到axx的xx處。
例如某個app的uid是10022,經過計算,userid為10022/100000=0,appid為10022-10000=22,則那麼最終通過ps列印得到uid字串就是u0_a22
可能你還會問,Settings和SystemUI都是系統apk,為什麼一個是system,一個是u0_axx?
這個其實很簡單,Settings的AndroidManifest.xml裡面聲明瞭android:sharedUserId="android.uid.system"
我們知道sharedUserID一致,同時簽名一致的兩個apk可以共享資料。這裡多說一句,其實
SystemUI和Keyguard在android高版本里面也是同一個uid。
frameworks/base/packages/Keyguard/AndroidManifest.xml:21: android:sharedUserId="android.uid.systemui"
frameworks/base/packages/SystemUI/AndroidManifest.xml:22: android:sharedUserId="android.uid.systemui"
這裡我們對uid的定義,來由有了一些瞭解。下面繼續看為什麼需要uid,它有什麼用。
下面的ipsm分割槽是我司自己定製的,大家可以忽略。
EDA51-1:/ # ls -l
drwxrwx--x 47 system system 4096 1970-01-07 12:06 data
drwxr-xr-x 14 root root 3800 2018-11-08 15:11 dev
drwxrwx--x 4 system system 4096 1970-01-07 12:05 ipsm
lrw-r--r-- 1 root root 21 2018-11-08 17:12 sdcard -> /storage/self/primary
drwxr-xr-x 5 root root 100 2018-11-08 15:06 storage
dr-xr-xr-x 13 root root 0 1970-01-07 12:12 sys
drwxr-xr-x 16 root root 4096 2018-11-08 17:12 system
drwxr-xr-x 17 root root 4096 1970-01-01 08:00 vendor
‘
第一列 共有10位,從左到右邊意思如下
1 位 表示檔案的型別
2~4位 表示檔案所有者的許可權就是系統對該檔案所擁有的許可權
5~7位 表示檔案所在群組的許可權
8~10 表示檔案的其它使用者許可權
第二列 純數字 表示檔案連結個數
第三列 表示檔案所有者
第四列 表示檔案所在的群組
第五列 表示檔案的大小
第六列 表示檔案最後更新的時間
第七列 表示檔案的名稱
b就是block
d就是directory
l就是link
舉個例子,我們知道某個apk的SharedPreference資料是放在/data/data/com.xxx.xxx/files/裡的
EDA51-1:/data/data # ls -l
drwx------ 4 system system 4096 1970-01-07 12:06 android
drwxr-x--x 4 u0_a78 u0_a78 4096 2018-11-08 15:06 com.action.ext.servicie
drwx------ 4 u0_a29 u0_a29 4096 2018-11-08 15:06 com.android.apps.tag
drwxr-x--x 4 system system 4096 2018-11-08 15:06 com.android.backup
drwx------ 4 u0_a49 u0_a49 4096 2018-11-08 15:06 com.android.egg
drwx------ 7 u0_a50 u0_a50 4096 2018-11-08 15:07 com.android.email
drwx------ 4 u0_a13 u0_a13 4096 2018-11-08 15:06 com.android.emergency
可以看到/data/data/com.xxx.xxx的目錄,對應的apk是具有讀寫許可權的,而其他組則沒有。
我們以檔案管理器再來舉例子:
EDA51-1:/data/data # ps -A|grep file
u0_a38 26659 899 4364576 100916 SyS_epoll_wait 6ffcd7b420 S com.cyanogenmod.filemanager
從我們的經驗來看,檔案管理是可以操作外部儲存的(我們插入的sdcard以及storage/emulated/0/)
我們先去storage/emlulated/0目錄下去ls -l看一下
EDA51-1:/storage/emulated # ls -l
drwxrwx--x 16 root sdcard_rw 4096 2018-11-09 07:47 0
EDA51-1:/storage/emulated/0 # ls -l
drwxrwx--x 2 root sdcard_rw 4096 2018-11-08 15:07 Alarms
drwxrwx--x 3 root sdcard_rw 4096 2018-11-08 15:07 Android
drwxrwx--x 2 root sdcard_rw 4096 2018-11-08 15:07 DCIM
這裡可以看到,root和sdcard_rw這兩個id都可以寫storage/emulated/0這個目錄的,可是我們的檔案管理器既不是root也不是sdcard_rw啊,而是u0_a38 ,怎麼做到的?
這個問題的解答涉及到一個檔案:frameworks/base/data/etc/platform.xml
它定義了宣告哪些許可權可以得到哪些對應組的操作許可權。
<permission name="android.permission.WRITE_MEDIA_STORAGE" >
<group gid="media_rw" />
<group gid="sdcard_rw" />
</permission>
也就是說我的FileManager只需要在AndroidManifest.xml裡面聲明瞭android.permission.WRITE_MEDIA_STORAGE就ok了。
這裡又衍生出一個問題?
為什麼我們經常是用的是
<permission name="android.permission.READ_EXTERNAL_STORAGE" />
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" />而不是android.permission.WRITE_MEDIA_STORAGE?
是否他們都在一個許可權組呢?我們知道動態許可權給了其中一個,整個組的許可權都會給到
frameworks\base\core\res\AndroidManifest.xml
裡面有
<!-- Allows an application to write to external storage.
{@link android.content.Context#getExternalFilesDir} and
{@link android.content.Context#getExternalCacheDir}.
<permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:permissionGroup="android.permission-group.STORAGE"
<permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:permissionGroup="android.permission-group.STORAGE"
<!-- @SystemApi Allows an application to write to internal media storage
@hide -->
<permission android:name="android.permission.WRITE_MEDIA_STORAGE"
android:protectionLevel="signature|privileged" />
可以看到,WRITE_EXTERNAL_STORAGE和READ_EXTERNAL_STORAGE在同一個許可權組。而WRITE_MEDIA_STORAGE不在裡面。那為什麼給了WRITE_EXTERNAL_STORAGE許可權,就可以讀外部儲存呢(雖然註釋寫了可以讀外部儲存,但是我需要確鑿的證據)?
那應該有個地方宣告:
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" >
<group gid="sdcard_rw" />
</permission>
為什麼在framework目錄沒找到呢,frameworks/base/data/etc/platform.xml裡面也沒有啊?
frameworks/base/data/etc/platform.xml裡面是這個寫的:
<!-- These are permissions that were mapped to gids but we need
to keep them here until an upgrade from L to the current
version is to be supported. These permissions are built-in
and in L were not stored in packages.xml as a result if they
are not defined here while parsing packages.xml we would
ignore these permissions being granted to apps and not
propagate the granted state. From N we are storing the
built-in permissions in packages.xml as the saved storage
is negligible (one tag with the permission) compared to
the fragility as one can remove a built-in permission which
no longer needs to be mapped to gids and break grant propagation. -->
<permission name="android.permission.READ_EXTERNAL_STORAGE" />
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" />
好吧,這段我也沒理解太懂,我去找了下packages.xml,它生成在/data/system/packages.xml,還有個/data/system/packages.list,兩個都pull出來,也沒找到sdcard_rw.
簡單看no longer needs to be mapped to gids就可以了.這一塊具體在哪我就沒再深究了。
這一部分,意思就是說我們可以通過申請許可權來加入到一些gid裡面去。擴充套件我們apk的檔案操作範圍。
再多講一個我這邊遇到的問題
這個例子強調的對檔案的操作(如把檔案A拷貝到另一個目錄),拷貝後的檔案一定要關注它開放給其他組的許可權。
客戶有個需求,是動態替換開機動畫。他們傳入一個路徑字串。我們提供介面。
實現的方式是在BootAnimation.cpp擴充套件一個目錄,預設是/system/media/bootanimation.zip嘛。
然後通過我們定義的api(我們的一個系統apk,sharedUserID是system的)把客戶的zip檔案通過程式碼拷貝到我們的目標路徑。
當然,這中間涉及到我司的ipsm分割槽和一些Selinux對應的te檔案修改,這些不細說。
只講,程式碼拷貝過來的檔案的許可權問題,就是拷貝過來的檔案,哪些組可以對他進行rwe.
我們嘗試adb的方法(在userdebug上)
adb push bootanimation.zip /ipsm/media/honeywell
重啟手機可以看到生效額。
但是先把bootanimation.zip放在sdcard,然後用程式碼拷貝過去卻不行?
adb push後:
EDA51-1:/ipsm/media/honeywell # ls -l
-rw-rw-rw- 1 root root 7649510 2018-08-22 23:55 bootanimation.zip
程式碼拷貝後:
EDA51-1:/ipsm/media/honeywell # ls -l
-rw------- 1 system system 7649510 2018-11-08 14:09 bootanimation.zip
發現push的是root,而且其他使用者是可以rw的。
而程式碼拷貝是system,其他使用者是不能讀寫的。
EDA51-1:/ipsm/media/honeywell # chmod -R 777 bootanimation.zip後
EDA51-1:/ipsm/media/honeywell # ls -l
-rwxrwxrwx 1 system system 7649510 2018-11-08 14:09 bootanimation.zip
重啟可以了。
那麼程式碼裡面如何修改許可權呢?
程式碼裡面通過Runtime去做肯定不行了,貌似android M以後的user版本就沒有su了。
不是su的話,shell裡面也是不行的,這種方式更別提程式碼裡面了。
EDA51-1:/ipsm/media/honeywell $ chmod -R 777 bootanimation.zip
chmod: chmod 'bootanimation.zip' to 100777: Operation not permitted提示沒有許可權。
可以通過以下方式:
private void changeFolderPermission(File dirFile) throws IOException { Set<PosixFilePermission> perms = new HashSet<PosixFilePermission>(); perms.add(PosixFilePermission.OWNER_READ); perms.add(PosixFilePermission.OWNER_WRITE); perms.add(PosixFilePermission.OWNER_EXECUTE); perms.add(PosixFilePermission.GROUP_READ); perms.add(PosixFilePermission.GROUP_WRITE); perms.add(PosixFilePermission.GROUP_EXECUTE); perms.add(PosixFilePermission.OTHERS_READ); perms.add(PosixFilePermission.OTHERS_WRITE); perms.add(PosixFilePermission.OTHERS_EXECUTE); try { Path path = Paths.get(dirFile.getAbsolutePath()); Files.setPosixFilePermissions(path, perms); } catch (Exception e) { //logger.log(Level.SEVERE, "Change folder " + dirFile.getAbsolutePath() + " permission failed.", e); } }
這個api是AndroidO才有的。那麼Android N及以下怎麼處理呢?
我們可以在bootanimation.cpp裡面加上system("chmod 777 /ipsm/media/bootanimation.zip");當然,這樣一開始也是不會生效的,因為會因為Selinux報avc。
avc: denied { execute } for name="sh" dev="mmcblk0p32" ino=995 scontext=u:r:bootanim:s0 tcontext=u:object_r:shell_exec:s0 tclass=file permissive=0
這個需要去bootanim.te裡面去allow一下就可以了。
總結來說:
判斷一個apk是否能對某個目錄或者檔案進行對應的操作?
在不考慮SELinux的情況下,需要考慮以下幾點:
1.ps -A|grep com.xxx.xx 獲取這個apk的uid
2.cd 到對應目錄,ls -l,看這個目錄或者檔案的對應操作許可權對那些uid開放。
3.可以通過修改sharedUserID,在AndroidManifest.xml增加對應許可權加入到對應gid組裡面去,獲取對檔案or目錄的操作許可權。
SELinux:
selinux這個大家一般都用的比較多了。網上資料也比較多。
我個人的心得是兩點:
1.userdebug軟體上可以通過adb shell 然後getenforce,setenforce(0或者1)去進行除錯
Permissive說明是關閉(寬容)的。這種模式也會列印avc denied的log,但是實際沒做限制。
Enforcing說明是開啟的。即列印log,也實際生效,預設高版本的Android Selinux是開啟的。
需要注意一點,Selinux的狀態在重啟後會重置。比如開機的時候從預設的Enforcing改為Permissive,重啟後又變成Enforcing。這一點在除錯重啟階段的問題會有點蛋疼。
2.因為修改SELinux對應的te檔案可能會導致gms測試有fail項,一般來說我們修改
device\xxx\sepolicy\common\下的te檔案,不要去動到system/sepolicy目錄下的te檔案的neverallow,就不會有什麼問題。如果allow和neverallow衝突了,編譯是會報錯的。