diff options
Diffstat (limited to 'services')
27 files changed, 1860 insertions, 180 deletions
diff --git a/services/java/com/android/server/IntentResolver.java b/services/java/com/android/server/IntentResolver.java index 9b19008..35345f5 100644 --- a/services/java/com/android/server/IntentResolver.java +++ b/services/java/com/android/server/IntentResolver.java @@ -117,7 +117,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { boolean printedHeader = false; F filter; for (int i=0; i<N && (filter=a[i]) != null; i++) { - if (packageName != null && !packageName.equals(packageForFilter(filter))) { + if (packageName != null && !isPackageForFilter(packageName, filter)) { continue; } if (title != null) { @@ -357,11 +357,11 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { } /** - * Return the package that owns this filter. This must be implemented to - * provide correct filtering of Intents that have specified a package name - * they are to be delivered to. + * Returns whether this filter is owned by this package. This must be + * implemented to provide correct filtering of Intents that have + * specified a package name they are to be delivered to. */ - protected abstract String packageForFilter(F filter); + protected abstract boolean isPackageForFilter(String packageName, F filter); protected abstract F[] newArray(int size); @@ -556,7 +556,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { } // Is delivery being limited to filters owned by a particular package? - if (packageName != null && !packageName.equals(packageForFilter(filter))) { + if (packageName != null && !isPackageForFilter(packageName, filter)) { if (debug) { Slog.v(TAG, " Filter is not from package " + packageName + "; skipping"); } @@ -710,8 +710,8 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { } private final IntentResolverOld<F, R> mOldResolver = new IntentResolverOld<F, R>() { - @Override protected String packageForFilter(F filter) { - return IntentResolver.this.packageForFilter(filter); + @Override protected boolean isPackageForFilter(String packageName, F filter) { + return IntentResolver.this.isPackageForFilter(packageName, filter); } @Override protected boolean allowFilterResult(F filter, List<R> dest) { return IntentResolver.this.allowFilterResult(filter, dest); diff --git a/services/java/com/android/server/IntentResolverOld.java b/services/java/com/android/server/IntentResolverOld.java index 4dd77ce..94a2379 100644 --- a/services/java/com/android/server/IntentResolverOld.java +++ b/services/java/com/android/server/IntentResolverOld.java @@ -106,7 +106,7 @@ public abstract class IntentResolverOld<F extends IntentFilter, R extends Object boolean printedHeader = false; for (int i=0; i<N; i++) { F filter = a.get(i); - if (packageName != null && !packageName.equals(packageForFilter(filter))) { + if (packageName != null && !isPackageForFilter(packageName, filter)) { continue; } if (title != null) { @@ -336,11 +336,11 @@ public abstract class IntentResolverOld<F extends IntentFilter, R extends Object } /** - * Return the package that owns this filter. This must be implemented to - * provide correct filtering of Intents that have specified a package name - * they are to be delivered to. + * Returns whether this filter is owned by this package. This must be + * implemented to provide correct filtering of Intents that have + * specified a package name they are to be delivered to. */ - protected abstract String packageForFilter(F filter); + protected abstract boolean isPackageForFilter(String packageName, F filter); @SuppressWarnings("unchecked") protected R newResult(F filter, int match, int userId) { @@ -529,7 +529,7 @@ public abstract class IntentResolverOld<F extends IntentFilter, R extends Object } // Is delivery being limited to filters owned by a particular package? - if (packageName != null && !packageName.equals(packageForFilter(filter))) { + if (packageName != null && !isPackageForFilter(packageName, filter)) { if (debug) { Slog.v(TAG, " Filter is not from package " + packageName + "; skipping"); } diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index 5cf1c28..44d730c 100644 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -237,9 +237,15 @@ public class NotificationManagerService extends INotificationManager.Stub ArrayDeque<StatusBarNotification> mBuffer = new ArrayDeque<StatusBarNotification>(BUFFER_SIZE); public Archive() { - } + public void record(StatusBarNotification nr) { + // Nuke heavy parts of notification before storing in archive + nr.notification.tickerView = null; + nr.notification.contentView = null; + nr.notification.bigContentView = null; + nr.notification.largeIcon = null; + if (mBuffer.size() == BUFFER_SIZE) { mBuffer.removeFirst(); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 4631395..a30fc3b 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -862,6 +862,11 @@ class ServerThread extends Thread { public void run() { Slog.i(TAG, "Making services ready"); + try { + ActivityManagerService.self().startObservingNativeCrashes(); + } catch (Throwable e) { + reportWtf("observing native crashes", e); + } if (!headless) startSystemUi(contextF); try { if (mountServiceF != null) mountServiceF.systemReady(); diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java index 1663106..167e7af 100644 --- a/services/java/com/android/server/Watchdog.java +++ b/services/java/com/android/server/Watchdog.java @@ -88,7 +88,6 @@ public class Watchdog extends Thread { AlarmManagerService mAlarm; ActivityManagerService mActivity; boolean mCompleted; - boolean mForceKillSystem; Monitor mCurrentMonitor; int mPhonePid; @@ -135,7 +134,9 @@ public class Watchdog extends Thread { final int size = mMonitors.size(); for (int i = 0 ; i < size ; i++) { - mCurrentMonitor = mMonitors.get(i); + synchronized (Watchdog.this) { + mCurrentMonitor = mMonitors.get(i); + } mCurrentMonitor.monitor(); } @@ -388,6 +389,8 @@ public class Watchdog extends Thread { mCompleted = false; mHandler.sendEmptyMessage(MONITOR); + + final String name; synchronized (this) { long timeout = TIME_TO_WAIT; @@ -396,16 +399,16 @@ public class Watchdog extends Thread { // to timeout on is asleep as well and won't have a chance to run, causing a false // positive on when to kill things. long start = SystemClock.uptimeMillis(); - while (timeout > 0 && !mForceKillSystem) { + while (timeout > 0) { try { - wait(timeout); // notifyAll() is called when mForceKillSystem is set + wait(timeout); } catch (InterruptedException e) { Log.wtf(TAG, e); } timeout = TIME_TO_WAIT - (SystemClock.uptimeMillis() - start); } - if (mCompleted && !mForceKillSystem) { + if (mCompleted) { // The monitors have returned. waitedHalf = false; continue; @@ -421,14 +424,14 @@ public class Watchdog extends Thread { waitedHalf = true; continue; } + + name = (mCurrentMonitor != null) ? + mCurrentMonitor.getClass().getName() : "null"; } // If we got here, that means that the system is most likely hung. // First collect stack traces from all threads of the system process. // Then kill this process so that the system will restart. - - final String name = (mCurrentMonitor != null) ? - mCurrentMonitor.getClass().getName() : "null"; EventLog.writeEvent(EventLogTags.WATCHDOG, name); ArrayList<Integer> pids = new ArrayList<Integer>(); diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java index eb144ab..14d808f 100644 --- a/services/java/com/android/server/accounts/AccountManagerService.java +++ b/services/java/com/android/server/accounts/AccountManagerService.java @@ -716,7 +716,7 @@ public class AccountManagerService * @param account the account to share with limited users */ private void addAccountToLimitedUsers(Account account) { - List<UserInfo> users = mUserManager.getUsers(); + List<UserInfo> users = getUserManager().getUsers(); for (UserInfo user : users) { if (user.isRestricted()) { addSharedAccountAsUser(account, user.id); diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 88ef884..7710f13 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -30,6 +30,7 @@ import com.android.server.ProcessMap; import com.android.server.SystemServer; import com.android.server.Watchdog; import com.android.server.am.ActivityStack.ActivityState; +import com.android.server.firewall.IntentFirewall; import com.android.server.pm.UserManagerService; import com.android.server.wm.AppTransition; import com.android.server.wm.WindowManagerService; @@ -274,6 +275,8 @@ public final class ActivityManagerService extends ActivityManagerNative public ActivityStack mMainStack; + public IntentFirewall mIntentFirewall; + private final boolean mHeadless; // Whether we should show our dialogs (ANR, crash, etc) or just perform their @@ -570,8 +573,8 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override - protected String packageForFilter(BroadcastFilter filter) { - return filter.packageName; + protected boolean isPackageForFilter(String packageName, BroadcastFilter filter) { + return packageName.equals(filter.packageName); } }; @@ -1407,7 +1410,7 @@ public final class ActivityManagerService extends ActivityManagerNative public static void setSystemProcess() { try { ActivityManagerService m = mSelf; - + ServiceManager.addService("activity", m, true); ServiceManager.addService("meminfo", new MemBinder(m)); ServiceManager.addService("gfxinfo", new GraphicsBinder(m)); @@ -1445,6 +1448,11 @@ public final class ActivityManagerService extends ActivityManagerNative mWindowManager = wm; } + public void startObservingNativeCrashes() { + final NativeCrashListener ncl = new NativeCrashListener(); + ncl.start(); + } + public static final Context main(int factoryTest) { AThread thr = new AThread(); thr.start(); @@ -1467,7 +1475,8 @@ public final class ActivityManagerService extends ActivityManagerNative m.mContext = context; m.mFactoryTest = factoryTest; m.mMainStack = new ActivityStack(m, context, true, thr.mLooper); - + m.mIntentFirewall = new IntentFirewall(m.new IntentFirewallInterface()); + m.mBatteryStatsService.publish(context); m.mUsageStatsService.publish(context); m.mAppOpsService.publish(context); @@ -4943,6 +4952,14 @@ public final class ActivityManagerService extends ActivityManagerNative } } + class IntentFirewallInterface implements IntentFirewall.AMSInterface { + public int checkComponentPermission(String permission, int pid, int uid, + int owningUid, boolean exported) { + return ActivityManagerService.this.checkComponentPermission(permission, pid, uid, + owningUid, exported); + } + } + /** * This can be called with or without the global lock held. */ @@ -8333,6 +8350,14 @@ public final class ActivityManagerService extends ActivityManagerNative final String processName = app == null ? "system_server" : (r == null ? "unknown" : r.processName); + handleApplicationCrashInner(r, processName, crashInfo); + } + + /* Native crash reporting uses this inner version because it needs to be somewhat + * decoupled from the AM-managed cleanup lifecycle + */ + void handleApplicationCrashInner(ProcessRecord r, String processName, + ApplicationErrorReport.CrashInfo crashInfo) { EventLog.writeEvent(EventLogTags.AM_CRASH, Binder.getCallingPid(), UserHandle.getUserId(Binder.getCallingUid()), processName, r == null ? -1 : r.info.flags, @@ -8846,7 +8871,7 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } - if (!r.crashing && !r.notResponding) { + if (!r.crashing && !r.notResponding && !r.forceCrashReport) { return null; } @@ -8857,7 +8882,7 @@ public final class ActivityManagerService extends ActivityManagerNative report.time = timeMillis; report.systemApp = (r.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0; - if (r.crashing) { + if (r.crashing || r.forceCrashReport) { report.type = ApplicationErrorReport.TYPE_CRASH; report.crashInfo = crashInfo; } else if (r.notResponding) { @@ -10867,7 +10892,7 @@ public final class ActivityManagerService extends ActivityManagerNative mProcessesToGc.remove(app); // Dismiss any open dialogs. - if (app.crashDialog != null) { + if (app.crashDialog != null && !app.forceCrashReport) { app.crashDialog.dismiss(); app.crashDialog = null; } diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 526b24f..3d7da7b 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -2489,6 +2489,7 @@ final class ActivityStack { int err = ActivityManager.START_SUCCESS; ProcessRecord callerApp = null; + if (caller != null) { callerApp = mService.getRecordForAppLocked(caller); if (callerApp != null) { @@ -2592,34 +2593,37 @@ final class ActivityStack { throw new SecurityException(msg); } + boolean abort = !mService.mIntentFirewall.checkStartActivity(intent, + callerApp==null?null:callerApp.info, callingPackage, callingUid, callingPid, + resolvedType, aInfo); + if (mMainStack) { if (mService.mController != null) { - boolean abort = false; try { // The Intent we give to the watcher has the extra data // stripped off, since it can contain private information. Intent watchIntent = intent.cloneFilter(); - abort = !mService.mController.activityStarting(watchIntent, + abort |= !mService.mController.activityStarting(watchIntent, aInfo.applicationInfo.packageName); } catch (RemoteException e) { mService.mController = null; } - - if (abort) { - if (resultRecord != null) { - sendActivityResultLocked(-1, - resultRecord, resultWho, requestCode, - Activity.RESULT_CANCELED, null); - } - // We pretend to the caller that it was really started, but - // they will just get a cancel result. - mDismissKeyguardOnNextActivity = false; - ActivityOptions.abort(options); - return ActivityManager.START_SUCCESS; - } } } + if (abort) { + if (resultRecord != null) { + sendActivityResultLocked(-1, + resultRecord, resultWho, requestCode, + Activity.RESULT_CANCELED, null); + } + // We pretend to the caller that it was really started, but + // they will just get a cancel result. + mDismissKeyguardOnNextActivity = false; + ActivityOptions.abort(options); + return ActivityManager.START_SUCCESS; + } + ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid, callingPackage, intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho, requestCode, componentSpecified); diff --git a/services/java/com/android/server/am/NativeCrashListener.java b/services/java/com/android/server/am/NativeCrashListener.java new file mode 100644 index 0000000..e83433f --- /dev/null +++ b/services/java/com/android/server/am/NativeCrashListener.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2013 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.app.ApplicationErrorReport.CrashInfo; +import android.util.Slog; + +import libcore.io.ErrnoException; +import libcore.io.Libcore; +import libcore.io.StructTimeval; +import libcore.io.StructUcred; + +import static libcore.io.OsConstants.*; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileDescriptor; +import java.net.InetSocketAddress; +import java.net.InetUnixAddress; + +/** + * Set up a Unix domain socket that debuggerd will connect() to in + * order to write a description of a native crash. The crash info is + * then parsed and forwarded to the ActivityManagerService's normal + * crash handling code. + * + * Note that this component runs in a separate thread. + */ +class NativeCrashListener extends Thread { + static final String TAG = "NativeCrashListener"; + static final boolean DEBUG = false; + + // Must match the path defined in debuggerd.c. + static final String DEBUGGERD_SOCKET_PATH = "/data/system/ndebugsocket"; + + // Use a short timeout on socket operations and abandon the connection + // on hard errors + static final long SOCKET_TIMEOUT_MILLIS = 1000; // 1 second + + final ActivityManagerService mAm; + + /* + * Spin the actual work of handling a debuggerd crash report into a + * separate thread so that the listener can go immediately back to + * accepting incoming connections. + */ + class NativeCrashReporter extends Thread { + ProcessRecord mApp; + int mSignal; + String mCrashReport; + + NativeCrashReporter(ProcessRecord app, int signal, String report) { + super("NativeCrashReport"); + mApp = app; + mSignal = signal; + mCrashReport = report; + } + + @Override + public void run() { + try { + CrashInfo ci = new CrashInfo(); + ci.exceptionClassName = "Native crash"; + ci.exceptionMessage = Libcore.os.strsignal(mSignal); + ci.throwFileName = "unknown"; + ci.throwClassName = "unknown"; + ci.throwMethodName = "unknown"; + ci.stackTrace = mCrashReport; + + if (DEBUG) Slog.v(TAG, "Calling handleApplicationCrash()"); + mAm.handleApplicationCrashInner(mApp, mApp.processName, ci); + if (DEBUG) Slog.v(TAG, "<-- handleApplicationCrash() returned"); + } catch (Exception e) { + Slog.e(TAG, "Unable to report native crash", e); + } + } + } + + /* + * Daemon thread that accept()s incoming domain socket connections from debuggerd + * and processes the crash dump that is passed through. + */ + NativeCrashListener() { + mAm = ActivityManagerService.self(); + } + + @Override + public void run() { + final byte[] ackSignal = new byte[1]; + + if (DEBUG) Slog.i(TAG, "Starting up"); + + // The file system entity for this socket is created with 0700 perms, owned + // by system:system. debuggerd runs as root, so is capable of connecting to + // it, but 3rd party apps cannot. + { + File socketFile = new File(DEBUGGERD_SOCKET_PATH); + if (socketFile.exists()) { + socketFile.delete(); + } + } + + try { + FileDescriptor serverFd = Libcore.os.socket(AF_UNIX, SOCK_STREAM, 0); + final InetUnixAddress sockAddr = new InetUnixAddress(DEBUGGERD_SOCKET_PATH); + Libcore.os.bind(serverFd, sockAddr, 0); + Libcore.os.listen(serverFd, 1); + + while (true) { + InetSocketAddress peer = new InetSocketAddress(); + FileDescriptor peerFd = null; + try { + if (DEBUG) Slog.v(TAG, "Waiting for debuggerd connection"); + peerFd = Libcore.os.accept(serverFd, peer); + if (DEBUG) Slog.v(TAG, "Got debuggerd socket " + peerFd); + if (peerFd != null) { + // Only the superuser is allowed to talk to us over this socket + StructUcred credentials = + Libcore.os.getsockoptUcred(peerFd, SOL_SOCKET, SO_PEERCRED); + if (credentials.uid == 0) { + // the reporting thread may take responsibility for + // acking the debugger; make sure we play along. + consumeNativeCrashData(peerFd); + } + } + } catch (Exception e) { + Slog.w(TAG, "Error handling connection", e); + } finally { + // Always ack debuggerd's connection to us. The actual + // byte written is irrelevant. + if (peerFd != null) { + try { + Libcore.os.write(peerFd, ackSignal, 0, 1); + } catch (Exception e) { /* we don't care about failures here */ } + } + } + } + } catch (Exception e) { + Slog.e(TAG, "Unable to init native debug socket!", e); + } + } + + static int unpackInt(byte[] buf, int offset) { + int b0, b1, b2, b3; + + b0 = ((int) buf[offset]) & 0xFF; // mask against sign extension + b1 = ((int) buf[offset+1]) & 0xFF; + b2 = ((int) buf[offset+2]) & 0xFF; + b3 = ((int) buf[offset+3]) & 0xFF; + return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + } + + static int readExactly(FileDescriptor fd, byte[] buffer, int offset, int numBytes) + throws ErrnoException { + int totalRead = 0; + while (numBytes > 0) { + int n = Libcore.os.read(fd, buffer, offset + totalRead, numBytes); + if (n <= 0) { + if (DEBUG) { + Slog.w(TAG, "Needed " + numBytes + " but saw " + n); + } + return -1; // premature EOF or timeout + } + numBytes -= n; + totalRead += n; + } + return totalRead; + } + + // Read the crash report from the debuggerd connection + void consumeNativeCrashData(FileDescriptor fd) { + if (DEBUG) Slog.i(TAG, "debuggerd connected"); + final byte[] buf = new byte[4096]; + final ByteArrayOutputStream os = new ByteArrayOutputStream(4096); + + try { + StructTimeval timeout = StructTimeval.fromMillis(SOCKET_TIMEOUT_MILLIS); + Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, timeout); + Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, timeout); + + // first, the pid and signal number + int headerBytes = readExactly(fd, buf, 0, 8); + if (headerBytes != 8) { + // protocol failure; give up + Slog.e(TAG, "Unable to read from debuggerd"); + return; + } + + int pid = unpackInt(buf, 0); + int signal = unpackInt(buf, 4); + if (DEBUG) { + Slog.v(TAG, "Read pid=" + pid + " signal=" + signal); + } + + // now the text of the dump + if (pid > 0) { + final ProcessRecord pr; + synchronized (mAm.mPidsSelfLocked) { + pr = mAm.mPidsSelfLocked.get(pid); + } + if (pr != null) { + int bytes; + do { + // get some data + bytes = Libcore.os.read(fd, buf, 0, buf.length); + if (bytes > 0) { + if (DEBUG) { + String s = new String(buf, 0, bytes, "UTF-8"); + Slog.v(TAG, "READ=" + bytes + "> " + s); + } + // did we just get the EOD null byte? + if (buf[bytes-1] == 0) { + os.write(buf, 0, bytes-1); // exclude the EOD token + break; + } + // no EOD, so collect it and read more + os.write(buf, 0, bytes); + } + } while (bytes > 0); + + // Okay, we've got the report. + if (DEBUG) Slog.v(TAG, "processing"); + + // Mark the process record as being a native crash so that the + // cleanup mechanism knows we're still submitting the report + // even though the process will vanish as soon as we let + // debuggerd proceed. + synchronized (mAm) { + pr.crashing = true; + pr.forceCrashReport = true; + } + + // Crash reporting is synchronous but we want to let debuggerd + // go about it business right away, so we spin off the actual + // reporting logic on a thread and let it take it's time. + final String reportString = new String(os.toByteArray(), "UTF-8"); + (new NativeCrashReporter(pr, signal, reportString)).start(); + } else { + Slog.w(TAG, "Couldn't find ProcessRecord for pid " + pid); + } + } else { + Slog.e(TAG, "Bogus pid!"); + } + } catch (Exception e) { + Slog.e(TAG, "Exception dealing with report", e); + // ugh, fail. + } + } + +} diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java index a32af2f..7929f96 100644 --- a/services/java/com/android/server/am/ProcessRecord.java +++ b/services/java/com/android/server/am/ProcessRecord.java @@ -138,6 +138,7 @@ class ProcessRecord { boolean persistent; // always keep this application running? boolean crashing; // are we in the process of crashing? Dialog crashDialog; // dialog being displayed due to crash. + boolean forceCrashReport; // suppress normal auto-dismiss of crash dialog & report UI? boolean notResponding; // does the app have a not responding dialog? Dialog anrDialog; // dialog being displayed due to app not resp. boolean removed; // has app package been removed from device? diff --git a/services/java/com/android/server/firewall/AndFilter.java b/services/java/com/android/server/firewall/AndFilter.java new file mode 100644 index 0000000..cabf00b --- /dev/null +++ b/services/java/com/android/server/firewall/AndFilter.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +class AndFilter extends FilterList { + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp) { + for (int i=0; i<children.size(); i++) { + if (!children.get(i).matches(ifw, intent, callerApp, callerPackage, callerUid, + callerPid, resolvedType, resolvedApp)) { + return false; + } + } + return true; + } + + public static final FilterFactory FACTORY = new FilterFactory("and") { + @Override + public Filter newFilter(XmlPullParser parser) + throws IOException, XmlPullParserException { + return new AndFilter().readFromXml(parser); + } + }; +} diff --git a/services/java/com/android/server/firewall/CategoryFilter.java b/services/java/com/android/server/firewall/CategoryFilter.java new file mode 100644 index 0000000..d5e9fe8 --- /dev/null +++ b/services/java/com/android/server/firewall/CategoryFilter.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.Set; + +class CategoryFilter implements Filter { + private static final String ATTR_NAME = "name"; + + private final String mCategoryName; + + private CategoryFilter(String categoryName) { + mCategoryName = categoryName; + } + + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, String callerPackage, + int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) { + Set<String> categories = intent.getCategories(); + if (categories == null) { + return false; + } + return categories.contains(mCategoryName); + } + + public static final FilterFactory FACTORY = new FilterFactory("category") { + @Override + public Filter newFilter(XmlPullParser parser) + throws IOException, XmlPullParserException { + String categoryName = parser.getAttributeValue(null, ATTR_NAME); + if (categoryName == null) { + throw new XmlPullParserException("Category name must be specified.", + parser, null); + } + return new CategoryFilter(categoryName); + } + }; +} diff --git a/services/java/com/android/server/firewall/Filter.java b/services/java/com/android/server/firewall/Filter.java new file mode 100644 index 0000000..7639466 --- /dev/null +++ b/services/java/com/android/server/firewall/Filter.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; + +interface Filter { + /** + * Does the given intent + context info match this filter? + * + * @param ifw The IntentFirewall instance + * @param intent The intent being started/bound/broadcast + * @param callerApp An ApplicationInfo of an application in the caller's process. This may not + * be the specific app that is actually sending the intent. This also may be + * null, if the caller is the system process, or an unrecognized process (e.g. + * am start) + * @param callerPackage The package name of the component sending the intent. This value is +* provided by the caller and might be forged/faked. + * @param callerUid + * @param callerPid + * @param resolvedType The resolved mime type of the intent + * @param resolvedApp The application that contains the resolved component that the intent is + */ + boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp); +} diff --git a/services/java/com/android/server/firewall/FilterFactory.java b/services/java/com/android/server/firewall/FilterFactory.java new file mode 100644 index 0000000..dea8b40 --- /dev/null +++ b/services/java/com/android/server/firewall/FilterFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2013 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.firewall; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +public abstract class FilterFactory { + private final String mTag; + + protected FilterFactory(String tag) { + if (tag == null) { + throw new NullPointerException(); + } + mTag = tag; + } + + public String getTagName() { + return mTag; + } + + public abstract Filter newFilter(XmlPullParser parser) + throws IOException, XmlPullParserException; +} diff --git a/services/java/com/android/server/firewall/FilterList.java b/services/java/com/android/server/firewall/FilterList.java new file mode 100644 index 0000000..d34b203 --- /dev/null +++ b/services/java/com/android/server/firewall/FilterList.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 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.firewall; + +import com.android.internal.util.XmlUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; + +abstract class FilterList implements Filter { + protected final ArrayList<Filter> children = new ArrayList<Filter>(); + + public FilterList readFromXml(XmlPullParser parser) throws IOException, XmlPullParserException { + int outerDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + readChild(parser); + } + return this; + } + + protected void readChild(XmlPullParser parser) throws IOException, XmlPullParserException { + Filter filter = IntentFirewall.parseFilter(parser); + children.add(filter); + } +} diff --git a/services/java/com/android/server/firewall/IntentFirewall.java b/services/java/com/android/server/firewall/IntentFirewall.java new file mode 100644 index 0000000..062183b --- /dev/null +++ b/services/java/com/android/server/firewall/IntentFirewall.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Environment; +import android.os.ServiceManager; +import android.util.Slog; +import android.util.Xml; +import com.android.internal.util.XmlUtils; +import com.android.server.IntentResolver; +import com.android.server.pm.PackageManagerService; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class IntentFirewall { + private static final String TAG = "IntentFirewall"; + + // e.g. /data/system/ifw/ifw.xml or /data/secure/system/ifw/ifw.xml + private static final File RULES_FILE = + new File(Environment.getSystemSecureDirectory(), "ifw/ifw.xml"); + + private static final String TAG_RULES = "rules"; + private static final String TAG_ACTIVITY = "activity"; + private static final String TAG_SERVICE = "service"; + private static final String TAG_BROADCAST = "broadcast"; + + private static final HashMap<String, FilterFactory> factoryMap; + + private final AMSInterface mAms; + + private final IntentResolver<FirewallIntentFilter, Rule> mActivityResolver = + new FirewallIntentResolver(); + private final IntentResolver<FirewallIntentFilter, Rule> mServiceResolver = + new FirewallIntentResolver(); + private final IntentResolver<FirewallIntentFilter, Rule> mBroadcastResolver = + new FirewallIntentResolver(); + + static { + FilterFactory[] factories = new FilterFactory[] { + AndFilter.FACTORY, + OrFilter.FACTORY, + NotFilter.FACTORY, + + StringFilter.ACTION, + StringFilter.COMPONENT, + StringFilter.COMPONENT_NAME, + StringFilter.COMPONENT_PACKAGE, + StringFilter.DATA, + StringFilter.HOST, + StringFilter.MIME_TYPE, + StringFilter.PATH, + StringFilter.SENDER_PACKAGE, + StringFilter.SSP, + + CategoryFilter.FACTORY, + SenderFilter.FACTORY, + SenderPermissionFilter.FACTORY, + PortFilter.FACTORY + }; + + // load factor ~= .75 + factoryMap = new HashMap<String, FilterFactory>(factories.length * 4 / 3); + for (int i=0; i<factories.length; i++) { + FilterFactory factory = factories[i]; + factoryMap.put(factory.getTagName(), factory); + } + } + + public IntentFirewall(AMSInterface ams) { + mAms = ams; + readRules(getRulesFile()); + } + + public boolean checkStartActivity(Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ActivityInfo resolvedActivity) { + List<Rule> matchingRules = mActivityResolver.queryIntent(intent, resolvedType, false, 0); + boolean log = false; + boolean block = false; + + for (int i=0; i< matchingRules.size(); i++) { + Rule rule = matchingRules.get(i); + if (rule.matches(this, intent, callerApp, callerPackage, callerUid, callerPid, + resolvedType, resolvedActivity.applicationInfo)) { + block |= rule.getBlock(); + log |= rule.getLog(); + + // if we've already determined that we should both block and log, there's no need + // to continue trying rules + if (block && log) { + break; + } + } + } + + if (log) { + // TODO: log info about intent to event log + } + + return !block; + } + + public static File getRulesFile() { + return RULES_FILE; + } + + private void readRules(File rulesFile) { + FileInputStream fis; + try { + fis = new FileInputStream(rulesFile); + } catch (FileNotFoundException ex) { + // Nope, no rules. Nothing else to do! + return; + } + + try { + XmlPullParser parser = Xml.newPullParser(); + + parser.setInput(fis, null); + + XmlUtils.beginDocument(parser, TAG_RULES); + + int outerDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + IntentResolver<FirewallIntentFilter, Rule> resolver = null; + String tagName = parser.getName(); + if (tagName.equals(TAG_ACTIVITY)) { + resolver = mActivityResolver; + } else if (tagName.equals(TAG_SERVICE)) { + resolver = mServiceResolver; + } else if (tagName.equals(TAG_BROADCAST)) { + resolver = mBroadcastResolver; + } + + if (resolver != null) { + Rule rule = new Rule(); + + try { + rule.readFromXml(parser); + } catch (XmlPullParserException ex) { + Slog.e(TAG, "Error reading intent firewall rule", ex); + continue; + } catch (IOException ex) { + Slog.e(TAG, "Error reading intent firewall rule", ex); + continue; + } + + for (int i=0; i<rule.getIntentFilterCount(); i++) { + resolver.addFilter(rule.getIntentFilter(i)); + } + } + } + } catch (XmlPullParserException ex) { + Slog.e(TAG, "Error reading intent firewall rules", ex); + } catch (IOException ex) { + Slog.e(TAG, "Error reading intent firewall rules", ex); + } finally { + try { + fis.close(); + } catch (IOException ex) { + Slog.e(TAG, "Error while closing " + rulesFile, ex); + } + } + } + + static Filter parseFilter(XmlPullParser parser) throws IOException, XmlPullParserException { + String elementName = parser.getName(); + + FilterFactory factory = factoryMap.get(elementName); + + if (factory == null) { + throw new XmlPullParserException("Unknown element in filter list: " + elementName); + } + return factory.newFilter(parser); + } + + private static class Rule extends AndFilter { + private static final String TAG_INTENT_FILTER = "intent-filter"; + + private static final String ATTR_BLOCK = "block"; + private static final String ATTR_LOG = "log"; + + private final ArrayList<FirewallIntentFilter> mIntentFilters = + new ArrayList<FirewallIntentFilter>(1); + private boolean block; + private boolean log; + + @Override + public Rule readFromXml(XmlPullParser parser) throws IOException, XmlPullParserException { + block = Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_BLOCK)); + log = Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_LOG)); + + super.readFromXml(parser); + return this; + } + + @Override + protected void readChild(XmlPullParser parser) throws IOException, XmlPullParserException { + if (parser.getName().equals(TAG_INTENT_FILTER)) { + FirewallIntentFilter intentFilter = new FirewallIntentFilter(this); + intentFilter.readFromXml(parser); + mIntentFilters.add(intentFilter); + } else { + super.readChild(parser); + } + } + + public int getIntentFilterCount() { + return mIntentFilters.size(); + } + + public FirewallIntentFilter getIntentFilter(int index) { + return mIntentFilters.get(index); + } + + public boolean getBlock() { + return block; + } + + public boolean getLog() { + return log; + } + } + + private static class FirewallIntentFilter extends IntentFilter { + private final Rule rule; + + public FirewallIntentFilter(Rule rule) { + this.rule = rule; + } + } + + private static class FirewallIntentResolver + extends IntentResolver<FirewallIntentFilter, Rule> { + @Override + protected boolean allowFilterResult(FirewallIntentFilter filter, List<Rule> dest) { + return !dest.contains(filter.rule); + } + + @Override + protected boolean isPackageForFilter(String packageName, FirewallIntentFilter filter) { + return true; + } + + @Override + protected FirewallIntentFilter[] newArray(int size) { + return new FirewallIntentFilter[size]; + } + + @Override + protected Rule newResult(FirewallIntentFilter filter, int match, int userId) { + return filter.rule; + } + + @Override + protected void sortResults(List<Rule> results) { + // there's no need to sort the results + return; + } + } + + /** + * This interface contains the methods we need from ActivityManagerService. This allows AMS to + * export these methods to us without making them public, and also makes it easier to test this + * component. + */ + public interface AMSInterface { + int checkComponentPermission(String permission, int pid, int uid, + int owningUid, boolean exported); + } + + /** + * Checks if the caller has access to a component + * + * @param permission If present, the caller must have this permission + * @param pid The pid of the caller + * @param uid The uid of the caller + * @param owningUid The uid of the application that owns the component + * @param exported Whether the component is exported + * @return True if the caller can access the described component + */ + boolean checkComponentPermission(String permission, int pid, int uid, int owningUid, + boolean exported) { + return mAms.checkComponentPermission(permission, pid, uid, owningUid, exported) == + PackageManager.PERMISSION_GRANTED; + } + + boolean signaturesMatch(int uid1, int uid2) { + PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package"); + return pm.checkUidSignatures(uid1, uid2) == PackageManager.SIGNATURE_MATCH; + } +} diff --git a/services/java/com/android/server/firewall/NotFilter.java b/services/java/com/android/server/firewall/NotFilter.java new file mode 100644 index 0000000..2ff108a --- /dev/null +++ b/services/java/com/android/server/firewall/NotFilter.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import com.android.internal.util.XmlUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +class NotFilter implements Filter { + private final Filter mChild; + + private NotFilter(Filter child) { + mChild = child; + } + + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp) { + return !mChild.matches(ifw, intent, callerApp, callerPackage, callerUid, callerPid, + resolvedType, resolvedApp); + } + + public static final FilterFactory FACTORY = new FilterFactory("not") { + @Override + public Filter newFilter(XmlPullParser parser) + throws IOException, XmlPullParserException { + Filter child = null; + int outerDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + Filter filter = IntentFirewall.parseFilter(parser); + if (child == null) { + child = filter; + } else { + throw new XmlPullParserException( + "<not> tag can only contain a single child filter.", parser, null); + } + } + return new NotFilter(child); + } + }; +} diff --git a/services/java/com/android/server/firewall/OrFilter.java b/services/java/com/android/server/firewall/OrFilter.java new file mode 100644 index 0000000..1ed1c85 --- /dev/null +++ b/services/java/com/android/server/firewall/OrFilter.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +class OrFilter extends FilterList { + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp) { + for (int i=0; i<children.size(); i++) { + if (children.get(i).matches(ifw, intent, callerApp, callerPackage, callerUid, callerPid, + resolvedType, resolvedApp)) { + return true; + } + } + return false; + } + + public static final FilterFactory FACTORY = new FilterFactory("or") { + @Override + public Filter newFilter(XmlPullParser parser) + throws IOException, XmlPullParserException { + return new OrFilter().readFromXml(parser); + } + }; +} diff --git a/services/java/com/android/server/firewall/PortFilter.java b/services/java/com/android/server/firewall/PortFilter.java new file mode 100644 index 0000000..2b2a198 --- /dev/null +++ b/services/java/com/android/server/firewall/PortFilter.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.net.Uri; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +class PortFilter implements Filter { + private static final String ATTR_EQUALS = "equals"; + private static final String ATTR_MIN = "min"; + private static final String ATTR_MAX = "max"; + + private static final int NO_BOUND = -1; + + // both bounds are inclusive + private final int mLowerBound; + private final int mUpperBound; + + private PortFilter(int lowerBound, int upperBound) { + mLowerBound = lowerBound; + mUpperBound = upperBound; + } + + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp) { + int port = -1; + Uri uri = intent.getData(); + if (uri != null) { + port = uri.getPort(); + } + return port != -1 && + (mLowerBound == NO_BOUND || mLowerBound <= port) && + (mUpperBound == NO_BOUND || mUpperBound >= port); + } + + public static final FilterFactory FACTORY = new FilterFactory("port") { + @Override + public Filter newFilter(XmlPullParser parser) + throws IOException, XmlPullParserException { + int lowerBound = NO_BOUND; + int upperBound = NO_BOUND; + + String equalsValue = parser.getAttributeValue(null, ATTR_EQUALS); + if (equalsValue != null) { + int value; + try { + value = Integer.parseInt(equalsValue); + } catch (NumberFormatException ex) { + throw new XmlPullParserException("Invalid port value: " + equalsValue, + parser, null); + } + lowerBound = value; + upperBound = value; + } + + String lowerBoundString = parser.getAttributeValue(null, ATTR_MIN); + String upperBoundString = parser.getAttributeValue(null, ATTR_MAX); + if (lowerBoundString != null || upperBoundString != null) { + if (equalsValue != null) { + throw new XmlPullParserException( + "Port filter cannot use both equals and range filtering", + parser, null); + } + + if (lowerBoundString != null) { + try { + lowerBound = Integer.parseInt(lowerBoundString); + } catch (NumberFormatException ex) { + throw new XmlPullParserException( + "Invalid minimum port value: " + lowerBoundString, + parser, null); + } + } + + if (upperBoundString != null) { + try { + upperBound = Integer.parseInt(upperBoundString); + } catch (NumberFormatException ex) { + throw new XmlPullParserException( + "Invalid maximum port value: " + upperBoundString, + parser, null); + } + } + } + + // an empty port filter is explicitly allowed, and checks for the existence of a port + return new PortFilter(lowerBound, upperBound); + } + }; +} diff --git a/services/java/com/android/server/firewall/SenderFilter.java b/services/java/com/android/server/firewall/SenderFilter.java new file mode 100644 index 0000000..0b790bd --- /dev/null +++ b/services/java/com/android/server/firewall/SenderFilter.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.os.Process; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +class SenderFilter { + private static final String ATTR_TYPE = "type"; + + private static final String VAL_SIGNATURE = "signature"; + private static final String VAL_SYSTEM = "system"; + private static final String VAL_SYSTEM_OR_SIGNATURE = "system|signature"; + private static final String VAL_USER_ID = "userId"; + + static boolean isSystemApp(ApplicationInfo callerApp, int callerUid, int callerPid) { + if (callerUid == Process.SYSTEM_UID || + callerPid == Process.myPid()) { + return true; + } + if (callerApp == null) { + return false; + } + return (callerApp.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } + + public static final FilterFactory FACTORY = new FilterFactory("sender") { + @Override + public Filter newFilter(XmlPullParser parser) throws IOException, XmlPullParserException { + String typeString = parser.getAttributeValue(null, ATTR_TYPE); + if (typeString == null) { + throw new XmlPullParserException("type attribute must be specified for <sender>", + parser, null); + } + if (typeString.equals(VAL_SYSTEM)) { + return SYSTEM; + } else if (typeString.equals(VAL_SIGNATURE)) { + return SIGNATURE; + } else if (typeString.equals(VAL_SYSTEM_OR_SIGNATURE)) { + return SYSTEM_OR_SIGNATURE; + } else if (typeString.equals(VAL_USER_ID)) { + return USER_ID; + } + throw new XmlPullParserException( + "Invalid type attribute for <sender>: " + typeString, parser, null); + } + }; + + private static final Filter SIGNATURE = new Filter() { + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp) { + if (callerApp == null) { + return false; + } + return ifw.signaturesMatch(callerUid, resolvedApp.uid); + } + }; + + private static final Filter SYSTEM = new Filter() { + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp) { + if (callerApp == null) { + // if callerApp is null, the caller is the system process + return false; + } + return isSystemApp(callerApp, callerUid, callerPid); + } + }; + + private static final Filter SYSTEM_OR_SIGNATURE = new Filter() { + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp) { + return isSystemApp(callerApp, callerUid, callerPid) || + ifw.signaturesMatch(callerUid, resolvedApp.uid); + } + }; + + private static final Filter USER_ID = new Filter() { + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp) { + // This checks whether the caller is either the system process, or has the same user id + // I.e. the same app, or an app that uses the same shared user id. + // This is the same set of applications that would be able to access the component if + // it wasn't exported. + return ifw.checkComponentPermission(null, callerPid, callerUid, resolvedApp.uid, false); + } + }; +} diff --git a/services/java/com/android/server/firewall/SenderPermissionFilter.java b/services/java/com/android/server/firewall/SenderPermissionFilter.java new file mode 100644 index 0000000..02d8b15 --- /dev/null +++ b/services/java/com/android/server/firewall/SenderPermissionFilter.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +class SenderPermissionFilter implements Filter { + private static final String ATTR_NAME = "name"; + + private final String mPermission; + + private SenderPermissionFilter(String permission) { + mPermission = permission; + } + + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, + String callerPackage, int callerUid, int callerPid, String resolvedType, + ApplicationInfo resolvedApp) { + // We assume the component is exported here. If the component is not exported, then + // ActivityManager would only resolve to this component for callers from the same uid. + // In this case, it doesn't matter whether the component is exported or not. + return ifw.checkComponentPermission(mPermission, callerPid, callerUid, resolvedApp.uid, + true); + } + + public static final FilterFactory FACTORY = new FilterFactory("sender-permission") { + @Override + public Filter newFilter(XmlPullParser parser) + throws IOException, XmlPullParserException { + String permission = parser.getAttributeValue(null, ATTR_NAME); + if (permission == null) { + throw new XmlPullParserException("Permission name must be specified.", + parser, null); + } + return new SenderPermissionFilter(permission); + } + }; +} diff --git a/services/java/com/android/server/firewall/StringFilter.java b/services/java/com/android/server/firewall/StringFilter.java new file mode 100644 index 0000000..de5a69f --- /dev/null +++ b/services/java/com/android/server/firewall/StringFilter.java @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2013 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.firewall; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.net.Uri; +import android.os.PatternMatcher; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.regex.Pattern; + +abstract class StringFilter implements Filter { + private static final String ATTR_EQUALS = "equals"; + private static final String ATTR_STARTS_WITH = "startsWith"; + private static final String ATTR_CONTAINS = "contains"; + private static final String ATTR_PATTERN = "pattern"; + private static final String ATTR_REGEX = "regex"; + private static final String ATTR_IS_NULL = "isNull"; + + private final ValueProvider mValueProvider; + + private StringFilter(ValueProvider valueProvider) { + this.mValueProvider = valueProvider; + } + + /** + * Constructs a new StringFilter based on the string filter attribute on the current + * element, and the given StringValueMatcher. + * + * The current node should contain exactly 1 string filter attribute. E.g. equals, + * contains, etc. Otherwise, an XmlPullParserException will be thrown. + * + * @param parser An XmlPullParser object positioned at an element that should + * contain a string filter attribute + * @return This StringFilter object + */ + public static StringFilter readFromXml(ValueProvider valueProvider, XmlPullParser parser) + throws IOException, XmlPullParserException { + StringFilter filter = null; + + for (int i=0; i<parser.getAttributeCount(); i++) { + StringFilter newFilter = getFilter(valueProvider, parser, i); + if (newFilter != null) { + if (filter != null) { + throw new XmlPullParserException("Multiple string filter attributes found"); + } + filter = newFilter; + } + } + + if (filter == null) { + // if there are no string filter attributes, we default to isNull="false" so that an + // empty filter is equivalent to an existence check + filter = new IsNullFilter(valueProvider, false); + } + + return filter; + } + + private static StringFilter getFilter(ValueProvider valueProvider, XmlPullParser parser, + int attributeIndex) { + String attributeName = parser.getAttributeName(attributeIndex); + + switch (attributeName.charAt(0)) { + case 'e': + if (!attributeName.equals(ATTR_EQUALS)) { + return null; + } + return new EqualsFilter(valueProvider, parser.getAttributeValue(attributeIndex)); + case 'i': + if (!attributeName.equals(ATTR_IS_NULL)) { + return null; + } + return new IsNullFilter(valueProvider, parser.getAttributeValue(attributeIndex)); + case 's': + if (!attributeName.equals(ATTR_STARTS_WITH)) { + return null; + } + return new StartsWithFilter(valueProvider, + parser.getAttributeValue(attributeIndex)); + case 'c': + if (!attributeName.equals(ATTR_CONTAINS)) { + return null; + } + return new ContainsFilter(valueProvider, parser.getAttributeValue(attributeIndex)); + case 'p': + if (!attributeName.equals(ATTR_PATTERN)) { + return null; + } + return new PatternStringFilter(valueProvider, + parser.getAttributeValue(attributeIndex)); + case 'r': + if (!attributeName.equals(ATTR_REGEX)) { + return null; + } + return new RegexFilter(valueProvider, parser.getAttributeValue(attributeIndex)); + } + return null; + } + + protected abstract boolean matchesValue(String value); + + @Override + public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, String callerPackage, + int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) { + String value = mValueProvider.getValue(intent, callerApp, callerPackage, resolvedType, + resolvedApp); + return matchesValue(value); + } + + private static abstract class ValueProvider extends FilterFactory { + protected ValueProvider(String tag) { + super(tag); + } + + public Filter newFilter(XmlPullParser parser) + throws IOException, XmlPullParserException { + return StringFilter.readFromXml(this, parser); + } + + public abstract String getValue(Intent intent, ApplicationInfo callerApp, + String callerPackage, String resolvedType, ApplicationInfo resolvedApp); + } + + private static class EqualsFilter extends StringFilter { + private final String mFilterValue; + + public EqualsFilter(ValueProvider valueProvider, String attrValue) { + super(valueProvider); + mFilterValue = attrValue; + } + + @Override + public boolean matchesValue(String value) { + return value != null && value.equals(mFilterValue); + } + } + + private static class ContainsFilter extends StringFilter { + private final String mFilterValue; + + public ContainsFilter(ValueProvider valueProvider, String attrValue) { + super(valueProvider); + mFilterValue = attrValue; + } + + @Override + public boolean matchesValue(String value) { + return value != null && value.contains(mFilterValue); + } + } + + private static class StartsWithFilter extends StringFilter { + private final String mFilterValue; + + public StartsWithFilter(ValueProvider valueProvider, String attrValue) { + super(valueProvider); + mFilterValue = attrValue; + } + + @Override + public boolean matchesValue(String value) { + return value != null && value.startsWith(mFilterValue); + } + } + + private static class PatternStringFilter extends StringFilter { + private final PatternMatcher mPattern; + + public PatternStringFilter(ValueProvider valueProvider, String attrValue) { + super(valueProvider); + mPattern = new PatternMatcher(attrValue, PatternMatcher.PATTERN_SIMPLE_GLOB); + } + + @Override + public boolean matchesValue(String value) { + return value != null && mPattern.match(value); + } + } + + private static class RegexFilter extends StringFilter { + private final Pattern mPattern; + + public RegexFilter(ValueProvider valueProvider, String attrValue) { + super(valueProvider); + this.mPattern = Pattern.compile(attrValue); + } + + @Override + public boolean matchesValue(String value) { + return value != null && mPattern.matcher(value).matches(); + } + } + + private static class IsNullFilter extends StringFilter { + private final boolean mIsNull; + + public IsNullFilter(ValueProvider valueProvider, String attrValue) { + super(valueProvider); + mIsNull = Boolean.parseBoolean(attrValue); + } + + public IsNullFilter(ValueProvider valueProvider, boolean isNull) { + super(valueProvider); + mIsNull = isNull; + } + + @Override + public boolean matchesValue(String value) { + return (value == null) == mIsNull; + } + } + + public static final ValueProvider COMPONENT = new ValueProvider("component") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + ComponentName cn = intent.getComponent(); + if (cn != null) { + return cn.flattenToString(); + } + return null; + } + }; + + public static final ValueProvider COMPONENT_NAME = new ValueProvider("component-name") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + ComponentName cn = intent.getComponent(); + if (cn != null) { + return cn.getClassName(); + } + return null; + } + }; + + public static final ValueProvider COMPONENT_PACKAGE = new ValueProvider("component-package") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + ComponentName cn = intent.getComponent(); + if (cn != null) { + return cn.getPackageName(); + } + return null; + } + }; + + public static final ValueProvider SENDER_PACKAGE = new ValueProvider("sender-package") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + // TODO: We can't trust this value, so maybe should check all packages in the caller process? + return callerPackage; + } + }; + + + public static final FilterFactory ACTION = new ValueProvider("action") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + return intent.getAction(); + } + }; + + public static final ValueProvider DATA = new ValueProvider("data") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + Uri data = intent.getData(); + if (data != null) { + return data.toString(); + } + return null; + } + }; + + public static final ValueProvider MIME_TYPE = new ValueProvider("mime-type") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + return resolvedType; + } + }; + + public static final ValueProvider SCHEME = new ValueProvider("scheme") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + Uri data = intent.getData(); + if (data != null) { + return data.getScheme(); + } + return null; + } + }; + + public static final ValueProvider SSP = new ValueProvider("scheme-specific-part") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + Uri data = intent.getData(); + if (data != null) { + return data.getSchemeSpecificPart(); + } + return null; + } + }; + + public static final ValueProvider HOST = new ValueProvider("host") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + Uri data = intent.getData(); + if (data != null) { + return data.getHost(); + } + return null; + } + }; + + public static final ValueProvider PATH = new ValueProvider("path") { + @Override + public String getValue(Intent intent, ApplicationInfo callerApp, String callerPackage, + String resolvedType, ApplicationInfo resolvedApp) { + Uri data = intent.getData(); + if (data != null) { + return data.getPath(); + } + return null; + } + }; +} diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index dc5916b..ca7bba2 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -110,8 +110,10 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; -import android.os.UserManager; import android.os.Environment.UserEnvironment; +import android.os.UserManager; +import android.provider.Settings.Secure; +import android.security.KeyStore; import android.security.SystemKeyStore; import android.util.DisplayMetrics; import android.util.EventLog; @@ -1323,6 +1325,12 @@ public class PackageManagerService extends IPackageManager.Stub { ? (UPDATE_PERMISSIONS_REPLACE_PKG|UPDATE_PERMISSIONS_REPLACE_ALL) : 0)); + // If this is the first boot, and it is a normal boot, then + // we need to initialize the default preferred apps. + if (!mRestoredSettings && !onlyCore) { + mSettings.readDefaultPreferredAppsLPw(this, 0); + } + // can downgrade to reader mSettings.writeLPr(); @@ -5114,141 +5122,90 @@ public class PackageManagerService extends IPackageManager.Stub { Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp); } } - if (bp != null && bp.packageSetting != null) { - final String perm = bp.name; - boolean allowed; - boolean allowedSig = false; - final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE; - if (level == PermissionInfo.PROTECTION_NORMAL - || level == PermissionInfo.PROTECTION_DANGEROUS) { - // If the permission is required, or it's optional and was previously - // granted to the application, then allow it. Otherwise deny. - allowed = (required || origPermissions.contains(perm)); - } else if (bp.packageSetting == null) { - // This permission is invalid; skip it. - allowed = false; - } else if (level == PermissionInfo.PROTECTION_SIGNATURE) { - allowed = (compareSignatures( - bp.packageSetting.signatures.mSignatures, pkg.mSignatures) - == PackageManager.SIGNATURE_MATCH) - || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures) - == PackageManager.SIGNATURE_MATCH); - if (!allowed && (bp.protectionLevel - & PermissionInfo.PROTECTION_FLAG_SYSTEM) != 0) { - if (isSystemApp(pkg)) { - // For updated system applications, a system permission - // is granted only if it had been defined by the original application. - if (isUpdatedSystemApp(pkg)) { - final PackageSetting sysPs = mSettings - .getDisabledSystemPkgLPr(pkg.packageName); - final GrantedPermissions origGp = sysPs.sharedUser != null - ? sysPs.sharedUser : sysPs; - if (origGp.grantedPermissions.contains(perm)) { - allowed = true; - } else { - // The system apk may have been updated with an older - // version of the one on the data partition, but which - // granted a new system permission that it didn't have - // before. In this case we do want to allow the app to - // now get the new permission, because it is allowed by - // the system image. - allowed = false; - if (sysPs.pkg != null) { - for (int j=0; - j<sysPs.pkg.requestedPermissions.size(); j++) { - if (perm.equals( - sysPs.pkg.requestedPermissions.get(j))) { - allowed = true; - break; - } - } - } - } - } else { - allowed = true; - } - } - } - if (!allowed && (bp.protectionLevel - & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) { - // For development permissions, a development permission - // is granted only if it was already granted. - allowed = origPermissions.contains(perm); - } - if (allowed) { - allowedSig = true; - } - } else { - allowed = false; + + if (bp == null || bp.packageSetting == null) { + Slog.w(TAG, "Unknown permission " + name + + " in package " + pkg.packageName); + continue; + } + + final String perm = bp.name; + boolean allowed; + boolean allowedSig = false; + final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE; + if (level == PermissionInfo.PROTECTION_NORMAL + || level == PermissionInfo.PROTECTION_DANGEROUS) { + // We grant a normal or dangerous permission if any of the following + // are true: + // 1) The permission is required + // 2) The permission is optional, but was granted in the past + // 3) The permission is optional, but was requested by an + // app in /system (not /data) + // + // Otherwise, reject the permission. + allowed = (required || origPermissions.contains(perm) + || (isSystemApp(ps) && !isUpdatedSystemApp(ps))); + } else if (bp.packageSetting == null) { + // This permission is invalid; skip it. + allowed = false; + } else if (level == PermissionInfo.PROTECTION_SIGNATURE) { + allowed = grantSignaturePermission(perm, pkg, bp, origPermissions); + if (allowed) { + allowedSig = true; } - if (DEBUG_INSTALL) { - if (gp != ps) { - Log.i(TAG, "Package " + pkg.packageName + " granting " + perm); + } else { + allowed = false; + } + if (DEBUG_INSTALL) { + if (gp != ps) { + Log.i(TAG, "Package " + pkg.packageName + " granting " + perm); + } + } + if (allowed) { + if (!isSystemApp(ps) && ps.permissionsFixed) { + // If this is an existing, non-system package, then + // we can't add any new permissions to it. + if (!allowedSig && !gp.grantedPermissions.contains(perm)) { + // Except... if this is a permission that was added + // to the platform (note: need to only do this when + // updating the platform). + allowed = isNewPlatformPermissionForPackage(perm, pkg); } } if (allowed) { - if ((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0 - && ps.permissionsFixed) { - // If this is an existing, non-system package, then - // we can't add any new permissions to it. - if (!allowedSig && !gp.grantedPermissions.contains(perm)) { - allowed = false; - // Except... if this is a permission that was added - // to the platform (note: need to only do this when - // updating the platform). - final int NP = PackageParser.NEW_PERMISSIONS.length; - for (int ip=0; ip<NP; ip++) { - final PackageParser.NewPermissionInfo npi - = PackageParser.NEW_PERMISSIONS[ip]; - if (npi.name.equals(perm) - && pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) { - allowed = true; - Log.i(TAG, "Auto-granting " + perm + " to old pkg " - + pkg.packageName); - break; - } - } - } - } - if (allowed) { - if (!gp.grantedPermissions.contains(perm)) { - changedPermission = true; - gp.grantedPermissions.add(perm); - gp.gids = appendInts(gp.gids, bp.gids); - } else if (!ps.haveGids) { - gp.gids = appendInts(gp.gids, bp.gids); - } - } else { - Slog.w(TAG, "Not granting permission " + perm - + " to package " + pkg.packageName - + " because it was previously installed without"); - } - } else { - if (gp.grantedPermissions.remove(perm)) { + if (!gp.grantedPermissions.contains(perm)) { changedPermission = true; - gp.gids = removeInts(gp.gids, bp.gids); - Slog.i(TAG, "Un-granting permission " + perm - + " from package " + pkg.packageName - + " (protectionLevel=" + bp.protectionLevel - + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags) - + ")"); - } else { - Slog.w(TAG, "Not granting permission " + perm - + " to package " + pkg.packageName - + " (protectionLevel=" + bp.protectionLevel - + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags) - + ")"); + gp.grantedPermissions.add(perm); + gp.gids = appendInts(gp.gids, bp.gids); + } else if (!ps.haveGids) { + gp.gids = appendInts(gp.gids, bp.gids); } + } else { + Slog.w(TAG, "Not granting permission " + perm + + " to package " + pkg.packageName + + " because it was previously installed without"); } } else { - Slog.w(TAG, "Unknown permission " + name - + " in package " + pkg.packageName); + if (gp.grantedPermissions.remove(perm)) { + changedPermission = true; + gp.gids = removeInts(gp.gids, bp.gids); + Slog.i(TAG, "Un-granting permission " + perm + + " from package " + pkg.packageName + + " (protectionLevel=" + bp.protectionLevel + + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags) + + ")"); + } else { + Slog.w(TAG, "Not granting permission " + perm + + " to package " + pkg.packageName + + " (protectionLevel=" + bp.protectionLevel + + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags) + + ")"); + } } } if ((changedPermission || replace) && !ps.permissionsFixed && - ((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) || - ((ps.pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)){ + !isSystemApp(ps) || isUpdatedSystemApp(ps)){ // This is the first that we have heard about this package, so the // permissions we have now selected are fixed until explicitly // changed. @@ -5256,7 +5213,77 @@ public class PackageManagerService extends IPackageManager.Stub { } ps.haveGids = true; } - + + private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) { + boolean allowed = false; + final int NP = PackageParser.NEW_PERMISSIONS.length; + for (int ip=0; ip<NP; ip++) { + final PackageParser.NewPermissionInfo npi + = PackageParser.NEW_PERMISSIONS[ip]; + if (npi.name.equals(perm) + && pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) { + allowed = true; + Log.i(TAG, "Auto-granting " + perm + " to old pkg " + + pkg.packageName); + break; + } + } + return allowed; + } + + private boolean grantSignaturePermission(String perm, PackageParser.Package pkg, + BasePermission bp, HashSet<String> origPermissions) { + boolean allowed; + allowed = (compareSignatures( + bp.packageSetting.signatures.mSignatures, pkg.mSignatures) + == PackageManager.SIGNATURE_MATCH) + || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures) + == PackageManager.SIGNATURE_MATCH); + if (!allowed && (bp.protectionLevel + & PermissionInfo.PROTECTION_FLAG_SYSTEM) != 0) { + if (isSystemApp(pkg)) { + // For updated system applications, a system permission + // is granted only if it had been defined by the original application. + if (isUpdatedSystemApp(pkg)) { + final PackageSetting sysPs = mSettings + .getDisabledSystemPkgLPr(pkg.packageName); + final GrantedPermissions origGp = sysPs.sharedUser != null + ? sysPs.sharedUser : sysPs; + if (origGp.grantedPermissions.contains(perm)) { + allowed = true; + } else { + // The system apk may have been updated with an older + // version of the one on the data partition, but which + // granted a new system permission that it didn't have + // before. In this case we do want to allow the app to + // now get the new permission, because it is allowed by + // the system image. + allowed = false; + if (sysPs.pkg != null) { + for (int j=0; + j<sysPs.pkg.requestedPermissions.size(); j++) { + if (perm.equals( + sysPs.pkg.requestedPermissions.get(j))) { + allowed = true; + break; + } + } + } + } + } else { + allowed = true; + } + } + } + if (!allowed && (bp.protectionLevel + & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) { + // For development permissions, a development permission + // is granted only if it was already granted. + allowed = origPermissions.contains(perm); + } + return allowed; + } + final class ActivityIntentResolver extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> { public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, @@ -5383,8 +5410,9 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override - protected String packageForFilter(PackageParser.ActivityIntentInfo info) { - return info.activity.owner.packageName; + protected boolean isPackageForFilter(String packageName, + PackageParser.ActivityIntentInfo info) { + return packageName.equals(info.activity.owner.packageName); } @Override @@ -5580,8 +5608,9 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override - protected String packageForFilter(PackageParser.ServiceIntentInfo info) { - return info.service.owner.packageName; + protected boolean isPackageForFilter(String packageName, + PackageParser.ServiceIntentInfo info) { + return packageName.equals(info.service.owner.packageName); } @Override @@ -8359,6 +8388,10 @@ public class PackageManagerService extends IPackageManager.Stub { return (ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0; } + private static boolean isUpdatedSystemApp(PackageSetting ps) { + return (ps.pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; + } + private static boolean isUpdatedSystemApp(PackageParser.Package pkg) { return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; } @@ -8614,6 +8647,17 @@ public class PackageManagerService extends IPackageManager.Stub { mSettings.writeLPr(); } } + // A user ID was deleted here. Go through all users and remove it from + // KeyStore. + final int appId = outInfo.removedAppId; + if (appId != -1) { + final KeyStore keyStore = KeyStore.getInstance(); + if (keyStore != null) { + for (final int userId : sUserManager.getUserIds()) { + keyStore.clearUid(UserHandle.getUid(userId, appId)); + } + } + } } /* diff --git a/services/java/com/android/server/pm/PreferredIntentResolver.java b/services/java/com/android/server/pm/PreferredIntentResolver.java index 3f1e50c..7fe6a05 100644 --- a/services/java/com/android/server/pm/PreferredIntentResolver.java +++ b/services/java/com/android/server/pm/PreferredIntentResolver.java @@ -27,8 +27,8 @@ public class PreferredIntentResolver return new PreferredActivity[size]; } @Override - protected String packageForFilter(PreferredActivity filter) { - return filter.mPref.mComponent.getPackageName(); + protected boolean isPackageForFilter(String packageName, PreferredActivity filter) { + return packageName.equals(filter.mPref.mComponent.getPackageName()); } @Override protected void dumpFilter(PrintWriter out, String prefix, diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java index 70183df..2e48074 100644 --- a/services/java/com/android/server/pm/Settings.java +++ b/services/java/com/android/server/pm/Settings.java @@ -1603,9 +1603,6 @@ final class Settings { mReadMessages.append("No settings file found\n"); PackageManagerService.reportSettingsProblem(Log.INFO, "No settings file; creating initial state"); - if (!onlyCore) { - readDefaultPreferredAppsLPw(service, 0); - } mInternalSdkPlatform = mExternalSdkPlatform = sdkVersion; return false; } diff --git a/services/java/com/android/server/updates/IntentFirewallInstallReceiver.java b/services/java/com/android/server/updates/IntentFirewallInstallReceiver.java new file mode 100644 index 0000000..9185903 --- /dev/null +++ b/services/java/com/android/server/updates/IntentFirewallInstallReceiver.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2013 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.updates; + +import com.android.server.firewall.IntentFirewall; + +public class IntentFirewallInstallReceiver extends ConfigUpdateInstallReceiver { + + public IntentFirewallInstallReceiver() { + super(IntentFirewall.getRulesFile().getParent(), IntentFirewall.getRulesFile().getName(), + "metadata/", "version"); + } +} diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 3c40238..af603fd 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -5802,6 +5802,19 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override + public void removeRotationWatcher(IRotationWatcher watcher) { + final IBinder watcherBinder = watcher.asBinder(); + synchronized (mWindowMap) { + for (int i=0; i<mRotationWatchers.size(); i++) { + if (watcherBinder == mRotationWatchers.get(i).asBinder()) { + mRotationWatchers.remove(i); + i--; + } + } + } + } + /** * Apps that use the compact menu panel (as controlled by the panelMenuIsCompact * theme attribute) on devices that feature a physical options menu key attempt to position |