有时候bug会推着你走,有些代码你不想现在分析是不行的,今天晚上就分析Appops相关权限管理的逻辑 AppopsService启动的代码如下
mAppOpsService = new AppOpsService(new File(systemDir, "appops.xml"), mHandler); mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_IN_BACKGROUND, null, new IAppOpsCallback.Stub() { @Override public void opChanged(int op, int uid, String packageName) { if (op == AppOpsManager.OP_RUN_IN_BACKGROUND && packageName != null) { if (mAppOpsService.checkOperation(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) { runInBackgroundDisabled(uid); } } } }); public AppOpsService(File storagePath, Handler handler) { mFile = new AtomicFile(storagePath); mHandler = handler; readState(); }我们先看构造函数,文件appops.xml为/data/system/appops.xml,然后Handler传入的是AMS的MainHandler,构造函数很简单,这里我们用屁股想也能知道readState()函数用于读取appops.xml持久的信息。 appops.xml文件格式大概如下
<?xml version='1.0' encoding='utf-8' standalone='yes' ?> <app-ops> <uid n="1001"> <op n="15" m="0" /> </uid> <pkg n="android"> <uid n="1000" p="true"> <op n="0" t="1525267751533" pu="0" /> <op n="3" t="1525267746272" d="1" /> <op n="4" t="1494632062921" pu="0" pp="com.android.providers.contacts" /> <op n="8" t="1525267751265" pu="0" pp="com.android.providers.calendar" /> <op n="11" t="1515770357293" pu="0" /> <op n="24" r="1523105890800" /> <op n="35" t="1496459511009" pu="0" /> <op n="40" t="1525267756904" d="9522" /> <op n="51" t="1494634611451" pu="0" /> <op n="54" t="1494634604481" pu="0" /> <op n="61" t="1525095671919" pu="0" /> <op n="62" t="1525267748446" pu="0" /> <op n="63" t="1525267755298" pu="0" /> </uid> </pkg> </app-ops>最外层是app-ops标签,里面一层是pkg,再向里面是uid,最里面是op,代表一种操作,具体的n和t我也不清楚我们之后分析清除。 同时也包含那种uid不在pkg中的情况。
void readState() { synchronized (mFile) { synchronized (this) { //1 打开文件 FileInputStream stream; try { stream = mFile.openRead(); } catch (FileNotFoundException e) { Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty"); return; } boolean success = false; mUidStates.clear(); try { XmlPullParser parser = Xml.newPullParser(); parser.setInput(stream, StandardCharsets.UTF_8.name()); int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { ; } if (type != XmlPullParser.START_TAG) { throw new IllegalStateException("no start tag found"); } int outerDepth = parser.getDepth(); 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("pkg")) { //2 解析package数据 readPackage(parser); } else if (tagName.equals("uid")) { //3解析uid数据 readUidOps(parser); } else { Slog.w(TAG, "Unknown element under <app-ops>: " + parser.getName()); XmlUtils.skipCurrentTag(parser); } } success = true; } catch (IllegalStateException e) { Slog.w(TAG, "Failed parsing " + e); } catch (NullPointerException e) { Slog.w(TAG, "Failed parsing " + e); } catch (NumberFormatException e) { Slog.w(TAG, "Failed parsing " + e); } catch (XmlPullParserException e) { Slog.w(TAG, "Failed parsing " + e); } catch (IOException e) { Slog.w(TAG, "Failed parsing " + e); } catch (IndexOutOfBoundsException e) { Slog.w(TAG, "Failed parsing " + e); } finally { if (!success) { mUidStates.clear(); } try { stream.close(); } catch (IOException e) { } } } } }这个函数很简单,但是try catch很多看起来很虎人,主要的函数就是readPackage(parser)和readUidOps(parser)函数,由于readUidOps函数比较简单不存在嵌套的情况,我们先分析这个函数
void readUidOps(XmlPullParser parser) throws NumberFormatException, XmlPullParserException, IOException { //1 读取n节点,也就是name 代表uid final int uid = Integer.parseInt(parser.getAttributeValue(null, "n")); int outerDepth = parser.getDepth(); int type; //2 下面读取uid下面的节点 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("op")) { //3 解析op节点 final int code = Integer.parseInt(parser.getAttributeValue(null, "n")); final int mode = Integer.parseInt(parser.getAttributeValue(null, "m")); UidState uidState = getUidStateLocked(uid, true); if (uidState.opModes == null) { uidState.opModes = new SparseIntArray(); } uidState.opModes.put(code, mode); } else { //4 未知节点直接跳过 Slog.w(TAG, "Unknown element under <uid-ops>: " + parser.getName()); XmlUtils.skipCurrentTag(parser); } } }这里主要是创建一个UidState。然后创建opModes,之后将code和mode关系保存在opModes中。
readPackage在外层我们先分析
void readPackage(XmlPullParser parser) throws NumberFormatException, XmlPullParserException, IOException { //1 n代表name ,也就是报名 String pkgName = parser.getAttributeValue(null, "n"); 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 (tagName.equals("uid")) { //2 解析uid相关数据,这里是uid在pkg内的情况 readUid(parser, pkgName); } else { Slog.w(TAG, "Unknown element under <pkg>: " + parser.getName()); XmlUtils.skipCurrentTag(parser); } } }从上面看解释pkg只是解析package name,然后解析uid的数据。
void readUid(XmlPullParser parser, String pkgName) throws NumberFormatException, XmlPullParserException, IOException { //1 n代表uid int uid = Integer.parseInt(parser.getAttributeValue(null, "n")); //2 p代表是否预装在/system/priv-app/下 String isPrivilegedString = parser.getAttributeValue(null, "p"); boolean isPrivileged = false; //3 如果xml里面没有写这个pkg是否是priv-app就使用PMS查询,最终确定isPrivileged变量 if (isPrivilegedString == null) { try { IPackageManager packageManager = ActivityThread.getPackageManager(); if (packageManager != null) { ApplicationInfo appInfo = ActivityThread.getPackageManager() .getApplicationInfo(pkgName, 0, UserHandle.getUserId(uid)); if (appInfo != null) { isPrivileged = (appInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; } } else { // Could not load data, don't add to cache so it will be loaded later. return; } } catch (RemoteException e) { Slog.w(TAG, "Could not contact PackageManager", e); } } else { isPrivileged = Boolean.parseBoolean(isPrivilegedString); } 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; } //4解析op标签 String tagName = parser.getName(); if (tagName.equals("op")) { //5 创建op数据结构,这里可以看到op包含的数据结构有uid,报名,name,我们在xml中看到op的name全都是数字,从Integer.parseInt来看也是这么实现的 Op op = new Op(uid, pkgName, Integer.parseInt(parser.getAttributeValue(null, "n"))); String mode = parser.getAttributeValue(null, "m"); //6 设置op的mode if (mode != null) { op.mode = Integer.parseInt(mode); } //7 设置时间 String time = parser.getAttributeValue(null, "t"); if (time != null) { op.time = Long.parseLong(time); } //8 获取拒绝时间 time = parser.getAttributeValue(null, "r"); if (time != null) { op.rejectTime = Long.parseLong(time); } //9 获取间隔 String dur = parser.getAttributeValue(null, "d"); if (dur != null) { op.duration = Integer.parseInt(dur); } //10代理uid String proxyUid = parser.getAttributeValue(null, "pu"); if (proxyUid != null) { op.proxyUid = Integer.parseInt(proxyUid); } //11 代理报名 String proxyPackageName = parser.getAttributeValue(null, "pp"); if (proxyPackageName != null) { op.proxyPackageName = proxyPackageName; } //12 这里和readUidOps函数是一样的,创建UidState和pkgOps集合 UidState uidState = getUidStateLocked(uid, true); if (uidState.pkgOps == null) { uidState.pkgOps = new ArrayMap<>(); } //13 创建Ops数据结构,添加到uidState Ops ops = uidState.pkgOps.get(pkgName); if (ops == null) { ops = new Ops(pkgName, uidState, isPrivileged); uidState.pkgOps.put(pkgName, ops); } //14添加Op到ops中 ops.put(op.op, op); } else { Slog.w(TAG, "Unknown element under <pkg>: " + parser.getName()); XmlUtils.skipCurrentTag(parser); } } }从这里来看虽然有些字段的含义我们还不清楚,但是我们已经知道了Ops大概的数据结构。大概分为三个级别 1 SparseArray mUidStates 用于存放uid和对应的uid下的状态,用UidState变量代表 2 UidState,一个uid对应一个UidState,然而一个uid可以对应多个package, 每个package下面都有一个Ops代表一组操作 3 每个Ops下又有多个op 另外每个UidState下还有一组opModes,分别保存code和mode的对应管理,我这里还没有完全理解。不过这个值来自于非pkg下的uid
到这里就分析完成了解析,和数据结构的组织。 我们来线下AppOps如何检查权限
我们以AppOpsManager类的checkOp和checkOpNoThrow为例子说明AppOps的使用
public int checkOp(String op, int uid, String packageName) { return checkOp(strOpToOp(op), uid, packageName); } public int checkOpNoThrow(String op, int uid, String packageName) { return checkOpNoThrow(strOpToOp(op), uid, packageName); } public int checkOp(int op, int uid, String packageName) { try { int mode = mService.checkOperation(op, uid, packageName); if (mode == MODE_ERRORED) { throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName)); } return mode; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } public int checkOpNoThrow(int op, int uid, String packageName) { try { return mService.checkOperation(op, uid, packageName); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }还是很简单一个抛出安全异常,一个不抛出, 看到这里我们似乎有了一些了解,看strOpToOp(op)函数,是把一个字符串转换成一个整形,也就是对应的op code, 这里我们就明白了,前面解析xml中的name,原来就是这个code.
public static int strOpToOp(String op) { Integer val = sOpStrToOp.get(op); if (val == null) { throw new IllegalArgumentException("Unknown operation string: " + op); } return val; } for (int i=0; i<_NUM_OP; i++) { if (sOpToString[i] != null) { sOpStrToOp.put(sOpToString[i], i); } } private static String[] sOpToString = new String[] { OPSTR_COARSE_LOCATION, OPSTR_FINE_LOCATION, null, null, ...... }sOpStrToOp是使用sOpToString所维护的字符串作为key,数组下标作为value去维护关系,所以这里的code就是数组下表,看来要加AppOps还要注意和google的兼容性 其实都是使用AppOpsService的checkOperation函数来检查的,
@Override public int checkOperation(int code, int uid, String packageName) { //1 检查uid是否具有UPDATE_APP_OPS_STATS权限 verifyIncomingUid(uid); //2 检查code是否合理 verifyIncomingOp(code); //3 根据uid和报名获取包名 String resolvedPackageName = resolvePackageName(uid, packageName); if (resolvedPackageName == null) { return AppOpsManager.MODE_IGNORED; } synchronized (this) { //4 检查user多用户相关的权限 if (isOpRestrictedLocked(uid, code, resolvedPackageName)) { return AppOpsManager.MODE_IGNORED; } //5 根据code转换为op code,这里又进行了一个转换的操作 code = AppOpsManager.opToSwitch(code); //6 获取UidState UidState uidState = getUidStateLocked(uid, false); if (uidState != null && uidState.opModes != null) { //7 检查opModes下维护的code权限 final int uidMode = uidState.opModes.get(code); if (uidMode != AppOpsManager.MODE_ALLOWED) { return uidMode; } } //8 获取pkg下的Op Op op = getOpLocked(code, uid, resolvedPackageName, false); if (op == null) { //9 op不存在使用默认规则 return AppOpsManager.opToDefaultMode(code); } //9op 存在返回op下的mode return op.mode; } } public static int opToSwitch(int op) { return sOpToSwitch[op]; } private static int[] sOpToSwitch = new int[] { OP_COARSE_LOCATION, OP_COARSE_LOCATION, OP_COARSE_LOCATION, OP_VIBRATE, OP_READ_CONTACTS, OP_WRITE_CONTACTS, OP_READ_CALL_LOG, OP_WRITE_CALL_LOG, OP_READ_CALENDAR, OP_WRITE_CALENDAR, .... }函数很简单,使用注释中的九个步骤去判断op code对应的授权模式。 1,2,3 三个步骤都是检查参数和调用者的合理性,这里需要说明的是如果resolvedPackageName不存在的时候返回AppOpsManager.MODE_IGNORED,看来这就是一种op对应的mode,这里对mode做一下说明
//1 操作允许 public static final int MODE_ALLOWED = 0; //操作不允许,但是checkOp不会抛出安全异常,表示忽略 public static final int MODE_IGNORED = 1; //3 表示操作不允许,checkOp操作会抛出安全异常 public static final int MODE_ERRORED = 2; //4 默认行为,可能进一步检查权限 public static final int MODE_DEFAULT = 3;
步骤4检查多用户相关的授权,我们先放一下再来分析 步骤5根据code转换为op code,这里又进行了一个转换的操作,对于这里的转换,注释里面说的比较清楚,就是多个code可能对应同一个op code,所以这里要进行一次转换。大多数时候code都是和opcode相同的。 步骤7 首先检查uid下的opModes,我们在分析读取appops.xml文件的时候,有时候一个uid标签是不在pkg内部的,这种uid标签下申明的mode,优先级要高于pkg下的op,所以在这里先去判断uid的op code 对应的mode. 步骤8 则是当uid下的规则不存在,继而检查pkg下的op 步骤9是如果没有指定规则,则采用默认规则,opToDefaultMode函数也是使用查表的方式确定默认规则,表则AppOpsManager类的sOpDefaultMode变量中维护。
private static int[] sOpDefaultMode = new int[] { AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_ALLOWED, ...}下面在分析几个AppOpsManager类下的函数
public int checkPackage(int uid, String packageName)这个方法会在uid下创建对应的uidState和一个Ops返回给用户,创建成功返回说明uid和packagename是对应的上的,返回AppOpsManager.MODE_ALLOWED,否则返回AppOpsManager.MODE_ERRORED
public int noteOp(int op, int uid, String packageName)noteOp 函数比checkOp函数额外多出的功能就是会创建对应的op,另外会更新一些op操作的时间用于统计信息
public int startOp(int op, int uid, String packageName)表示一个长时间运行的操作,比noteOp增加了一个统计信息,放在一个叫starting的集合里可以dump到,调用finishOp结束操作。
AppOps的另外一个功能是针对多用户的授权检查
@Override public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle, String[] exceptionPackages) { //1 不是针对自己进程的首先规则,要检查MANAGE_APP_OPS_RESTRICTIONS权限 if (Binder.getCallingPid() != Process.myPid()) { mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS, Binder.getCallingPid(), Binder.getCallingUid(), null); } //2 跨用户的调用检查INTERACT_ACROSS_USERS_FULL 权限 if (userHandle != UserHandle.getCallingUserId()) { if (mContext.checkCallingOrSelfPermission(Manifest.permission .INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission(Manifest.permission .INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or" + " INTERACT_ACROSS_USERS to interact cross user "); } } //3 检查code合理性 verifyIncomingOp(code); Preconditions.checkNotNull(token); setUserRestrictionNoCheck(code, restricted, token, userHandle, exceptionPackages); }函数很简单,做了一系列检查后进入setUserRestrictionNoCheck真正的创先相应的受限规则
private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token, int userHandle, String[] exceptionPackages) { boolean notifyChange = false; //1 获取token所对应的ClientRestrictionState,这代表一个用户应用进程,用户应用进程会发布一些针对某些user的限制 synchronized (AppOpsService.this) { ClientRestrictionState restrictionState = mOpUserRestrictions.get(token); if (restrictionState == null) { try { restrictionState = new ClientRestrictionState(token); } catch (RemoteException e) { return; } //2 不存在的情况创建,放入mOpUserRestrictions集合 mOpUserRestrictions.put(token, restrictionState); } //3给userHandle添加一个受限规则,其中code为op,restricted为是否收到限制,真为是,exceptionPackages //是指白名单的包,这些包属于userHandle这个用户。 if (restrictionState.setRestriction(code, restricted, exceptionPackages, userHandle)) { notifyChange = true; } //4 没有任何授权需要创建,直接销毁 if (restrictionState.isDefault()) { mOpUserRestrictions.remove(token); restrictionState.destroy(); } } if (notifyChange) { //5 通知监听者 notifyWatchersOfChange(code); } } public boolean setRestriction(int code, boolean restricted, String[] excludedPackages, int userId) { boolean changed = false; //1 创建user数据结构,维护每个user的授权情况 if (perUserRestrictions == null && restricted) { perUserRestrictions = new SparseArray<>(); } if (perUserRestrictions != null) { boolean[] userRestrictions = perUserRestrictions.get(userId); if (userRestrictions == null && restricted) { //2创建每种权限的受限情况 userRestrictions = new boolean[AppOpsManager._NUM_OP]; perUserRestrictions.put(userId, userRestrictions); } //3 如果设置完成后,对所有op的操作都是不受限制的,则清除该限制的数据结构 if (userRestrictions != null && userRestrictions[code] != restricted) { userRestrictions[code] = restricted; if (!restricted && isDefault(userRestrictions)) { perUserRestrictions.remove(userId); userRestrictions = null; } changed = true; } //4 创建excludedPackages相关信息 if (userRestrictions != null) { final boolean noExcludedPackages = ArrayUtils.isEmpty(excludedPackages); if (perUserExcludedPackages == null && !noExcludedPackages) { perUserExcludedPackages = new SparseArray<>(); } if (perUserExcludedPackages != null && !Arrays.equals(excludedPackages, perUserExcludedPackages.get(userId))) { if (noExcludedPackages) { perUserExcludedPackages.remove(userId); if (perUserExcludedPackages.size() <= 0) { perUserExcludedPackages = null; } } else { perUserExcludedPackages.put(userId, excludedPackages); } changed = true; } } } return changed; }这个api的作用就是限制一个user去做某件事情,举个例子,在
public class OverlayTouchActivity extends Activity { private final IBinder mToken = new Binder(); @Override protected void onResume() { super.onResume(); setOverlayAllowed(false); } @Override protected void onPause() { super.onPause(); setOverlayAllowed(true); } private void setOverlayAllowed(boolean allowed) { AppOpsManager appOpsManager = getSystemService(AppOpsManager.class); if (appOpsManager != null) { appOpsManager.setUserRestriction(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, !allowed, mToken); appOpsManager.setUserRestriction(AppOpsManager.OP_TOAST_WINDOW, !allowed, mToken); } } }在进入OverlayTouchActivity这个页面时候就不允许这个用户弹出window,除非来自那些Privileged的包. 检查函数如下 “` private boolean isOpRestrictedLocked(int uid, int code, String packageName) { int userHandle = UserHandle.getUserId(uid); final int restrictionSetCount = mOpUserRestrictions.size();
for (int i = 0; i < restrictionSetCount; i++) { // For each client, check that the given op is not restricted, or that the given // package is exempt from the restriction. ClientRestrictionState restrictionState = mOpUserRestrictions.valueAt(i); if (restrictionState.hasRestriction(code, packageName, userHandle)) { if (AppOpsManager.opAllowSystemBypassRestriction(code)) { // If we are the system, bypass user restrictions for certain codes synchronized (this) { Ops ops = getOpsRawLocked(uid, packageName, true); if ((ops != null) && ops.isPrivileged) { return false; } } } return true; } } return false; } ```AppOpsManager.opAllowSystemBypassRestriction(code)h函数用于判断有一些code可以对Privileged开发的
