夜雨聆风学习资料网

ARTICLE · 1354937

90%答不出系统权限面试题:从APP申请到内核剖析

90%答不出系统权限面试题:从APP申请到内核剖析

1 背景

在前几天vip群中有一起讨论一个关于安卓权限的面试题:

 “framework层定义的权限是怎么让linux认识的”

针对这个题目的审题大家其实也是有一定争议的:

其实有同学就提出到底这里说的linux是系统内核认识还是linux的普通native进程认识,所以是有两种回答方向:

 1、linux系统内核识别权限方向:往framework的权限最后是如何影响到进程,gid,groups,从platform.xml解析权限到fork进设置相关gid等。

2、linux的native进程识别方向:比如SurfaceFlinger,audioserver中如何判断一些跨进程的进是否有某个权限.

那今天我们就主要针对方向1部分进行深入剖析,用一个经典的网络权限来作为案例进行说明,为啥在Manifest文件中声明一行

    <uses-permission android:name="android.permission.INTERNET" />

就可以顺利的进行网络访问了,如果不声明权限则百分百会报错如下:

E MainActivity: java.lang.SecurityException: Permission denied (missing INTERNET permission?)
E MainActivity:  at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:150)
E MainActivity:  at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:103)
E MainActivity:  at java.net.InetAddress.getAllByName(InetAddress.java:1152)

2. 第一层:Manifest 声明与框架侧权限定义

App 在 AndroidManifest.xml 里写:

<uses-permissionandroid:name="android.permission.INTERNET" />

而这条权限的「官方定义」在框架资源里,AOSP 15 位于:

frameworks/base/core/res/AndroidManifest.xml:2096-2099

<permissionandroid:name="android.permission.INTERNET"
android:description="@string/permdesc_createNetworkSockets"
android:label="@string/permlab_createNetworkSockets"
android:protectionLevel="normal|instant" />

关键点就在 protectionLevel="normal":

  • normal 权限 → 安装时自动授予,不需要用户确认;
  • 没有 runtime 运行时授权逻辑,所以 checkSelfPermission() 对它而言意义不大。

这意味着:决定「能不能联网」的不是 PMS 的运行时检查,而是下面这套 GID 机制。


3. 第二层:权限名 → GID 的映射表 platform.xml

Android 把「权限名 → Linux 组」的映射集中放在 platform.xml,AOSP 15:

frameworks/base/data/etc/platform.xml:59-61

<permissionname="android.permission.INTERNET" >
<groupgid="inet" />
</permission>

同一份文件里还有两条特殊的 assign-permission(把权限直接固定派给某个系统 uid,不走正常授权流程):

<assign-permissionname="android.permission.INTERNET"uid="media" /><!-- :169 -->
<assign-permissionname="android.permission.INTERNET"uid="shell" /><!-- :171 -->

这里揭示了一个重要事实:网络权限的载体是组名 inet,而不是什么运行时标记。

inet 这个名字对应的数字,定义在 AOSP 的 AID 表里:

system/core/libcutils/include/private/android_filesystem_config.h:166

#define AID_INET 3003         /* can create AF_INET and AF_INET6 sockets */

内核侧同样有一份(供 in_egroup_p() 使用):

kernel/xiaomi/sdm710/include/linux/android_aid.h:22

#define AID_INET         KGIDT_INIT(3003)

两处一致,都是 3003。


4. 第三层:SystemConfig 解析并落地 GID

系统启动时 SystemConfig 读取 platform.xml,把 <group gid="inet"/> 转成数字 GID:

frameworks/base/services/core/java/com/android/server/SystemConfig.java:1797-1836(readPermission)

String tagName = parser.getName();
if ("group".equals(tagName)) {
    String gidStr = parser.getAttributeValue(null, "gid");
if (gidStr != null) {
int gid = Process.getGidForName(gidStr);   // "inet" -> 3003
if (gid != -1) {
            perm.gids = appendInt(perm.gids, gid);  // 写入 PermissionEntry.gids
        }
        ...
    }
}

随后 PermissionManagerServiceImpl 在构造时,把这份 PermissionEntry.gids 挂到权限对象 Permission 上:

frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java:471-473

if (perm.gids != null) {
    bp.setGids(perm.gids, perm.perUser);
}

至此,「INTERNET 权限」在 Java 世界里就等价于「Permission 对象携带 mGids = {3003}」。


5. 第四层:授权判定 → 按 uid 汇总 GID

normal 权限安装即授,所以对声明了 INTERNET 的 app,PermissionState.isGranted() 为 true。

