我正在制作基于 AOSP Nougat 最新源的定制 bsp。
Android 服务进程要求服务管理器查找或添加服务。服务管理器尝试通过调用 svc_can_register() 或 svc_can_find() 来检查 mac 权限,后者调用 check_mac_perms(),后者调用 getpidcon()。
让我们看看 svc_can_find()
static int svc_can_find(const uint16_t *name, size_t name_len, pid_t spid, uid_t uid)
{
const char *perm = "find";
return check_mac_perms_from_lookup(spid, uid, perm, str8(name, name_len)) ? 1 : 0;
}
check_mac_perms_from_lookup() 是这样的:
static bool check_mac_perms_from_lookup(pid_t spid, uid_t uid, const char *perm, const char *name)
{
bool allowed;
char *tctx = NULL;
if (selinux_enabled <= 0) {
return true;
}
if (!sehandle) {
ALOGE("SELinux: Failed to find sehandle. Aborting service_manager.\n");
abort();
}
if (selabel_lookup(sehandle, &tctx, name, 0) != 0) {
ALOGE("SELinux: No match for %s in service_contexts.\n", name);
return false;
}
allowed = check_mac_perms(spid, uid, tctx, perm, name);
freecon(tctx);
return allowed;
}
它调用 check_mac_perms()。check_mac_perms() 像这样:
static bool check_mac_perms(pid_t spid, uid_t uid, const char *tctx, const char *perm, const char *name)
{
char *sctx = NULL;
const char *class = "service_manager";
bool allowed;
struct audit_data ad;
if (getpidcon(spid, &sctx) < 0) {
ALOGE("SELinux: getpidcon(pid=%d) failed to retrieve pid context.\n", spid);
return false;
}
ad.pid = spid;
ad.uid = uid;
ad.name = name;
int result = selinux_check_access(sctx, tctx, class, perm, (void *) &ad);
allowed = (result == 0);
freecon(sctx);
return allowed;
}
它调用 getpidcon()。getpidcon() 在 external/selinux/libselinux/src/procattr.c 中定义
getpidcon() 定义如下:
#define getpidattr_def(fn, attr) \
int get##fn(pid_t pid, char **c) \
{ \
if (pid <= 0) { \
errno = EINVAL; \
return -1; \
} else { \
return getprocattrcon(c, pid, #attr); \
} \
}
...
...
getpidattr_def(pidcon, current)
"getpidattr_def(pidcon, current)" 扩展为 getpidcon() 函数定义并调用 getprocatrcon()
getprocattrcon() 是这样的:
static int getprocattrcon(char ** context,
pid_t pid, const char *attr)
{
char *buf;
size_t size;
int fd;
ssize_t ret;
int errno_hold;
fd = openattr(pid, attr, O_RDONLY);
if (fd < 0)
return -1;
size = selinux_page_size;
buf = malloc(size);
if (!buf) {
ret = -1;
goto out;
}
memset(buf, 0, size);
do {
ret = read(fd, buf, size - 1);
} while (ret < 0 && errno == EINTR);
if (ret < 0)
goto out2;
if (ret == 0) {
*context = NULL;
goto out2;
}
*context = strdup(buf);
if (!(*context)) {
ret = -1;
goto out2;
}
ret = 0;
out2:
free(buf);
out:
errno_hold = errno;
close(fd);
errno = errno_hold;
return ret;
}
很简单吧?只需打开一些文件并读取内容并通过函数参数返回。
它在 openattr() 处失败。我已经通过在 openattr() 中插入一些日志函数来确认这一点。openattr() 也是一个简单的函数。
static int openattr(pid_t pid, const char *attr, int flags)
{
int fd, rc;
char *path;
pid_t tid;
if (pid > 0) {
rc = asprintf(&path, "/proc/%d/attr/%s", pid, attr);
} else if (pid == 0) {
rc = asprintf(&path, "/proc/thread-self/attr/%s", attr);
if (rc < 0)
return -1;
fd = open(path, flags | O_CLOEXEC);
if (fd >= 0 || errno != ENOENT)
goto out;
free(path);
tid = gettid();
rc = asprintf(&path, "/proc/self/task/%d/attr/%s", tid, attr);
} else {
errno = EINVAL;
return -1;
}
if (rc < 0)
return -1;
fd = open(path, flags | O_CLOEXEC);
out:
free(path);
return fd;
}
失败点是“fd = open(path, flags | O_CLOEXEC);”
即使文件存在,几乎总是打开失败。我不明白这一点,想知道是什么导致了问题。我通过插入一些日志打印代码、检查 android log(adb logcat) 并从 android shell(adb shell) 读取文件来确认失败,例如“cat /proc/412/attr/current”。'cat ...' 读取成功,但日志显示打开文件失败。奇怪的是,如果 'pid' 为 0,它就成功了。
如果打开失败,将无法启动服务,导致系统无法正常启动。如果我忽略失败并从 getpidcon() 返回成功,系统会正确启动,但这显然不是正确的做法。
我正在将 bsp 测试为 selinux 许可模式。
谁能有我这样的经历?如果有人,请分享经验和问题的解决方案。
谢谢你。李三永。