JasonWang's Blog

Android中HAL服务无法使用网络的问题

字数统计: 1.5k阅读时长: 7 min
2023/03/09

最近有同学在Android S(12)上遇到了一个奇怪的网络问题,说自己的audio HAL服务尝试通过以太网创建socket与其他局域网的节点通讯时,总是提示Operation Not Permitted。原先怀疑是Selinux的问题,但是目前在开发版本中selinux是完全关闭的;从问题发生的现象看,只有属于audioserver这个UID的进程才有问题,其他的如system/root的进程则没有问题。

据此,我们可以推断,audioserver这个UID的进程没有相关权限,所以导致无法使用局域网的网络。记得在早前的Android版本中,很多网络系统调用会通过netd代理进行权限检查,比如socket/connect/bind等系统调用都会先通过netdClient这个库的接口进行权限检查,而后才真正进行系统调用。查看了Android Snetd/client中的代码,果真有一个socket的代理接口会检查权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

int netdClientSocket(int domain, int type, int protocol) {
// Block creating AF_INET/AF_INET6 socket if networking is not allowed.
if (FwmarkCommand::isSupportedFamily(domain) && !allowNetworkingForProcess.load()) {
errno = EPERM;
return -1;
}
int socketFd = libcSocket(domain, type, protocol);
if (socketFd == -1) {
return -1;
}
unsigned netId = netIdForProcess & ~NETID_USE_LOCAL_NAMESERVERS;
if (netId != NETID_UNSET && FwmarkClient::shouldSetFwmark(domain)) {
if (int error = setNetworkForSocket(netId, socketFd)) {
return closeFdAndSetErrno(socketFd, error);
}
}
return socketFd;
}

有关netd的详细介绍可以参考Android Netd工作原理详解

继续跟踪这个代码路径,发现AndroidZygote进程创建应用进程的时候会调用setAllowNetworkingForProcess接口,根据进程是否在一个INET_GID用户组来设置该进程是否有权限访问网络:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

// 进程是否包含了在INET对应的用户组
private static boolean containsInetGid(int[] gids) {
for (int i = 0; i < gids.length; i++) {
if (gids[i] == android.os.Process.INET_GID) return true;
}
return false;
}

static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
boolean isTopApp, String[] pkgDataInfoList, String[] allowlistedDataInfoList,
boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) {
ZygoteHooks.preFork();

int pid = nativeForkAndSpecialize(
uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
pkgDataInfoList, allowlistedDataInfoList, bindMountAppDataDirs,
bindMountAppStorageDirs);
if (pid == 0) {
// Note that this event ends at the end of handleChildProc,
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");

// If no GIDs were specified, don't make any permissions changes based on groups.
if (gids != null && gids.length > 0) {
NetworkUtilsInternal.setAllowNetworkingForProcess(containsInetGid(gids));
}
}

// Set the Java Language thread priority to the default value for new apps.
Thread.currentThread().setPriority(Thread.NORM_PRIORITY);

ZygoteHooks.postForkCommon();
return pid;
}

再次检查HAL服务的rc配置,发现group用户组中已经包含了inet了。而且,从问题的现象来看,是跟进程的UID有关,与GID的关系不大。那么,到底是哪里设置了UID相关的权限了? 跟同事一起确认发现,只要在/system/etc/permission/platform.xml中为audioserver添加一个INTERNET权限网络就可以正常访问了:

1
2
3

<assign-permission name="android.permission.INTERNET" uid="audioserver" />

问题好像比较清晰了,应该是系统启动时,对于那些无法通过AndroidManifest.xml声明权限的服务,比如native的服务,包管理服务PMS会去解析对应UID的权限,然后再通过某种方式设置到系统,确保其在创建socket时相关的权限会被检查。查看frameworks/base/services/../pm/permission代码可以大概知道,Android中的所有权限都统一由系统服务PermissionManagerService来管理,系统启动时,就会通过这个服务解析platform.xml文件中各个UID的权限声明:

1
2
3
4

SystemConfig systemConfig = SystemConfig.getInstance();
mSystemPermissions = systemConfig.getSystemPermissions();

但继续跟踪代码,却没有看到是这个权限配置是如何与socket的创建控制关联在一起的。这个想来十分奇怪,按照实现逻辑来说,这种权限控制不会放在内核实现,而是应该在用户空间的某个地方设置。过了两天再来看这个问题,突然想起之前做流量统计功能时,Android中使用了BPF,很有可能这个网络权限的控制也是在netd对应的BPF中实现的。查看了下/system/netd/bpf_progs/netd.c的代码,确实有一个bpf map来控制socket的创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