真正把「已授权权限」折算成「进程该带的 GID 列表」的是这一段:

frameworks/base/services/core/java/com/android/server/pm/permission/UidPermissionState.java:331-349

publicint[] computeGids(@NonNullint[] globalGids, @UserIdIntint userId) {
    IntArray gids = IntArray.wrap(globalGids);
    ...
for (int i = 0; i < permissionsSize; i++) {
        PermissionState permissionState = mPermissions.valueAt(i);
if (!permissionState.isGranted()) {     // 只统计已授权的
continue;
        }
finalint[] permissionGids = permissionState.computeGids(userId);
if (permissionGids.length != 0) {
            gids.addAll(permissionGids);        // 3003 被加进来
        }
    }
return gids.toArray();
}

对外的统一入口:

PermissionManagerServiceImpl.java:5124-5136

publicint[] getGidsForUid(int uid) {
    ...
return uidState.computeGids(mGlobalGids, userId);
}

注意 Permission.computeGids() 里还有 gidsPerUser 的分支:若配置了 perUser,会把 GID 变成 userId * AID_USER_OFFSET + gid 这种 per-user 形式;inet 没有标 perUser,所以始终是裸的 3003。


6. 第五层:进程启动时把 GID 带进 zygote

真正拉 app 进程时,ActivityManager 通过 ProcessList 向 PM 要 GID:

frameworks/base/services/core/java/com/android/server/am/ProcessList.java:1892-1926

permGids = pm.getPackageGids(app.info.packageName,
        MATCH_DIRECT_BOOT_AUTO, app.userId);   // ← 拿到 [3003, ...]

// 若有被拒绝的权限,把对应 GID 摘掉
if (app.processInfo != null && app.processInfo.deniedPermissions != null) {
for (int i = ...; i >= 0; i--) {
int[] denyGids = mService.mPackageManagerInt.getPermissionGids(
                app.processInfo.deniedPermissions.valueAt(i), app.userId);
if (denyGids != null) {
for (int gid : denyGids) {
                permGids = ArrayUtils.removeInt(permGids, gid);  // 动态剔除 3003
            }
        }
    }
}

gids = computeGidsForProcess(mountExternal, uid, permGids, externalStorageAccess);

computeGidsForProcess 在 perm GID 之外再补上 sharedAppGid / cacheAppGid / userGid 等基础设施组:

ProcessList.java:1781-1802

for (int permGid : permGids) {
    gidList.add(permGid);
}
if (sharedAppGid != UserHandle.ERR_GID) gidList.add(sharedAppGid);
if (cacheAppGid  != UserHandle.ERR_GID) gidList.add(cacheAppGid);
if (userGid      != UserHandle.ERR_GID) gidList.add(userGid);

最终 app.setGids(gids)(ProcessList.java:2057),这些 GID 会作为 zygote 参数拼进启动命令:

  • --setgid=<gid>:主组
  • --setgroups=<gid1,gid2,...>:补充组列表,3003 就在这里

(参数解析见 frameworks/base/core/java/com/android/internal/os/ZygoteArguments.java:288/349。)


7. 第六层:zygote 落地为进程的 supplementary group

zygote fork 出子进程后,native 层 SpecializeCommon 调用 SetGids,最终走 POSIX setgroups(2):

frameworks/base/core/jni/com_android_internal_os_Zygote.cpp:532-553

staticvoidSetGids(JNIEnv* env, jintArray managed_gids, jboolean is_child_zygote,
fail_fn_t fail_fn)
{
  ...
ScopedIntArrayRO gids(env, managed_gids);
  ...
if (setgroups(gids.size(), reinterpret_cast<constgid_t*>(&gids[0])) == -1) {
    fail_fn(CREATE_ERROR("setgroups failed: ..."));
  }
}

随后降权:

com_android_internal_os_Zygote.cpp:1999-2014

if (setresgid(gid, gid, gid) == -1) { ... }   // 设置真实/有效/保存组 id
...
if (setresuid(uid, uid, uid) == -1) { ... }   // 设置真实/有效/保存用户 id

到这一步,app 进程在内核里的凭证就确定了:

  • uid = 10000 + appId(应用自身 uid)
  • gid = 对应主组
  • supplementary groups 里带 3003(inet) —— 这是网络权限的唯一「落地形态」。

可以用 cat /proc/<pid>/status 里的 Groups: 行验证:有 3003 就说明该进程声明了 INTERNET。


8. 第七层:内核在网络协议栈入口做检查

有了 3003 这个补充组,内核才能在创建 socket 时「认出」这个进程有网络权限。

