Change our Intent flag to indicate that a Uri permission grant is persistable, but don't actually persist it until explicitly taken by the receiving app. This prevents apps from spamming each other if persisted permissions aren't really required. Remember the last time a persisted grant was taken by an app, and use this to prune away the oldest grants when the number of grants grows too large. Allow apps to query persisted grants they are holding, and allow them to release previously persisted grants. Add public UriPermission class to return grant details and timestamp. Track various permission strengths separately, and combine together after each mutation pass. Persistable grants are currently treated like global grants, but they could be moved to have owners in the future. Require that grant holders trying to extend a persistable permission actually hold a persistable permission themselves. Bug: 10835779 Change-Id: I95b2f797c04ce7fd2612f9a644685dbd44e03759
358 lines
12 KiB
Java
358 lines
12 KiB
Java
/*
|
|
* Copyright (C) 2006 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.server.am;
|
|
|
|
import android.content.Intent;
|
|
import android.net.Uri;
|
|
import android.os.UserHandle;
|
|
import android.util.Log;
|
|
import android.util.Slog;
|
|
|
|
import com.google.android.collect.Sets;
|
|
|
|
import java.io.PrintWriter;
|
|
import java.util.Comparator;
|
|
import java.util.HashSet;
|
|
|
|
/**
|
|
* Description of a permission granted to an app to access a particular URI.
|
|
*
|
|
* CTS tests for this functionality can be run with "runtest cts-appsecurity".
|
|
*
|
|
* Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/
|
|
* src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
|
|
*/
|
|
final class UriPermission {
|
|
private static final String TAG = "UriPermission";
|
|
|
|
public static final int STRENGTH_NONE = 0;
|
|
public static final int STRENGTH_OWNED = 1;
|
|
public static final int STRENGTH_GLOBAL = 2;
|
|
public static final int STRENGTH_PERSISTABLE = 3;
|
|
|
|
final int userHandle;
|
|
final String sourcePkg;
|
|
final String targetPkg;
|
|
|
|
/** Cached UID of {@link #targetPkg}; should not be persisted */
|
|
final int targetUid;
|
|
|
|
final Uri uri;
|
|
|
|
/**
|
|
* Allowed modes. All permission enforcement should use this field. Must
|
|
* always be a combination of {@link #ownedModeFlags},
|
|
* {@link #globalModeFlags}, {@link #persistableModeFlags}, and
|
|
* {@link #persistedModeFlags}. Mutations <em>must</em> only be performed by
|
|
* the owning class.
|
|
*/
|
|
int modeFlags = 0;
|
|
|
|
/** Allowed modes with explicit owner. */
|
|
int ownedModeFlags = 0;
|
|
/** Allowed modes without explicit owner. */
|
|
int globalModeFlags = 0;
|
|
/** Allowed modes that have been offered for possible persisting. */
|
|
int persistableModeFlags = 0;
|
|
/** Allowed modes that should be persisted across device boots. */
|
|
int persistedModeFlags = 0;
|
|
|
|
/**
|
|
* Timestamp when {@link #persistedModeFlags} was first defined in
|
|
* {@link System#currentTimeMillis()} time base.
|
|
*/
|
|
long persistedCreateTime = INVALID_TIME;
|
|
|
|
private static final long INVALID_TIME = Long.MIN_VALUE;
|
|
|
|
private HashSet<UriPermissionOwner> mReadOwners;
|
|
private HashSet<UriPermissionOwner> mWriteOwners;
|
|
|
|
private String stringName;
|
|
|
|
UriPermission(String sourcePkg, String targetPkg, int targetUid, Uri uri) {
|
|
this.userHandle = UserHandle.getUserId(targetUid);
|
|
this.sourcePkg = sourcePkg;
|
|
this.targetPkg = targetPkg;
|
|
this.targetUid = targetUid;
|
|
this.uri = uri;
|
|
}
|
|
|
|
private void updateModeFlags() {
|
|
modeFlags = ownedModeFlags | globalModeFlags | persistableModeFlags | persistedModeFlags;
|
|
}
|
|
|
|
/**
|
|
* Initialize persisted modes as read from file. This doesn't issue any
|
|
* global or owner grants.
|
|
*/
|
|
void initPersistedModes(int modeFlags, long createdTime) {
|
|
persistableModeFlags = modeFlags;
|
|
persistedModeFlags = modeFlags;
|
|
persistedCreateTime = createdTime;
|
|
|
|
updateModeFlags();
|
|
}
|
|
|
|
void grantModes(int modeFlags, boolean persistable, UriPermissionOwner owner) {
|
|
if (persistable) {
|
|
persistableModeFlags |= modeFlags;
|
|
}
|
|
|
|
if (owner == null) {
|
|
globalModeFlags |= modeFlags;
|
|
} else {
|
|
if ((modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
|
|
addReadOwner(owner);
|
|
}
|
|
if ((modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
|
|
addWriteOwner(owner);
|
|
}
|
|
}
|
|
|
|
updateModeFlags();
|
|
}
|
|
|
|
/**
|
|
* @return if mode changes should trigger persisting.
|
|
*/
|
|
boolean takePersistableModes(int modeFlags) {
|
|
if ((~persistableModeFlags & modeFlags) != 0) {
|
|
Slog.w(TAG, "Trying to take 0x" + Integer.toHexString(modeFlags) + " but only 0x"
|
|
+ Integer.toHexString(persistableModeFlags) + " are available");
|
|
}
|
|
|
|
final int before = persistedModeFlags;
|
|
persistedModeFlags |= (persistableModeFlags & modeFlags);
|
|
|
|
if (persistedModeFlags != 0) {
|
|
persistedCreateTime = System.currentTimeMillis();
|
|
}
|
|
|
|
updateModeFlags();
|
|
return persistedModeFlags != before;
|
|
}
|
|
|
|
boolean releasePersistableModes(int modeFlags) {
|
|
final int before = persistedModeFlags;
|
|
|
|
persistableModeFlags &= ~modeFlags;
|
|
persistedModeFlags &= ~modeFlags;
|
|
|
|
if (persistedModeFlags == 0) {
|
|
persistedCreateTime = INVALID_TIME;
|
|
}
|
|
|
|
updateModeFlags();
|
|
return persistedModeFlags != before;
|
|
}
|
|
|
|
/**
|
|
* @return if mode changes should trigger persisting.
|
|
*/
|
|
boolean clearModes(int modeFlags, boolean persistable) {
|
|
final int before = persistedModeFlags;
|
|
|
|
if ((modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
|
|
if (persistable) {
|
|
persistableModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
|
|
persistedModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
|
|
}
|
|
globalModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
|
|
if (mReadOwners != null) {
|
|
ownedModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
|
|
for (UriPermissionOwner r : mReadOwners) {
|
|
r.removeReadPermission(this);
|
|
}
|
|
mReadOwners = null;
|
|
}
|
|
}
|
|
if ((modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
|
|
if (persistable) {
|
|
persistableModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
|
|
persistedModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
|
|
}
|
|
globalModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
|
|
if (mWriteOwners != null) {
|
|
ownedModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
|
|
for (UriPermissionOwner r : mWriteOwners) {
|
|
r.removeWritePermission(this);
|
|
}
|
|
mWriteOwners = null;
|
|
}
|
|
}
|
|
|
|
if (persistedModeFlags == 0) {
|
|
persistedCreateTime = INVALID_TIME;
|
|
}
|
|
|
|
updateModeFlags();
|
|
return persistedModeFlags != before;
|
|
}
|
|
|
|
/**
|
|
* Return strength of this permission grant for the given flags.
|
|
*/
|
|
public int getStrength(int modeFlags) {
|
|
if ((persistableModeFlags & modeFlags) == modeFlags) {
|
|
return STRENGTH_PERSISTABLE;
|
|
} else if ((globalModeFlags & modeFlags) == modeFlags) {
|
|
return STRENGTH_GLOBAL;
|
|
} else if ((ownedModeFlags & modeFlags) == modeFlags) {
|
|
return STRENGTH_OWNED;
|
|
} else {
|
|
return STRENGTH_NONE;
|
|
}
|
|
}
|
|
|
|
private void addReadOwner(UriPermissionOwner owner) {
|
|
if (mReadOwners == null) {
|
|
mReadOwners = Sets.newHashSet();
|
|
ownedModeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION;
|
|
updateModeFlags();
|
|
}
|
|
if (mReadOwners.add(owner)) {
|
|
owner.addReadPermission(this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove given read owner, updating {@Link #modeFlags} as needed.
|
|
*/
|
|
void removeReadOwner(UriPermissionOwner owner) {
|
|
if (!mReadOwners.remove(owner)) {
|
|
Log.wtf(TAG, "Unknown read owner " + owner + " in " + this);
|
|
}
|
|
if (mReadOwners.size() == 0) {
|
|
mReadOwners = null;
|
|
ownedModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
|
|
updateModeFlags();
|
|
}
|
|
}
|
|
|
|
private void addWriteOwner(UriPermissionOwner owner) {
|
|
if (mWriteOwners == null) {
|
|
mWriteOwners = Sets.newHashSet();
|
|
ownedModeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
|
|
updateModeFlags();
|
|
}
|
|
if (mWriteOwners.add(owner)) {
|
|
owner.addWritePermission(this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove given write owner, updating {@Link #modeFlags} as needed.
|
|
*/
|
|
void removeWriteOwner(UriPermissionOwner owner) {
|
|
if (!mWriteOwners.remove(owner)) {
|
|
Log.wtf(TAG, "Unknown write owner " + owner + " in " + this);
|
|
}
|
|
if (mWriteOwners.size() == 0) {
|
|
mWriteOwners = null;
|
|
ownedModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
|
|
updateModeFlags();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
if (stringName != null) {
|
|
return stringName;
|
|
}
|
|
StringBuilder sb = new StringBuilder(128);
|
|
sb.append("UriPermission{");
|
|
sb.append(Integer.toHexString(System.identityHashCode(this)));
|
|
sb.append(' ');
|
|
sb.append(uri);
|
|
sb.append('}');
|
|
return stringName = sb.toString();
|
|
}
|
|
|
|
void dump(PrintWriter pw, String prefix) {
|
|
pw.print(prefix);
|
|
pw.print("userHandle=" + userHandle);
|
|
pw.print(" sourcePkg=" + sourcePkg);
|
|
pw.println(" targetPkg=" + targetPkg);
|
|
|
|
pw.print(prefix);
|
|
pw.print("mode=0x" + Integer.toHexString(modeFlags));
|
|
pw.print(" owned=0x" + Integer.toHexString(ownedModeFlags));
|
|
pw.print(" global=0x" + Integer.toHexString(globalModeFlags));
|
|
pw.print(" persistable=0x" + Integer.toHexString(persistableModeFlags));
|
|
pw.print(" persisted=0x" + Integer.toHexString(persistedModeFlags));
|
|
if (persistedCreateTime != INVALID_TIME) {
|
|
pw.print(" persistedCreate=" + persistedCreateTime);
|
|
}
|
|
pw.println();
|
|
|
|
if (mReadOwners != null) {
|
|
pw.print(prefix);
|
|
pw.println("readOwners:");
|
|
for (UriPermissionOwner owner : mReadOwners) {
|
|
pw.print(prefix);
|
|
pw.println(" * " + owner);
|
|
}
|
|
}
|
|
if (mWriteOwners != null) {
|
|
pw.print(prefix);
|
|
pw.println("writeOwners:");
|
|
for (UriPermissionOwner owner : mReadOwners) {
|
|
pw.print(prefix);
|
|
pw.println(" * " + owner);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static class PersistedTimeComparator implements Comparator<UriPermission> {
|
|
@Override
|
|
public int compare(UriPermission lhs, UriPermission rhs) {
|
|
return Long.compare(lhs.persistedCreateTime, rhs.persistedCreateTime);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Snapshot of {@link UriPermission} with frozen
|
|
* {@link UriPermission#persistedModeFlags} state.
|
|
*/
|
|
public static class Snapshot {
|
|
final int userHandle;
|
|
final String sourcePkg;
|
|
final String targetPkg;
|
|
final Uri uri;
|
|
final int persistedModeFlags;
|
|
final long persistedCreateTime;
|
|
|
|
private Snapshot(UriPermission perm) {
|
|
this.userHandle = perm.userHandle;
|
|
this.sourcePkg = perm.sourcePkg;
|
|
this.targetPkg = perm.targetPkg;
|
|
this.uri = perm.uri;
|
|
this.persistedModeFlags = perm.persistedModeFlags;
|
|
this.persistedCreateTime = perm.persistedCreateTime;
|
|
}
|
|
}
|
|
|
|
public Snapshot snapshot() {
|
|
return new Snapshot(this);
|
|
}
|
|
|
|
public android.content.UriPermission buildPersistedPublicApiObject() {
|
|
return new android.content.UriPermission(uri, persistedModeFlags, persistedCreateTime);
|
|
}
|
|
}
|