Android 權(quán)限控制代碼分析
前在文章介紹過android系統(tǒng)管理層次:http://blog.csdn.net/andyhuabing/article/details/7030212 ,這里就核心代碼分析一下
android系統(tǒng)充分利用了linux的用戶權(quán)限管理方法,所以如果需要移植到其它系統(tǒng),這一塊也是一個相當(dāng)不小的工作量。那么android系統(tǒng)到底是如何使用這些的有利因素呢?
首先需要知道linux權(quán)限的兩個基本知識:
1、 一個用戶可以屬于多個組.
2、 一個文件只能屬于某個組。
這里主要是在AndroidManifest.xml中聲明權(quán)限,主要是通過在AndroidManifest.xml中顯示地聲明應(yīng)用程序需要的權(quán)限,防止應(yīng)用程序錯誤的使用服務(wù),不恰當(dāng)訪問資源。
Android中每種權(quán)限都用一個獨立的標(biāo)簽表示.如:
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
當(dāng)在安裝(Install) 應(yīng)用程序時,Android就會給予一個UID。這個UID可連結(jié)到該應(yīng)用程序的 AndroidManifest.xml檔案的內(nèi)容。所User在安裝你的應(yīng)用程序時,在屏幕上的窗口里可以檢視這個 AndroidManifest.xml檔案的內(nèi)容。在檢視時,用戶會看到你對應(yīng)用程序的目的、權(quán)限等說明。當(dāng)你接受這支程序的意圖、權(quán)限說明之后,Android就安裝它,并給它一個UID。萬一在你的應(yīng)用程序執(zhí)行期間有越軌(企圖做出非權(quán)限范圍)的行為時,用戶將會得到Android的警告訊息。
下面是兩個安裝程序安裝時的界面
這兩個應(yīng)用其實代碼是一樣的,唯一的不同就是它們的AndroidManifest.xml,圖2的AndroidManifest.xml中多了如下內(nèi)容:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
即需要使用存儲設(shè)備和錄音設(shè)備,在安裝的時候,就會提示用戶它需要的權(quán)限。Android里面是怎么去控制的呢?
在安裝apk的時候,會解析這個AndroidManifest.xml,把相應(yīng)的信息保存起來。
代碼路徑:frameworks\base\core\java\android\content\pm\PackageParser.java
最終調(diào)用的是private Package parsePackage(
Resources res, XmlResourceParser parser, int flags, String[] outError)
這個函數(shù),在里面對AndroidManifest.xml進行了解析,其中就有
private Package parsePackage(
Resources res, XmlResourceParser parser, int flags, String[] outError)
throws XmlPullParserException, IOException {
...
String tagName = parser.getName();
if (tagName.equals("application")) {
...
} else if (tagName.equals("permission-group")) {
if (parsePermissionGroup(pkg, res, parser, attrs, outError) == null) {
return null;
}
} else if (tagName.equals("permission")) {
if (parsePermission(pkg, res, parser, attrs, outError) == null) {
return null;
}
} else if (tagName.equals("permission-tree")) {
if (parsePermissionTree(pkg, res, parser, attrs, outError) == null) {
return null;
}
} else if (tagName.equals("uses-permission")) {
sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestUsesPermission);
// Note: don't allow this value to be a reference to a resource
// that may change.
String name = sa.getNonResourceString(
com.android.internal.R.styleable.AndroidManifestUsesPermission_name);
sa.recycle();
if (name != null && !pkg.requestedPermissions.contains(name)) {
pkg.requestedPermissions.add(name.intern());
}
XmlUtils.skipCurrentTag(parser);
}
...
}
這里對它使用的權(quán)限進行了解析。
這里保存的都是"android.permission.WRITE_EXTERNAL_STORAGE"這樣的字符串,在解析完后,會調(diào)用grantPermissionsLP函數(shù)獲取對應(yīng)的group_id,
代碼路徑:frameworks\base\services\java\com\android\server\PackageManagerService.java
private void grantPermissionsLP(PackageParser.Package pkg, boolean replace) {
...
if (allowed) {
if (!gp.grantedPermissions.contains(perm)) {
changedPermission = true;
gp.grantedPermissions.add(perm);
gp.gids = appendInts(gp.gids, bp.gids);
} else if (!ps.haveGids) {
gp.gids = appendInts(gp.gids, bp.gids);
}
} else {
Slog.w(TAG, "Not granting permission " + perm
+ " to package " + pkg.packageName
+ " because it was previously installed without");
}
...
}
這里把相應(yīng)的組都保存到了gids中。
當(dāng)應(yīng)用程序啟動的過程中會調(diào)用
private final void startProcessLocked(ProcessRecord app,
String hostingType, String hostingNameStr)
代碼路徑:frameworks\base\services\java\com\android\server\am\ActivityManagerService.java
private final void startProcessLocked(ProcessRecord app,
String hostingType, String hostingNameStr) {
...
try {
int uid = app.info.uid;
int[] gids = null;
try {
gids = mContext.getPackageManager().getPackageGids(
app.info.packageName);
} catch (PackageManager.NameNotFoundException e) {
Slog.w(TAG, "Unable to retrieve gids", e);
}
...
int pid = Process.start("android.app.ActivityThread",
mSimpleProcessManagement ? app.processName : null, uid, uid,
gids, debugFlags, null);
}
這里就是獲取前面保存的gids,再后面調(diào)用創(chuàng)建了一個新的進程,這里傳的參數(shù)就有g(shù)ids
創(chuàng)建新進程利用jni最終調(diào)用 forkAndSpecializeCommon 函數(shù) (路徑:\dalvik\vm\native\dalvik_system_Zygote.c)
static pid_t forkAndSpecializeCommon(const u4* args, bool isSystemServer)
{
pid_t pid;
uid_t uid = (uid_t) args[0];
gid_t gid = (gid_t) args[1];
ArrayObject* gids = (ArrayObject *)args[2];
u4 debugFlags = args[3];
ArrayObject *rlimits = (ArrayObject *)args[4];
int64_t permittedCapabilities, effectiveCapabilities;
...
pid = fork();
if (pid == 0) {
int err;
/* The child process */
err = setgroupsIntarray(gids);
if (err < 0) {
LOGE("cannot setgroups(): %s", strerror(errno));
dvmAbort();
}
err = setrlimitsFromArray(rlimits);
if (err < 0) {
LOGE("cannot setrlimit(): %s", strerror(errno));
dvmAbort();
}
err = setgid(gid);
if (err < 0) {
LOGE("cannot setgid(%d): %s", gid, strerror(errno));
dvmAbort();
}
err = setuid(uid);
if (err < 0) {
LOGE("cannot setuid(%d): %s", uid, strerror(errno));
dvmAbort();
}
...
}
我們看到在子進程里調(diào)用setgroupsIntarray設(shè)置該進程所屬的組,這樣它就擁有了該組的權(quán)限。也通過setgid及setuid決定了應(yīng)用程序的uid及gid值。
舉個例子:
我們新建一Android工程,讀取其應(yīng)用的uid/gid值,貼入如下代碼:
try{
java.lang.Process process = Runtime.getRuntime().exec("id");
InputStream input = process.getInputStream();
byte[] bytes = new byte[1204];
int len;
while((len = (input.read(bytes))) > 0)
{
System.out.print(new String(bytes, 0, len));
}
input.close();
這里運行運行linux的id命令的代碼,id命令的功能是輸出當(dāng)前用戶的uid,主要的group id和所在的group
在其AndroidManifest.xml添加如下權(quán)限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
運行后看log里面有:
這里面就有groups=1015
再看下android_filesystem_config.h里面
代碼路徑:
system\core\include\private
有如下代碼
#define AID_ROOT 0 /* traditional unix root user */...
#define AID_WIFI 1010 /* wifi subsystem */
#define AID_ADB 1011 /* android debug bridge (adbd) */
#define AID_INSTALL 1012 /* group for installing packages */
#define AID_MEDIA 1013 /* mediaserver process */
#define AID_DHCP 1014 /* dhcp client */
#define AID_SDCARD_RW 1015 /* external storage write access */
再看下platform.xml
路徑:frameworks\base\data\etc
有如下配置
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" >
<group gid="sdcard_rw" />
</permission>
這樣就把android.permission.WRITE_EXTERNAL_STORAGE 、"sdcard_rw"、以及 1015組關(guān)聯(lián)起來了
我們再看下當(dāng)sd卡掛載上去后目錄的權(quán)限:
ls -l
drwxr-xr-x root system 1970-01-01 08:00 obb
drwxr-xr-x root system 1970-01-01 08:00 asec
drwx------ root root 1970-01-01 08:00 secure
d---rwxr-x system sdcard_rw 2012-03-29 17:11 sdcard
可以看到對于sd卡,組用戶具有讀寫權(quán)限,而我們的應(yīng)用也加入了這個組,這樣它就可以操作sdcard了。
當(dāng)一個應(yīng)用需要操作sdcard而沒有在AndroidManifest.xml添加相應(yīng)的權(quán)限時,就不能成功完成。
以下是同一個程序,一個有在AndroidManifest.xml添加WRITE_EXTERNAL_STORAGE權(quán)限,一個沒有,它們的對比,可以看到由于沒有權(quán)限程序運行異常了。
對于管理權(quán)限的xml文件補充說明一下:
源代碼中權(quán)限文件位于: frameworks\base\data\etc 下面,鏡像生成在 system\etc\permissions\platform.xml中
文件權(quán)限讀取點:
readPermissions @ PackageManagerService.java
void readPermissions() {
// Read permissions from .../etc/permission directory.
File libraryDir = new File(Environment.getRootDirectory(), "etc/permissions");
if (!libraryDir.exists() || !libraryDir.isDirectory()) {
Slog.w(TAG, "No directory " + libraryDir + ", skipping");
return;
}
if (!libraryDir.canRead()) {
Slog.w(TAG, "Directory " + libraryDir + " cannot be read");
return;
}
// Iterate over the files in the directory and scan .xml files
for (File f : libraryDir.listFiles()) {
// We'll read platform.xml last
if (f.getPath().endsWith("etc/permissions/platform.xml")) {
continue;
}
if (!f.getPath().endsWith(".xml")) {
Slog.i(TAG, "Non-xml file " + f + " in " + libraryDir + " directory, ignoring");
continue;
}
if (!f.canRead()) {
Slog.w(TAG, "Permissions library file " + f + " cannot be read");
continue;
}
readPermissionsFromXml(f);
}
// Read permissions from .../etc/permissions/platform.xml last so it will take precedence
final File permFile = new File(Environment.getRootDirectory(),
"etc/permissions/platform.xml");
readPermissionsFromXml(permFile);
}
platform.xml 的重要性在于:
<!-- This file is used to define the mappings between lower-level system
user and group IDs and the higher-level permission names managed
by the platform.
Be VERY careful when editing this file! Mistakes made here can open
big security holes.
-->
聯(lián)系客服