背景

android 11版本(api level 30)上新增了隐私查看api。运用运用相应的api来查看自己的运用中是否有超出预料外的隐私访问(比方接入的第三方sdk有未奉告的隐私访问行为)。

api阐明详见谷歌官方文档:

developer.android.google.cn/guide/topic…

从文档中咱们能够得知,相应的功用完成是在AppOpsManager完成的。在这篇文档中咱们将讨论AppOpsManager在这块代码上的更新。

以下代码剖析根据Android 10和11版本源码。关于android 11,个人在本地运用的是lineageOS 18.1的源码,或许和aosp略有出入。

首要结构

AppOps在android 4.3版本就已经引进,关于运用开发者来说一向不可见。Ops,为Opereations的简写,意为“运用的操作、行为”。在android 6.0引进运行时权限之前,AppOps机制一向作为隐藏权限办理机制存在,国内各大手机厂商定制rom基本都会根据AppOps进行二次开发,以完成自己的权限办理。AppOps中界说的“行为”是“权限”的超集,所以这篇文章我将多用“行为办理”而不是“权限办理”来描述AppOps的操作。

AppOpsService完成了首要的行为校验机制。和其他所有的相似服务相同,在开机时发动并注入到体系服务会集。AppOpsManager首要完成了对外的接口,可供开发者及其他体系服务调用。

流程剖析

咱们以比较简单的Clipboard(剪贴板)服务调用来作为示例进行剖析。

运用获取到ClipboardManager之后,经过ClipboardManager.getPrimaryClip接口获取粘帖板中信息。ClipboardManager经过绑定的信息获取调用者的packageName和uid,然后调用ClipboardService.getPrimaryClip办法。

在ClipboardService.getPrimaryClip办法中,能够看到调用了clipboardAccessAllowed的办法。这个办法便是验证权限和行为的办法。在这儿我把要害代码展示一下:

  private boolean clipboardAccessAllowed(int op, String callingPackage, int uid,
            @UserIdInt int userId, boolean shouldNoteOp) {
        boolean allowed = false;
        // First, verify package ownership to ensure use below is safe.
        mAppOps.checkPackage(uid, callingPackage);
        // Shell can access the clipboard for testing purposes.
        if (mPm.checkPermission(android.Manifest.permission.READ_CLIPBOARD_IN_BACKGROUND,
                    callingPackage) == PackageManager.PERMISSION_GRANTED) {
            allowed = true;
        }
        // The default IME is always allowed to access the clipboard.
        String defaultIme = Settings.Secure.getStringForUser(getContext().getContentResolver(),
                Settings.Secure.DEFAULT_INPUT_METHOD, userId);
        if (!TextUtils.isEmpty(defaultIme)) {
            final String imePkg = ComponentName.unflattenFromString(defaultIme).getPackageName();
            if (imePkg.equals(callingPackage)) {
                allowed = true;
            }
        }
        switch (op) {
            case AppOpsManager.OP_READ_CLIPBOARD:
                // Clipboard can only be read by applications with focus..
                // or the application have the INTERNAL_SYSTEM_WINDOW and INTERACT_ACROSS_USERS_FULL
                // at the same time. e.x. SystemUI. It needs to check the window focus of
                // Binder.getCallingUid(). Without checking, the user X can't copy any thing from
                // INTERNAL_SYSTEM_WINDOW to the other applications.
                if (!allowed) {
                    allowed = mWm.isUidFocused(uid)
                            || isInternalSysWindowAppWithWindowFocus(callingPackage);
                }
                if (!allowed && mContentCaptureInternal != null) {
                    // ...or the Content Capture Service
                    // The uid parameter of mContentCaptureInternal.isContentCaptureServiceForUser
                    // is used to check if the uid has the permission BIND_CONTENT_CAPTURE_SERVICE.
                    // if the application has the permission, let it to access user's clipboard.
                    // To passed synthesized uid user#10_app#systemui may not tell the real uid.
                    // userId must pass intending userId. i.e. user#10.
                    allowed = mContentCaptureInternal.isContentCaptureServiceForUser(uid, userId);
                }
                if (!allowed && mAutofillInternal != null) {
                    // ...or the Augmented Autofill Service
                    // The uid parameter of mAutofillInternal.isAugmentedAutofillServiceForUser
                    // is used to check if the uid has the permission BIND_AUTOFILL_SERVICE.
                    // if the application has the permission, let it to access user's clipboard.
                    // To passed synthesized uid user#10_app#systemui may not tell the real uid.
                    // userId must pass intending userId. i.e. user#10.
                    allowed = mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId);
                }
                break;
            case AppOpsManager.OP_WRITE_CLIPBOARD:
                // Writing is allowed without focus.
                allowed = true;
                break;
            default:
                throw new IllegalArgumentException("Unknown clipboard appop " + op);
        }
        if (!allowed) {
            Slog.e(TAG, "Denying clipboard access to " + callingPackage
                    + ", application is not in focus nor is it a system service for "
                    + "user " + userId);
            return false;
        }
        // Finally, check the app op.
        int appOpsResult;
        if (shouldNoteOp) {
            appOpsResult = mAppOps.noteOp(op, uid, callingPackage);
        } else {
            appOpsResult = mAppOps.checkOp(op, uid, callingPackage);
        }
        return appOpsResult == AppOpsManager.MODE_ALLOWED;
    }

首先咱们看到调用了mAppOps.checkPackage(uid, callingPackage);,这儿首要是为了保证调用者的包名和uid是归于同一个运用的。个人了解首要是为了躲避跨运用的攻击行为,增强安全性。

然后是调用了PackageManager的鉴权办法。mPm.checkPermission,校验经过会设置标志位为true。

另外默认的IME(输入法)能够设置一直答应运用获取剪切板信息。这儿经过查看了同样会设置标志位为true。

然后便是校验op(operation,行为)。这儿依旧是查看是否有操作的权限,比方一般运用假如未获取焦点,是不答应读取剪切板的。

经过了上述所有查看之后,在最终,会调用AppOpsManager来进行行为查看。

noteOp和checkOp的首要表现是相同的,最大的不同点是noteOp会在校验行为的一起,对行为进行记载。这个办法的回来值介绍如下:

  • MODE_ALLOWED:故名思议,答应操作。

  • MODE_IGNORED:“忽略”,运用未具有相应操作的权限,可是此刻会静默拒绝,不会抛出反常导致运用crash。

  • MODE_ERRORED:和MODE_IGNORED相似,同样是运用不具有相应权限。可是此刻framework会抛出一个SecurityException反常。

  • MODE_DEFAULT:不常见

  • MODE_FOREGROUND:故名意思,只有运用在前台的时分才被答应相应操作。

咱们从noteOp这个办法持续往下的剖析。能够发现在最终会调用到AppOpsManager.noteOpNoThrow办法中。这个办法在android 10上的完成极为简单,可是在android 11上做了很大的改变。咱们对比一下前后的改变:

android 10

  public int noteOpNoThrow(int op, int uid, String packageName) {
        try {
            return mService.noteOperation(op, uid, packageName);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
  }

android 11

  public int noteOpNoThrow(int op, int uid, @Nullable String packageName,
            @Nullable String attributionTag, @Nullable String message) {
        try {
            collectNoteOpCallsForValidation(op);
            int collectionMode = getNotedOpCollectionMode(uid, packageName, op);
            boolean shouldCollectMessage = Process.myUid() == Process.SYSTEM_UID ? true : false;
            if (collectionMode == COLLECT_ASYNC) {
                if (message == null) {
                    // Set stack trace as default message
                    message = getFormattedStackTrace();
                    shouldCollectMessage = true;
                }
            }
            int mode = mService.noteOperation(op, uid, packageName, attributionTag,
                    collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
            if (mode == MODE_ALLOWED) {
                if (collectionMode == COLLECT_SELF) {
                    collectNotedOpForSelf(op, attributionTag);
                } else if (collectionMode == COLLECT_SYNC) {
                    collectNotedOpSync(op, attributionTag);
                }
            }
            return mode;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

能够看到android 11上首要是增加了搜集调用者信息的相关办法。

首先经过collectNoteOpCallsForValidation进行信息搜集。这个办法内部如下:

    private void collectNoteOpCallsForValidation(int op) {
        if (NOTE_OP_COLLECTION_ENABLED) {
            try {
                mService.collectNoteOpCallsForValidation(getFormattedStackTrace(),
                        op, mContext.getOpPackageName(), mContext.getApplicationInfo().longVersionCode);
            } catch (RemoteException e) {
                // Swallow error, only meant for logging ops, should not affect flow of the code
            }
        }
    }

经过getFormattedStackTrace获取现在的调用栈信息,然后将信息传入AppOpsService中,以供存入文件。由于AppOpsService归于跨进程调用,无法直接获取调用者的仓库信息,而AppOpsManager能够做到,所以才将这部分逻辑代码放置在AppOpsManager中完成。

咱们持续跟随noteOpNoThrow中的代码。调用了getNotedOpCollectionMode办法来判别当前是否要进行信息搜集。这部分判别会决定最终是否调用collectNotedOpForSelf或者collectNotedOpSync。从源码中咱们能够得知,这两块的信息搜集是传递给OnOpNotedCallback的,用户经过AppOpsManager设置的隐私代码审计便是经过这个接口。

getNotedOpCollectionMode办法会从sAppOpsToNote数组中取得判别结果,这个数组是一个简单的缓存结构,在没有缓存的时分会从AppOpsService.shouldCollectNotes中取得结果。这个办法的代码如下:

  public boolean shouldCollectNotes(int opCode) {
        Preconditions.checkArgumentInRange(opCode, 0, _NUM_OP - 1, "opCode");
        String perm = AppOpsManager.opToPermission(opCode);
        if (perm == null) {
            return false;
        }
        PermissionInfo permInfo;
        try {
            permInfo = mContext.getPackageManager().getPermissionInfo(perm, 0);
        } catch (PackageManager.NameNotFoundException e) {
            return false;
        }
        return permInfo.getProtection() == PROTECTION_DANGEROUS
                || (permInfo.getProtectionFlags() & PROTECTION_FLAG_APPOP) != 0;
    }

最要害的判别其实是最终一行。前一句permInfo.getProtection() == PROTECTION_DANGEROUS比较好了解,当权限是PROTECTION_DANGEROUS等级的时分,回来true。后一句涉及到一个额外的标志,appop。这儿我用举例的方式解说,咱们定位到framework/base/core/res/AndroidManifest.xml,查看ACCESS_NOTIFICATIONS这个权限:

    <permission android:name="android.permission.ACCESS_NOTIFICATIONS"
        android:protectionLevel="signature|privileged|appop" />

像这种在protectionLevel标签中附加appop的权限,哪怕不是PROTECTION_DANGEROUS等级,同样会回来为true。可是咱们能够看到,附加了这种标签的权限非常少,只有寥寥几个。所以大部分非敏感权限,都是不会触发OnOpNotedCallback的回调的。可是不扫除google将来收紧的或许。

总结

能够看到,android中的“权限校验”不止permissionManager,还混杂了AppOps在其间。两者共同完成了权限校验。可是AppOps的查看更为广泛,很多没有被界说为“权限”的行为,也会由AppOps来进行查看。

AppOps在android 11上有了很大更新,添加了隐私查看机制,记载的信息也更加全面。关于运用开发者来说,能够经过新api来查看自己的运用是否有预料之外的隐私访问,躲避法令危险。关于framework开发者来说,能够经过新的api来更轻松地完成相似隐私访问统计的功用(如MIUI照明弹);