IPv4 的 socket 创建入口:

kernel/xiaomi/sdm710/net/ipv4/af_inet.c:273-274

if (!current_has_network())
return -EACCES;

而 current_has_network() 的定义:

kernel/xiaomi/sdm710/net/ipv4/af_inet.c:124-136

#ifdef CONFIG_ANDROID_PARANOID_NETWORK
#include<linux/android_aid.h>

staticinlineintcurrent_has_network(void)
{
return in_egroup_p(AID_INET) || capable(CAP_NET_RAW);
}
#else
staticinlineintcurrent_has_network(void)
{
return1;
}
#endif

IPv6 完全对称:

kernel/xiaomi/sdm710/net/ipv6/af_inet6.c:71-76, 138-139

staticinlineintcurrent_has_network(void)
{
return in_egroup_p(AID_INET) || capable(CAP_NET_RAW);
}
...
if (!current_has_network())
return -EACCES;

这里的核心逻辑一句话:

当前进程在 inet(3003) 这个补充组里,或者具备 CAP_NET_RAW capability,才允许创建 AF_INET/AF_INET6 socket;否则 inet_create()/inet6_create() 直接返回 -EACCES。

in_egroup_p(AID_INET) 检查的就是第 7 步 setgroups() 写进进程凭证的 supplementary group。


实战验证

带上网络权限时候情况查看是否带有网络相关的gid

声明后查看相关数据

grus:/ # cat /proc/30339/status | grep Groups                                                                                                                                                             
Groups: 3003 9997 20186 50186 

可以看到有声明网络权限时候,可以看到有3003这个值,代表属于inet(3003)组。

去除网络权限验证查看对应的

grus:/ # cat /proc/30118/status | grep Groups
Groups: 9997 20186 50186 

这里就没有看到inet(3003)这个组,自然访问网络就会报错。

总结

android.permission.INTERNET 本质上不是一次 Java 权限检查,而是一条「**Manifest 声明 → platform.xml 里的 inet 组名 → AID_INET=3003 → PMS 按 uid 汇总 GID → ProcessList 传给 zygote → native setgroups() 写入进程补充组 → 内核 inet_create()/inet6_create() 检查 in_egroup_p(AID_INET)**」的完整链路。控制点既在用户态的 GID 装配,也在内核态的 socket 创建入口,二者缺一不可。

注意误区:

很多人以为 INTERNET 是一个「运行时权限」(dangerous permission),会在安装后弹窗申请。它不是。

INTERNET 的 protectionLevel 是 normal,属于安装时自动授予、永不弹窗、用户也关不掉的权限。

那它的「控制力」到底从哪来?答案是:它根本不是靠 Java 层运行时检查起作用的,而是靠一个 Linux 补充组 GID,由内核在网络协议栈入口做访问控制。 下面把这个「权限 → GID → 内核」的转化链路逐层拆开。

全景链路图

APK AndroidManifest.xml
  <uses-permission android:name="android.permission.INTERNET"/>
        │  安装时(PackageInstaller / PMS)
        ▼
frameworks/base/core/res/AndroidManifest.xml   ← 框架侧权限声明
  <permission android:name="android.permission.INTERNET"
      android:protectionLevel="normal|instant" />
        │  系统启动时被 SystemConfig 读取
        ▼
frameworks/base/data/etc/platform.xml          ← 权限名 → GID 映射表
  <permission name="android.permission.INTERNET">
      <group gid="inet" />                     ← "inet" 这个名字
  </permission>
        │  Process.getGidForName("inet")
        ▼
AID_INET = 3003                                ← 数字 GID
        │  存入 Permission.mGids
        ▼
授权判定(normal → 安装即 granted)→ UidPermissionState.computeGids()
        │  getGidsForUid()
        ▼
进程启动 ProcessList.startProcess()
        │  pm.getPackageGids() → computeGidsForProcess()
        ▼
zygote fork + "--setgroups=...,3003,...""--setgid=..."
        │  native SpecializeCommon → setgroups(2)
        ▼
app 进程携带 supplementary group: inet(3003)
        │
        ▼
内核 inet_create() / inet6_create()
  current_has_network() = in_egroup_p(AID_INET) || capable(CAP_NET_RAW)
  不满足 → return -EACCES

其他更多framework系统车机收开发面试题,入门到精通学习资料:
Android Framework开发rom实战合集课表/车载车机手机高级系统开发工程必会技能
安卓系统性能优化高级实战开发专题发布啦
咨询报名课程的进入vip群讨论也可以联系马哥w浩:androidframework007
或者公众号私聊马哥

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料