DEFINE_BPF_PROG_KVER("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create,
KVER(4, 14, 0))
(struct bpf_sock* sk) {
uint64_t gid_uid = bpf_get_current_uid_gid();
/*
* A given app is guaranteed to have the same app ID in all the profiles in
* which it is installed, and install permission is granted to app for all
* user at install time so we only check the appId part of a request uid at
* run time. See UserHandle#isSameApp for detail.
*/
uint32_t appId = (gid_uid & 0xffffffff) % PER_USER_RANGE;
uint8_t* permissions = bpf_uid_permission_map_lookup_elem(&appId);
if (!permissions) {
// UID not in map. Default to just INTERNET permission.
return 1;
}

// A return value of 1 means allow, everything else means deny.
return (*permissions & BPF_PERMISSION_INTERNET) == BPF_PERMISSION_INTERNET;
}

这个UID权限map对象正是通过Netd的接口进行设置的,大致的路径如下:

1
2
3
4
5

PermissionMonitor.startMoniotr -> (SystemServer)
NetdNativeService.trafficSetNetPermForUids --> (Netd)
TrafficeController.setPermissionForUids

就是说ConnectivityService在启动过程中,通过PermissionMonitor读取到SystemConfig的用户权限配置后,会调用NETD的接口设置对应的UID的网络权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

public synchronized void startMonitoring() {
log("Monitoring");

final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
intentFilter.addDataScheme("package");
userAllContext.registerReceiver(
mIntentReceiver, intentFilter, null /* broadcastPermission */,
null /* scheduler */);
...
List<PackageInfo> apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS
| MATCH_ANY_USER);
if (apps == null) {
loge("No apps");
return;
}

SparseIntArray netdPermsUids = new SparseIntArray();

for (PackageInfo app : apps) {
int uid = app.applicationInfo != null ? app.applicationInfo.uid : INVALID_UID;
if (uid < 0) {
continue;
}
mAllApps.add(UserHandle.getAppId(uid));

boolean isNetwork = hasNetworkPermission(app);
boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app);

if (isNetwork || hasRestrictedPermission) {
Boolean permission = mApps.get(UserHandle.getAppId(uid));
// If multiple packages share a UID (cf: android:sharedUserId) and ask for different
// permissions, don't downgrade (i.e., if it's already SYSTEM, leave it as is).
if (permission == null || permission == NETWORK) {
mApps.put(UserHandle.getAppId(uid), hasRestrictedPermission);
}
}

//TODO: unify the management of the permissions into one codepath.
int otherNetdPerms = getNetdPermissionMask(app.requestedPermissions,
app.requestedPermissionsFlags);
netdPermsUids.put(uid, netdPermsUids.get(uid) | otherNetdPerms);
}

mUsers.addAll(mUserManager.getUserHandles(true /* excludeDying */));

final SparseArray<String> netdPermToSystemPerm = new SparseArray<>();
netdPermToSystemPerm.put(INetd.PERMISSION_INTERNET, INTERNET);
netdPermToSystemPerm.put(INetd.PERMISSION_UPDATE_DEVICE_STATS, UPDATE_DEVICE_STATS);
for (int i = 0; i < netdPermToSystemPerm.size(); i++) {
final int netdPermission = netdPermToSystemPerm.keyAt(i);
final String systemPermission = netdPermToSystemPerm.valueAt(i);
//获取有INTERNET权限的UID
final int[] hasPermissionUids =
mSystemConfigManager.getSystemPermissionUids(systemPermission);
for (int j = 0; j < hasPermissionUids.length; j++) {
final int uid = hasPermissionUids[j];
netdPermsUids.put(uid, netdPermsUids.get(uid) | netdPermission);
}
}
log("Users: " + mUsers.size() + ", Apps: " + mApps.size());
update(mUsers, mApps, true);

//设置UID的网络权限
sendPackagePermissionsToNetd(netdPermsUids);
}

至此,谜底终于解开了。感兴趣的可以查看packages/modules/Connectivity/PermissionMonitor.java的代码。

原文作者:Jason Wang

更新日期:2023-03-13, 20:03:38

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG