Android Content Provider 호출 오류 "Bad call: specified package xxx under uid 10032 but it is really 10001"및 Binder 권한 문제 분석
프로젝트 중 한 가지 상황이 있습니다: 프로세스 A는 다른 프로세스의BContentProvider를 호출합니다. B는 이query에서query의 다른 CContentProvider를 호출해야 합니다.
class BContentProvider extends ContentProvider {
Context mContext;
...
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
...
try {
// query C ContentProvider:
Cursor cursor = mContext.getContentResolver().query(...);
if (cursor != null) {
try {
//do something;
} finally {
cursor.close();
}
}
Cursor cursor = mContext.getContentResolver().query(...);
...
...
}
}
}
이 경우 다음과 같이 Exception이 제거됩니다.
1-11 16:04:51.867 2633 3557 W AppOps : Bad call: specified package com.providers.xxx under uid 10032 but it is really 10001
01-11 16:04:51.867 2633 3557 W AppOps : java.lang.RuntimeException: here
01-11 16:04:51.867 2633 3557 W AppOps : at com.android.server.AppOpsService.getOpsRawLocked(AppOpsService.java:1399)
01-11 16:04:51.867 2633 3557 W AppOps : at com.android.server.AppOpsService.noteOperationUnchecked(AppOpsService.java:1115)
01-11 16:04:51.867 2633 3557 W AppOps : at com.android.server.AppOpsService.noteProxyOperation(AppOpsService.java:1093)
01-11 16:04:51.867 2633 3557 W AppOps : at com.android.internal.app.IAppOpsService$Stub.onTransact(IAppOpsService.java:157)
01-11 16:04:51.867 2633 3557 W AppOps : at android.os.BinderInjector.onTransact(BinderInjector.java:30)
01-11 16:04:51.867 2633 3557 W AppOps : at android.os.Binder.execTransact(Binder.java:569)
01-11 16:04:51.868 4659 6791 E DatabaseUtils: Writing exception to parcel
01-11 16:04:51.868 4659 6791 E DatabaseUtils: java.lang.SecurityException: Proxy package com.providers.xxx from uid 10001 or calling package com.providers.xxx from uid 10032 not allowed to perform READ_PROVIDER_C
01-11 16:04:51.868 4659 6791 E DatabaseUtils: at android.app.AppOpsManager.noteProxyOp(AppOpsManager.java:1834)
01-11 16:04:51.868 4659 6791 E DatabaseUtils: at android.content.ContentProvider.checkPermissionAndAppOp(ContentProvider.java:538)
01-11 16:04:51.868 4659 6791 E DatabaseUtils: at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:560)
01-11 16:04:51.868 4659 6791 E DatabaseUtils: at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:483)
01-11 16:04:51.868 4659 6791 E DatabaseUtils: at android.content.ContentProvider$Transport.query(ContentProvider.java:212)
01-11 16:04:51.868 4659 6791 E DatabaseUtils: at android.content.ContentResolver.query(ContentResolver.java:532)
01-11 16:04:51.868 4659 6791 E DatabaseUtils: at android.content.ContentResolver.query(ContentResolver.java:473)
01-11 16:04:51.868 4659 6791 E DatabaseUtils: at com.android.providers.xxx.BDatabaseHelper.query(BDatabaseHelper.java:7238)
01-11 16:04:51.868 4659 6791 E DatabaseUtils: at
01-11 16:04:51.868 4659 6791 E DatabaseUtils: at android.content.ContentProvider$Transport.query(ContentProvider.java:239)
01-11 16:04:51.868 4659 6791 E DatabaseUtils: at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:112)
01-11 16:04:51.868 4659 6791 E DatabaseUtils: at android.os.BinderInjector.onTransact(BinderInjector.java:30)
01-11 16:04:51.868 4659 6791 E DatabaseUtils: at android.os.Binder.execTransact(Binder.java:569)
분석:
오류 로그는 C Content Provider 권한이 없음을 먼저 반영하지만 A 애플리케이션에 C 읽기 및 쓰기 권한이 있는지 확인합니다.그래서 A의 권한 문제를 배제했다.계속 분석: 로그를 통해 ContentProvider가 권한 검사를 할 때 오류가 발생했음을 알 수 있습니다.log에 대응하는 원본 코드를 통해 분석합니다: 우선ContentProvider를 볼 수 있습니다.query () 때 권한 검사를 했습니다. 전송된 enforceReadPermission () 의callingPkg은 호출자의 가방 이름입니다. 위의 예를 들어 B의 가방 이름입니다.
ContentProvider.query():
@Override
public Cursor query(String callingPkg, Uri uri, @Nullable String[] projection,
@Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) {
validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
enforceReadPermission()이 호출되었습니다.checkPermissionAndAppOp() 메서드, ContentProvider.checkPermissionAndAppOp()에서 AppOpsManager가 호출되었습니다.noteProxyOp () 에서 이상을 검사했습니다.
AppOpsManager.noteProxyOp():
public int noteProxyOp(int op, String proxiedPackageName) {
int mode = noteProxyOpNoThrow(op, proxiedPackageName);
if (mode == MODE_ERRORED) {
throw new SecurityException("Proxy package " + mContext.getOpPackageName()
+ " from uid " + Process.myUid() + " or calling package "
+ proxiedPackageName + " from uid " + Binder.getCallingUid()
+ " not allowed to perform " + sOpNames[op]);
}
return mode;
}
noteProxyOpNoThrow()는 또 무엇을 했습니까?AppOpsManager.noteProxyOpNoThrow():
/**
* Like {@link #noteProxyOp(int, String)} but instead
* of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
* @hide
*/
public int noteProxyOpNoThrow(int op, String proxiedPackageName) {
try {
return mService.noteProxyOperation(op, mContext.getOpPackageName(),
Binder.getCallingUid(), proxiedPackageName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
noteProxyOpNoThrow () 는 binder를 통해 AppOps 서비스로 호출되었음을 알 수 있습니다.noteProxyOperation () 방법, 여기 들어오는 것은 AppOps 서비스입니다.noteProxyOperation()의 다음 두 매개변수는 Binder입니다.getCallingUid () 와 이전에 층층이 전송된 호출자의 패키지 이름, 즉 상기 예의 B의 패키지 이름.
다음은 binder 반대편에 있는 AppOps Service를 살펴보겠습니다.noteProxyOperation () 방법은 log의 AppOps 출력 log와 결합합니다.
AppOpsService.noteProxyOperation():
@Override
public int noteProxyOperation(int code, String proxyPackageName,
int proxiedUid, String proxiedPackageName) {
verifyIncomingOp(code);
final int proxyUid = Binder.getCallingUid();
String resolveProxyPackageName = resolvePackageName(proxyUid, proxyPackageName);
if (resolveProxyPackageName == null) {
return AppOpsManager.MODE_IGNORED;
}
final int proxyMode = noteOperationUnchecked(code, proxyUid,
resolveProxyPackageName, -1, null);
if (proxyMode != AppOpsManager.MODE_ALLOWED || Binder.getCallingUid() == proxiedUid) {
return proxyMode;
}
String resolveProxiedPackageName = resolvePackageName(proxiedUid, proxiedPackageName);
if (resolveProxiedPackageName == null) {
return AppOpsManager.MODE_IGNORED;
}
return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
proxyMode, resolveProxyPackageName);
}
AppOpsService.noteOperationUnchecked():
private int noteOperationUnchecked(int code, int uid, String packageName,
int proxyUid, String proxyPackageName) {
Op op = null;
Op switchOp = null;
int switchCode;
int resultMode = AppOpsManager.MODE_ALLOWED;
synchronized (this) {
Ops ops = getOpsRawLocked(uid, packageName, true);
...
}
...
}
AppOpsService.getOpsRawLocked():
private Ops getOpsRawLocked(int uid, String packageName, boolean edit) {
...
Ops ops = uidState.pkgOps.get(packageName);
if (ops == null) {
if (!edit) {
return null;
}
boolean isPrivileged = false;
// This is the first time we have seen this package name under this uid,
// so let's make sure it is valid.
if (uid != 0) {
final long ident = Binder.clearCallingIdentity();
try {
int pkgUid = -1;
try {
ApplicationInfo appInfo = ActivityThread.getPackageManager()
.getApplicationInfo(packageName,
PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
UserHandle.getUserId(uid));
if (appInfo != null) {
pkgUid = appInfo.uid;
isPrivileged = (appInfo.privateFlags
& ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
}
...
}
...
if (pkgUid != uid) {
// Oops! The package name is not valid for the uid they are calling
// under. Abort.
RuntimeException ex = new RuntimeException("here");
ex.fillInStackTrace();
Slog.w(TAG, "Bad call: specified package " + packageName
+ " under uid " + uid + " but it is really " + pkgUid, ex);
return null;
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
ops = new Ops(packageName, uidState, isPrivileged);
uidState.pkgOps.put(packageName, ops);
}
return ops;
}
이 동작은 전송된 uid와 패키지 이름을 판단하는 것입니다. 이 패키지에 대응하는 uid와 전송된 uid를 비교하고 일치하지 않으면 오류를 보고합니다.오류 정보와 log의 일치:
Bad call: specified package com.providers.xxx under uid 10032 but it is really 10001
위에서 언급한 바와 같이 이 가방 이름은 전송된Content Provider의 호출자의 가방 이름이고 예를 들어 B의 가방 이름이다.uid는 AppOps Manager에서 Binder를 통과합니다.getCallingUid()에서 획득한 것입니다.log에 이 uid는 B의 uid가 아니라 상위 호출자 A의 uid입니다.왜 C에서 Binder를 호출합니까?getCallingUid () 가 A 프로세스를 얻었습니까?나는 원휘휘 대신의 블로그: Binder IPC의 권한 제어를 찾았다
"스레드 B는 Binder를 통해 현재 스레드의 구성 요소를 호출합니다. 이 때 스레드 B는 스레드 B의 구성 요소의 호출단입니다. mCallingUid와 mCallingPid는 현재 스레드 B의 PID와 UID를 저장해야 하기 때문에clearCallingIdentity () 를 호출해야 합니다.방법은 이 기능을 완성한다.스레드 B가 어떤 구성 요소를 다 사용하면 스레드 B가 여전히 스레드 A의 호출단에 있기 때문에 mCalling Uid와 mCalling Pid는 스레드 A의 UID와 PID로 복원해야 합니다. 이것은restore Calling Identity () 를 호출하면 완성할 수 있습니다.
Binder의 메커니즘은 이렇게 설계되어 있기 때문에 B가 다음 Binder 호출(즉query Content Provider)을 진행하기 전에clear Calling Identity ()를 호출해서 B의 PID와 UID를 mCalling Uid와 mCalling Pid에 첨부해야 한다.Binder 호출이 끝난 후 Restore Calling Identity () 에서 호출자의 PID와 UID로 복원합니다.이렇게 하면 C에서 B와 관련된 정보로 권한 검사를 하고 AppOps 서비스에서 권한을 검사합니다.getopsRawLocked (), UID와 가방 이름이 모두 B이고 일치하면 잘못 보고하지 않습니다.
해결 방법:
사실 상기에서 언급한 바와 같이 Binder IPC의 권한 제어를 참고하여 B가Query 전후에clearCallingIdentity()//를 각각 호출하는 역할은 원격 호출단의 uid와 pid를 비우고 현재 로컬 프로세스의 uid와 pid로 대체하는 것이다. 그러면 이후 호출자가 권한 검사를 할 때 B의 정보를 위주로 하고 패키지 이름과 UID가 일치하지 않는 상황이 발생하지 않는다.마지막으로 수정된 호출 방법은 다음과 같습니다.
long token = Binder.clearCallingIdentity();
try {
Cursor cursor = mContext.getContentResolver().query(...);
if (cursor != null) {
try {
//do something;
} finally {
cursor.close();
}
}
} finally {
Binder.restoreCallingIdentity(token);
}
요약:
1. Content Provider는 Binder로 이루어진 것으로 조회하는 과정은 사실 Binder 호출이기 때문에 Content Provider에 대해 깊이 있게 이해하려면 반드시 Binder와 관련된 지식을 알아야 한다.2. Content Provider는 한 번의 조회를 받기 전에 AppOps Manager(Binder를 통해 AppOps Service에서 완성됨)를 호출하여 권한 검사를 한다. 호출자의 UID와 패키지 이름이 일치하는지 검사한다. 그 관련 기능은 Android 권한 관리인 AppOps를 볼 수 있다.2. Binder가 호출될 때 Binder를 통과할 수 있다.getCallingPid() 및 Binder.get Calling Uid () 는 호출자의 PID와 UID를 가져오고, A가 Binder를 통해 B를 호출하면 Binder가 C를 호출하면 C에서 Binder를 가져옵니다.getCallingPid() 및 Binder.getCallingUid () 는 A의 PID와 UID를 받았습니다. 이 경우 B 호출 C의 앞뒤에 Binder를 사용해야 합니다.clearCallingIdentity () 및 Binder.restore Calling Identity () 는 B의 PID와 UID를 가지고 와서 C에서 권한 검사를 할 때 B의 정보로 검사를 한다. 물론 이것도 논리에 부합된다. B가 호출한 C는 B에게 상응하는 권한이 있어야 한다.3.Binder.clearCallingIdentity () 및 Binder.restoreCallingIdentity()의 실현 원리인 Binder IPC의 권한 제어도 위치 이동을 통해 이뤄졌다고 소개했다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.