SEAndroid的中心理念是,即使root权限被篡夺,只求阻止应用的恶意行为。
bionic 作为第一个分析的模块,在此之前,先简单叙述一下SEAndroid,便于后续理解。
SEAndroid 与 Android 的区别
SEAndroid 如何管控程序
一句话概括:程序向SEAndroid(确切的说是SELinux)发送请求,SEAndroid根据策略数据库进行策略分析,比对安全上下文,控制应用程序的资源存取。
SEAndroid bionic
SEAndroid在架构和机制上与SELinux完全一样,考虑到移动设备的特点,所以移植到SEAndroid的只是SELinux的一个子集。
1.增加系统调用
对比Android,SEAndroid bionic下增加了12个 system call :
int fsetxattr(int, const char *, const void *, size_t, int)
ssize_t fgetxattr(int, const char *, void *, size_t)
ssize_t flistxattr(int, char *, size_t)
int fremovexattr(int, const char *)
int setxattr(const char *, const char *, const void *, size_t, int)
int lsetxattr(const char *, const char *, const void *, size_t, int)
ssize_t getxattr(const char *, const char *, void *, size_t)
ssize_t lgetxattr(const char *, const char *, void *, size_t)
ssize_t listxattr(const char *, char *, size_t)
ssize_t llistxattr(const char *, char *, size_t)
int removexattr(const char *, const char *)
int lremovexattr(const char *, const char *)
2.linker增加AT_SECURE
SELinux为加载的应用程序设置AT_SECURE auxv 标志,具体在bionic/linker/linker.c:
/* Initialize environment functions, and get to the ELF aux vectors table */
vecs = linker_env_init(vecs);
/* Check auxv for AT_SECURE first to see if program is setuid, setgid,
has file caps, or caused a SELinux/AppArmor domain transition. */
for (v = vecs; v[0]; v += 2) {
if (v[0] == AT_SECURE) {
/* kernel told us whether to enable secure mode */
program_is_setuid = v[1];
goto sanitize;
}
}
/* Kernel did not provide AT_SECURE - fall back on legacy test. */
program_is_setuid = (getuid() != geteuid()) || (getgid() != getegid());
综上所述,bionic差异并不大,主要是AT_SECURE的加入, SELinux水很深,推荐鸟哥的文章一读:
鳥哥的 Linux 私房菜-第十七章、程序管理與 SELinux 初探
在Android系统中,所有的应用程序进程,以及系统服务进程SystemServer都是由Zygote孕育fork出来的。 Zygote的native获取主要研究dalvik/vm/native/dalvik_system_Zygote.cpp,SEAndroid管控应用程序资源存取权限,对于整个dalvik,也正是在此动的手脚。
首先看抛出的DalvikNativeMethod dvm_dalvik_system_Zygote,与原生Android相比,SEAndroid 在 nativeForkAndSpecialize 增加传入了两个String类型的参数:
const DalvikNativeMethod dvm_dalvik_system_Zygote[] = {
{"nativeFork", "()I",
Dalvik_dalvik_system_Zygote_fork },
{ "nativeForkAndSpecialize", "(II[II[[ILjava/lang/String;Ljava/lang/String;)I",
Dalvik_dalvik_system_Zygote_forkAndSpecialize },
{ "nativeForkSystemServer", "(II[II[[IJJ)I",
Dalvik_dalvik_system_Zygote_forkSystemServer },
{ "nativeExecShell", "(Ljava/lang/String;)V",
Dalvik_dalvik_system_Zygote_execShell },
{ NULL, NULL, NULL },
}
那么这两个参数是什么呢?继续追一下forkAndSpecialize。
/* native public static int forkAndSpecialize(int uid, int gid,
* int[] gids, int debugFlags, String seInfo, String niceName);
*/
static void Dalvik_dalvik_system_Zygote_forkAndSpecialize(const u4* args,
JValue* pResult)
{
pid_t pid;
pid = forkAndSpecializeCommon(args, false);
RETURN_INT(pid);
}
可以看到,增加传入的2个参数一个是seInfo,用于定义新进程的SEAndroid信息,一个是niceName,用于定义新进程名。
在static pid_t forkAndSpecializeCommon(const u4* args, bool isSystemServer)中,其中SEAndroid加入了设置SELinux安全上下文代码段,seInfo和niceName:
#ifdef HAVE_SELINUX
err = setSELinuxContext(uid, isSystemServer, seInfo, niceName);
if (err < 0) {
LOGE("cannot set SELinux context: %s\n", strerror(errno));
dvmAbort();
}
free(seInfo);
free(niceName);
#endif
其中设置SELinux安全上下文方法实现:
#ifdef HAVE_SELINUX
/*
* Set SELinux security context.
*
* Returns 0 on success, -1 on failure.
*/
static int setSELinuxContext(uid_t uid, bool isSystemServer,
const char *seInfo, const char *niceName)
{
#ifdef HAVE_ANDROID_OS
return selinux_android_setcontext(uid, isSystemServer, seInfo, niceName);
#else
return 0;
#endif
}
#endif
再往上一层就到了libcore/dalvik/src/main/java/dalvik/system/Zygote.java ,Zygote类的封装,对应forkAndSpecialize方法中添加seInfo和niceName参数传递。
public class Zygote {
...
public static int forkAndSpecialize(int uid, int gid, int[] gids,
int debugFlags, int[][] rlimits, String seInfo, String niceName) {
preFork();
int pid = nativeForkAndSpecialize(uid, gid, gids, debugFlags, rlimits, seInfo, niceName);
postFork();
return pid;
}
native public static int nativeForkAndSpecialize(int uid, int gid,
int[] gids, int debugFlags, int[][] rlimits, String seInfo, String niceName);
/**
* Forks a new VM instance.
* @deprecated use {@link Zygote#forkAndSpecialize(int, int, int[], int, int[][])}
*/
@Deprecated
public static int forkAndSpecialize(int uid, int gid, int[] gids,
boolean enableDebugger, int[][] rlimits) {
int debugFlags = enableDebugger ? DEBUG_ENABLE_DEBUGGER : 0;
return forkAndSpecialize(uid, gid, gids, debugFlags, rlimits, null, null);
}
...
}
Android应用程序启动流程不再赘述,当建立了ZygoteConnection对象用于socket连接后,接下来就是调用ZygoteConnection.runOnce函数进一步处理了。
源码位置:frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java,其中,SEAndroid增加zygote安全策略函数,在runOnce中调用。
/**
* Applies zygote security policy.
* Based on the credentials of the process issuing a zygote command:
* <ol>
* <li> uid 0 (root) may specify --invoke-with to launch Zygote with a
* wrapper command.
* <li> Any other uid may not specify any invoke-with argument.
* </ul>
*
* @param args non-null; zygote spawner arguments
* @param peer non-null; peer credentials
* @throws ZygoteSecurityException
*/
private static void applyInvokeWithSecurityPolicy(Arguments args, Credentials peer,
String peerSecurityContext)
throws ZygoteSecurityException {
int peerUid = peer.getUid();
if (args.invokeWith != null && peerUid != 0) {
throw new ZygoteSecurityException("Peer is not permitted to specify "
+ "an explicit invoke-with wrapper command");
}
if (args.invokeWith != null) {
boolean allowed = SELinux.checkSELinuxAccess(peerSecurityContext,
peerSecurityContext,
"zygote",
"specifyinvokewith");
if (!allowed) {
throw new ZygoteSecurityException("Peer is not permitted to specify "
+ "an explicit invoke-with wrapper command");
}
}
}
/**
* Applies zygote security policy for SEAndroid information.
*
* @param args non-null; zygote spawner arguments
* @param peer non-null; peer credentials
* @throws ZygoteSecurityException
*/
private static void applyseInfoSecurityPolicy(
Arguments args, Credentials peer, String peerSecurityContext)
throws ZygoteSecurityException {
int peerUid = peer.getUid();
if (args.seInfo == null) {
// nothing to check
return;
}
if (!(peerUid == 0 || peerUid == Process.SYSTEM_UID)) {
// All peers with UID other than root or SYSTEM_UID
throw new ZygoteSecurityException(
"This UID may not specify SEAndroid info.");
}
boolean allowed = SELinux.checkSELinuxAccess(peerSecurityContext,
peerSecurityContext,
"zygote",
"specifyseinfo");
if (!allowed) {
throw new ZygoteSecurityException(
"Peer may not specify SEAndroid info");
}
return;
}
理所当然的,在启动一个新的进程时,frameworks/base/core/java/android/os/Process.java中也会加入SEAndroid信息seInfo。