自定义Android系统级权限组
来源:互联网 发布:linux shadowsock 编辑:程序博客网 时间:2024/06/05 16:15
转自: https://yq.aliyun.com/articles/3008?spm=5176.100240.searchblog.19.yMfiLd#
写完上一篇博客,发现了这么一篇文章跟自己上一篇有些类似,还没来得及仔细看,先转来
自定义Android系统级权限组
摘要: Android安全模型基于Linux的权限管理,使用沙箱隔离机制将每个应用的进程资源隔离。Android应用程序在安装时赋予一个UID,UID不同的应用程序完全隔离。另一方面,应用如果想使用某种服务,需要在AndroidManifest.xml中申请。
Android安全模型基于Linux的权限管理,使用沙箱隔离机制将每个应用的进程资源隔离。Android应用程序在安装时赋予一个UID,UID不同的应用程序完全隔离。
另一方面,应用如果想使用某种服务,需要在AndroidManifest.xml中申请。比如,想使用网络的话,需要在AndroidManifest.xml中添加:
<uses-permission android:name="android.permission.INTERNET" />
INTERNET权限将被映射到底层的GID。所以,一个应用有一个UID,可以有多个GID,来获得多个权限。
关于Android权限管理更详细的内容这里就不再赘述了,这方面的资料很多。直接进入正题,如何自定义一个类似于上面的INTERNET的系统级权限组?
我们知道,Android本身支持在应用程序的AndroidManifest.xml中自定义权限,但这种自定义的权限没有被映射到系统底层的用户组中,没有独立的GID。如果在系统中有一个C语言写的服务,只有应用申请了权限才可以使用,我们就需要将这个权限映射到底层。
分析
本文中,作为例子,我们假设有一个C语言实现的功能,它提供say_hello的服务,使用这个服务的应用要在AndroidManifest.xml中添加来申请权限。
AndroidManifest.xml是在安装应用的时候解析的。最终调用的解析函数是
frameworks/base/core/java/android/content/pm/PackageParser.java
private Package parsePackage( Resources res, XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException, IOException { ...... while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { continue; } String tagName = parser.getName(); if (tagName.equals("application")) { ...... } else if (tagName.equals("keys")) { if (!parseKeys(pkg, res, parser, attrs, outError)) { return null; } } else if (tagName.equals("permission-group")) { if (parsePermissionGroup(pkg, flags, 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")) { if (!parseUsesPermission(pkg, res, parser, attrs, outError)) { return null; } } else if (tagName.equals("uses-configuration")) { ......
针对不同标签调用对应的解析函数。对于uses-permission调用的是parseUsesPermission:
frameworks/base/core/java/android/content/pm/PackageParser.java
private boolean parseUsesPermission(Package pkg, Resources res, XmlResourceParser parser, AttributeSet attrs, String[] outError) throws XmlPullParserException, IOException { ······ if ((maxSdkVersion == 0) || (maxSdkVersion >= Build.VERSION.RESOURCES_SDK_INT)) { if (name != null) { int index = pkg.requestedPermissions.indexOf(name); if (index == -1) { pkg.requestedPermissions.add(name.intern()); pkg.requestedPermissionsRequired.add(required ? Boolean.TRUE : Boolean.FALSE); } else { if (pkg.requestedPermissionsRequired.get(index) != required) { outError[0] = "conflicting <uses-permission> entries"; mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } } } } XmlUtils.skipCurrentTag(parser); return true; }
它的工作就是调用pkg.requestedPermissions.add(name.intern());将诸如android.permission.INTERNET这样的字符串添加到pkg.requestedPermissions列表中。
解析完成后,会调用grantPermissionsLPw获取相应的GID:
frameworks/base/services/java/com/android/server/pm/PackageManagerService.java
private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace) { ....... final int N = pkg.requestedPermissions.size(); for (int i=0; i<N; i++) { final String name = pkg.requestedPermissions.get(i); final boolean required = pkg.requestedPermissionsRequired.get(i); final BasePermission bp = mSettings.mPermissions.get(name); if (DEBUG_INSTALL) { if (gp != ps) { Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp); } } if (bp == null || bp.packageSetting == null) { Slog.w(TAG, "Unknown permission " + name + " in package " + pkg.packageName); continue; } final String perm = bp.name; boolean allowed; boolean allowedSig = false; ...... if (allowed) { ...... 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"); } } else { ...... } } ...... }
需要注意的是final BasePermission bp = mSettings.mPermissions.get(name);
mSettings保存了与设定相关的东西,class Settings定义在frameworks/base/services/java/com/android/server/pm/Settings.java中,它的mPermissions成员的类型是HashMap,保存了权限名字到权限信息的映射。BasePermission中有一个int[] gids成员,这就是这个权限对应的gid;还有一个PackageSettingBase类型的packageSetting成员,它指定了声明这个权限的包的配置信息。
定义权限名
那么,mSettings.mPermissions是在什么时候初始化的呢?
PackageManagerService在初始化时,会调用readPermissions();它又调用了readPermissionsFromXml(permFile),permFile文件的文件路径是/etc/permissions/platform.xml,这个文件中定义了底层GID和高层权限名字之间的对应关系:
frameworks/base/data/etc/platform.xml
<permissions> ...... <permission name="android.permission.INTERNET" > <group gid="inet" /> </permission> ......</permissions>
所以我们在这个文件中添加say_hello权限:
frameworks/base/data/etc/platform.xml
<permission name="android.permission.SAY_HELLO" > <group gid="say_hello" /> </permission>
获取整型GID
回到readPermissionsFromXml函数,对于名字是“permission”的标签,会调用readPermission函数:
frameworks/base/services/java/com/android/server/pm/PackageManagerService.java
void readPermission(XmlPullParser parser, String name) throws IOException, XmlPullParserException { name = name.intern(); BasePermission bp = mSettings.mPermissions.get(name); if (bp == null) { bp = new BasePermission(name, null, BasePermission.TYPE_BUILTIN); mSettings.mPermissions.put(name, bp); } int outerDepth = parser.getDepth(); int type; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { continue; } String tagName = parser.getName(); if ("group".equals(tagName)) { String gidStr = parser.getAttributeValue(null, "gid"); if (gidStr != null) { int gid = Process.getGidForName(gidStr); bp.gids = appendInt(bp.gids, gid); } else { Slog.w(TAG, "<group> without gid at " + parser.getPositionDescription()); } } XmlUtils.skipCurrentTag(parser); } }
首先将权限的名字添加到mSettings.mPermissions列表中,然后进入while循环,把这个权限对应的所有gid都添加到bp.gids中。gid是调用Process.getGidForName根据gid的名字得到了,在我们的例子中,也就是"say_hello"。getGidForName是Process中的一个native方法:
frameworks/base/core/java/android/os/Process.java
public static final native int getGidForName(String name);
它的实际定义是
frameworks/base/core/jni/android_util_Process.cpp
jint android_os_Process_getGidForName(JNIEnv* env, jobject clazz, jstring name){ ...... const size_t N = name8.size(); if (N > 0) { const char* str = name8.string(); for (size_t i=0; i<N; i++) { if (str[i] < '0' || str[i] > '9') { struct group* grp = getgrnam(str); if (grp == NULL) { return -1; } return grp->gr_gid; } } return atoi(str); } return -1;}
它就是根据组名调用getgrnam获取组信息。我们知道,getgrnam是一个C库函数,在Linux中标准定义是根据读取/etc/group文件获取组信息,但在Android中并没有这个文件,那么这个函数是怎么实现的呢?
bionic/libc/bionic/stubs.cpp
static group* android_iinfo_to_group(group* gr, const android_id_info* iinfo) { gr->gr_name = (char*) iinfo->name; gr->gr_gid = iinfo->aid; gr->gr_mem[0] = gr->gr_name; gr->gr_mem[1] = NULL; return gr;}static group* android_name_to_group(group* gr, const char* name) { for (size_t n = 0; n < android_id_count; ++n) { if (!strcmp(android_ids[n].name, name)) { return android_iinfo_to_group(gr, android_ids + n); } } return NULL;}group* getgrnam(const char* name) { // NOLINT: implementing bad function. stubs_state_t* state = __stubs_state(); if (state == NULL) { return NULL; } if (android_name_to_group(&state->group_, name) != 0) { return &state->group_; } return app_id_to_group(app_id_from_name(name), state);}
显而易见,它是遍历android_ids数组,查找是否有对应的组。
android_ids的定义在android_filesystem_config.h中
system/core/include/private/android_filesystem_config.h
......#define AID_INET 3003 /* can create AF_INET and AF_INET6 sockets */......static const struct android_id_info android_ids[] = {...... { "inet", AID_INET, },......};
这样就把inet字符串和整型值3003关联起来了。所以我们不妨把say_hello的整型gid定义为8001,在android_filesystem_config.h中添加
#define AID_SAY_HELLO 8001
在android_ids数组中添加
{ "say_hello", AID_SAY_HELLO, },
这样就把字符串的say_hello和数字8001关联起来了。
在android中声明权限
回到readPermissions,readPermissions()完成后会调用scanDirLI扫描系统中安装的apk,它调用scanPackageLI建立每个apk的配置结构PackageSetting(继承于上面提到的PackageSettingBase),并把mSettings.mPermissions中保存的权限与之相关联。然后调用updatePermissionsLPw更新mSettings.mPermissions列表。
frameworks/base/services/java/com/android/server/pm/PackageManagerService.java
private void updatePermissionsLPw(String changingPkg, PackageParser.Package pkgInfo, int flags) { ...... // Make sure all dynamic permissions have been assigned to a package, // and make sure there are no dangling permissions. it = mSettings.mPermissions.values().iterator(); while (it.hasNext()) { final BasePermission bp = it.next(); if (bp.type == BasePermission.TYPE_DYNAMIC) { if (DEBUG_SETTINGS) Log.v(TAG, "Dynamic permission: name=" + bp.name + " pkg=" + bp.sourcePackage + " info=" + bp.pendingInfo); if (bp.packageSetting == null && bp.pendingInfo != null) { final BasePermission tree = findPermissionTreeLP(bp.name); if (tree != null && tree.perm != null) { bp.packageSetting = tree.packageSetting; bp.perm = new PackageParser.Permission(tree.perm.owner, new PermissionInfo(bp.pendingInfo)); bp.perm.info.packageName = tree.perm.info.packageName; bp.perm.info.name = bp.name; bp.uid = tree.uid; } } } if (bp.packageSetting == null) { // We may not yet have parsed the package, so just see if // we still know about its settings. bp.packageSetting = mSettings.mPackages.get(bp.sourcePackage); } else { } if (bp.packageSetting == null) { Slog.w(TAG, "Removing dangling permission: " + bp.name + " from package " + bp.sourcePackage); it.remove(); } else if (changingPkg != null && changingPkg.equals(bp.sourcePackage)) { if (pkgInfo == null || !hasPermission(pkgInfo, bp.name)) { Slog.i(TAG, "Removing old permission: " + bp.name + " from package " + bp.sourcePackage); flags |= UPDATE_PERMISSIONS_ALL; it.remove(); } } }
可以看到如果权限的packageSetting为空,则将被从列表中删除。所以,只在platform.xml中定义了权限是不够的。必须有包声明这个权限,从而使bp.packageSetting不为空(前面说过,scanPackageLI会将权限和包配置关联起来)。像INTERNET这样的系统权限是在framework-res.apk(包名是android)中声明的:
frameworks/base/core/res/AndroidManifest.xml
...... <!-- Allows applications to open network sockets. --> <permission android:name="android.permission.INTERNET" android:permissionGroup="android.permission-group.NETWORK" android:protectionLevel="dangerous" android:description="@string/permdesc_createNetworkSockets" android:label="@string/permlab_createNetworkSockets" /> ......
所以我们也在frameworks/base/core/res/AndroidManifest.xml中添加如下内容:
<permission android:name="android.permission.SAY_HELLO" android:protectionLevel="dangerous" android:label="say hello" />
至此,我们就完成了say_hello权限的定义。
实现过程总结
1、在platform.xml中添加
frameworks/base/data/etc/platform.xml
<permission name="android.permission.SAY_HELLO" > <group gid="say_hello" /> </permission>
2、在android_filesystem_config.h中添加
#define AID_SAY_HELLO 8001
在android_ids数组中添加
{ "say_hello", AID_SAY_HELLO, },
3、在frameworks/base/core/res/AndroidManifest.xml中添加
<permission android:name="android.permission.SAY_HELLO" android:protectionLevel="dangerous" android:label="say hello" />
4、将frameworks/base/data/etc/platform.xml push到/etc/permissions/下
5、执行mmm bionic/libc/ 编译出libc.so,并将其push到/system/lib下
6、执行mmm frameworks/base/core/res/编译出framework-res.apk,并将其push到/system/framework下
完成,可以在android应用中验证成果了!
在底层获取应用的权限
我们的应用场景是在C语言中管理权限,那么如何在C语言中获取各应用的权限呢?
其实,在PackageManagerService初始化所有包信息之后就会调用mSettings.writeLPr()(只要系统中包的信息有改变,比如安装应用,都会调用这个函数)。
frameworks/base/services/java/com/android/server/pm/Settings.java
void writeLPr() { ...... // 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(); fstr = new FileOutputStream(writeTarget); str = new BufferedOutputStream(fstr); try { FileUtils.setPermissions(fstr.getFD(), 0660, SYSTEM_UID, PACKAGE_INFO_GID); StringBuilder sb = new StringBuilder(); for (final PackageSetting pkg : mPackages.values()) { if (pkg.pkg == null || pkg.pkg.applicationInfo == null) { 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.getGids(); // 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: // system/core/run-as/run-as.c // system/core/sdcard/sdcard.c // sb.setLength(0); sb.append(ai.packageName); sb.append(" "); sb.append((int)ai.uid); sb.append(isDebug ? " 1 " : " 0 "); sb.append(dataPath); sb.append(" "); sb.append(ai.seinfo); sb.append(" "); 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"); str.write(sb.toString().getBytes()); } str.flush(); FileUtils.sync(fstr); str.close(); journal.commit(); } catch (Exception e) { ...... }
mPackageListFilename.getAbsolutePath()的结果是/data/system/packages.list,这段代码的任务就是将mPackages中保存的所有包的信息保存到/data/system/packages.list.tmp,保存的内容和格式见注释。如果这个过程没有出错,最后调用journal.commit()将/data/system/packages.list.tmp重命名为/data/system/packages.list覆盖原来的文件。
所以,/data/system/packages.list中保存了所有应用申请的权限,C代码只要读这个文件就能判断某个应用是否申请了我们要求的权限。
通常情况下,在接收到应用的请求时,我们不愿意每次都读取这个文件然后解析、判断这个应用的gids中是否有我们定义的id,更好的做法是将所有申请了权限的包缓存起来,这样就不必每次都读文件。而且,writeLPr更新这个文件的方法是直接用新文件覆盖旧文件,所以我们只需要监听这个文件的删除事件,在事件发生时,更新缓存。下面的代码片段是一个使用这种方法的例子。
const static char *package_list_file = "/data/system/packages.list";/*packages.list文件*/static int read_package_list() { FILE* file = fopen(package_list_file, "r"); if (!file) { return -1; } char buf[512]; while (fgets(buf, sizeof(buf), file) != NULL) { char package_name[512]; int appid; char gids[512]; if (sscanf(buf, "%s %d %*d %*s %*s %s", package_name, &appid, gids) == 3) { char* package_name_dup = strdup(package_name); char* token = strtok(gids, ","); /*将appid(也就是应用进程的uid)从缓存中删除*/ while (token != NULL) { if (strtoul(token, NULL, 10) == AID_SAY_HELLO /*权限gid*/) { /*该应用申请了权限,将其添加到缓存中*/ break; } token = strtok(NULL, ","); } } } fclose(file); return 0;}void watch_package_list() { struct inotify_event *event; char event_buf[512]; int nfd = inotify_init(); if (nfd < 0) { return; } bool active = false; while (1) { if (!active) { int res = inotify_add_watch(nfd, package_list_file, IN_DELETE_SELF);/*监听删除事件*/ if (res == -1) { if (errno == ENOENT || errno == EACCES) { sleep(3); continue; } else { return; } } if (read_package_list() == -1) { return; } active = true; } int event_pos = 0; int res = read(nfd, event_buf, sizeof(event_buf)); if (res < (int) sizeof(*event)) { if (errno == EINTR) continue; return; } while (res >= (int) sizeof(*event)) { int event_size; event = (struct inotify_event *) (event_buf + event_pos); if ((event->mask & IN_IGNORED) == IN_IGNORED) { active = false; } event_size = sizeof(*event) + event->len; res -= event_size; event_pos += event_size; } }}
- 自定义Android系统级权限组
- Android 系统默认自定义launcher权限设置。
- framework中自定义系统级权限
- 权限系统自定义标签
- 自定义系统权限
- Android--Android自定义权限
- Android--Android自定义权限
- 给android系统加入系统级权限
- Android 自定义Permission;permission-tree;permission-group自定义(权限,权限组,权限树)
- android 自定义权限问题
- android自定义权限
- 【Android】自定义权限
- Android 自定义权限 permission
- android 自定义 permission 权限
- android 自定义权限 permission
- android 自定义权限 permission
- android 自定义权限 permission
- android 自定义权限
- 安装opencv时cmake的问题
- 发送验证码的倒计时。GCD方法
- 九度题目1124:Digital Roots
- sscanf 与 正则表达式
- Android 内存
- 自定义Android系统级权限组
- Horizontal+异步+pullTorefresh+viewPager(本地图片)+每页都有数据
- 两个HTML页面之间传值的问题
- Android推荐一个查看资源文件引用次数的小插件
- GPIO驱动框架
- 虚拟机字节码执行引擎
- JDK9干货
- Java 访问权限控制
- openssl的加解密,签名,验签代码