Clear calling identity in BMS.backupNow
BMS.backupNow is called from GMSCore, which has a different calling
identity than the framework. This causes an IllegalArgumentException due
to uid mismatch when backupNow tries to schedule a job.
This occurs when the following conditions are combined:
1) Battery saver mode enabled
2) Network change detected
3) Backup pass is scheduled for now
Bug: 79441902
Test: 1) m -j RunFrameworksServicesRoboTests
2) Manual: Enabled battery saver, modified code to run backupNow with
each network change and overwrite previously scheduled KeyValueJob.
Then, change wifi to trigger scheduling the KeyValueJob.
Verified:
- IllegalStateException b/c of uid mismatch without change
- No exception and correct calling uid with change
Change-Id: Iac90cd435e3fc32ff5428236aa15507b36aa833d
diff --git a/services/backup/java/com/android/server/backup/BackupManagerConstants.java b/services/backup/java/com/android/server/backup/BackupManagerConstants.java
index dd6e6ab..ec21961 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerConstants.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerConstants.java
@@ -32,7 +32,7 @@
* <p>The backup manager constants are encoded as a key value list separated by commas and stored as
* a Settings.Secure.
*/
-class BackupManagerConstants extends KeyValueSettingObserver {
+public class BackupManagerConstants extends KeyValueSettingObserver {
private static final String TAG = "BackupManagerConstants";
private static final String SETTING = Settings.Secure.BACKUP_MANAGER_CONSTANTS;
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index d7db471..cd1bd67 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -353,6 +353,11 @@
mAlarmManager = alarmManager;
}
+ @VisibleForTesting
+ void setPowerManager(PowerManager powerManager) {
+ mPowerManager = powerManager;
+ }
+
public void setBackupManagerBinder(IBackupManager backupManagerBinder) {
mBackupManagerBinder = backupManagerBinder;
}
@@ -2410,25 +2415,30 @@
public void backupNow() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "backupNow");
- final PowerSaveState result =
- mPowerManager.getPowerSaveState(ServiceType.KEYVALUE_BACKUP);
- if (result.batterySaverEnabled) {
- if (DEBUG) Slog.v(TAG, "Not running backup while in battery save mode");
- KeyValueBackupJob.schedule(mContext, mConstants); // try again in several hours
- } else {
- if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass");
- synchronized (mQueueLock) {
- // Fire the intent that kicks off the whole shebang...
- try {
- mRunBackupIntent.send();
- } catch (PendingIntent.CanceledException e) {
- // should never happen
- Slog.e(TAG, "run-backup intent cancelled!");
- }
+ long oldId = Binder.clearCallingIdentity();
+ try {
+ final PowerSaveState result =
+ mPowerManager.getPowerSaveState(ServiceType.KEYVALUE_BACKUP);
+ if (result.batterySaverEnabled) {
+ if (DEBUG) Slog.v(TAG, "Not running backup while in battery save mode");
+ KeyValueBackupJob.schedule(mContext, mConstants); // try again in several hours
+ } else {
+ if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass");
+ synchronized (mQueueLock) {
+ // Fire the intent that kicks off the whole shebang...
+ try {
+ mRunBackupIntent.send();
+ } catch (PendingIntent.CanceledException e) {
+ // should never happen
+ Slog.e(TAG, "run-backup intent cancelled!");
+ }
- // ...and cancel any pending scheduled job, because we've just superseded it
- KeyValueBackupJob.cancel(mContext);
+ // ...and cancel any pending scheduled job, because we've just superseded it
+ KeyValueBackupJob.cancel(mContext);
+ }
}
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
}
}
diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
index 8399aaf..d16bc26 100644
--- a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -22,9 +22,7 @@
import static com.android.server.backup.testing.TransportData.localTransport;
import static com.android.server.backup.testing.TransportTestUtils.setUpCurrentTransport;
import static com.android.server.backup.testing.TransportTestUtils.setUpTransports;
-
import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -43,9 +41,10 @@
import android.content.ContextWrapper;
import android.content.Intent;
import android.os.HandlerThread;
+import android.os.PowerManager;
+import android.os.PowerSaveState;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
-
import com.android.server.backup.internal.BackupRequest;
import com.android.server.backup.testing.TransportData;
import com.android.server.backup.testing.TransportTestUtils.TransportMock;
@@ -54,8 +53,11 @@
import com.android.server.testing.SystemLoaderPackages;
import com.android.server.testing.shadows.ShadowAppBackupUtils;
import com.android.server.testing.shadows.ShadowBackupPolicyEnforcer;
+import com.android.server.testing.shadows.ShadowBinder;
+import com.android.server.testing.shadows.ShadowKeyValueBackupJob;
import com.android.server.testing.shadows.ShadowPerformBackupTask;
-
+import java.io.File;
+import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -64,6 +66,7 @@
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowContextWrapper;
import org.robolectric.shadows.ShadowLog;
import org.robolectric.shadows.ShadowLooper;
@@ -71,9 +74,6 @@
import org.robolectric.shadows.ShadowSettings;
import org.robolectric.shadows.ShadowSystemClock;
-import java.io.File;
-import java.util.List;
-
@RunWith(FrameworkRobolectricTestRunner.class)
@Config(
manifest = Config.NONE,
@@ -796,6 +796,34 @@
tearDownForRequestBackup();
}
+ @Test
+ @Config(shadows = {ShadowBinder.class, ShadowKeyValueBackupJob.class})
+ public void testBackupNow_clearsCallingIdentityForJobScheduler() {
+ mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+ BackupManagerService backupManagerService = createInitializedBackupManagerService();
+ setUpPowerManager(backupManagerService);
+ ShadowBinder.setCallingUid(1);
+
+ backupManagerService.backupNow();
+
+ assertThat(ShadowKeyValueBackupJob.getCallingUid()).isEqualTo(ShadowBinder.LOCAL_UID);
+ assertThat(ShadowBinder.getCallingUid()).isEqualTo(1);
+ }
+
+ @Test
+ @Config(shadows = {ShadowBinder.class, ShadowKeyValueBackupJobException.class})
+ public void testBackupNow_whenExceptionThrown_restoresCallingIdentity() {
+ mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+ BackupManagerService backupManagerService = createInitializedBackupManagerService();
+ setUpPowerManager(backupManagerService);
+ ShadowBinder.setCallingUid(1);
+
+ expectThrows(IllegalArgumentException.class, backupManagerService::backupNow);
+ assertThat(ShadowKeyValueBackupJobException.getCallingUid())
+ .isEqualTo(ShadowBinder.LOCAL_UID);
+ assertThat(ShadowBinder.getCallingUid()).isEqualTo(1);
+ }
+
private BackupManagerService createBackupManagerServiceForRequestBackup() {
BackupManagerService backupManagerService = createInitializedBackupManagerService();
backupManagerService.setEnabled(true);
@@ -853,4 +881,23 @@
ShadowSystemClock.setCurrentTimeMillis(mShadowBackupLooper.getScheduler().getCurrentTime());
return backupManagerService;
}
+
+ private void setUpPowerManager(BackupManagerService backupManagerService) {
+ PowerManager powerManagerMock = mock(PowerManager.class);
+ when(powerManagerMock.getPowerSaveState(anyInt()))
+ .thenReturn(new PowerSaveState.Builder().setBatterySaverEnabled(true).build());
+ backupManagerService.setPowerManager(powerManagerMock);
+ }
+
+ /**
+ * We can't mock the void method {@link #schedule(Context, long, BackupManagerConstants)} so we
+ * extend {@link ShadowKeyValueBackupJob} and throw an exception at the end of the method.
+ */
+ @Implements(KeyValueBackupJob.class)
+ public static class ShadowKeyValueBackupJobException extends ShadowKeyValueBackupJob {
+ public static void schedule(Context ctx, long delay, BackupManagerConstants constants) {
+ ShadowKeyValueBackupJob.schedule(ctx, delay, constants);
+ throw new IllegalArgumentException();
+ }
+ }
}
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowBinder.java b/services/robotests/src/com/android/server/testing/shadows/ShadowBinder.java
new file mode 100644
index 0000000..043d44b
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowBinder.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 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.testing.shadows;
+
+import android.os.Binder;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/**
+ * Extends {@link org.robolectric.shadows.ShadowBinder} with {@link Binder#clearCallingIdentity()}
+ * and {@link Binder#restoreCallingIdentity(long)}. Uses a hardcoded default {@link #LOCAL_UID} to
+ * mimic the local process uid.
+ */
+@Implements(Binder.class)
+public class ShadowBinder extends org.robolectric.shadows.ShadowBinder {
+ public static final Integer LOCAL_UID = 1000;
+ private static Integer originalCallingUid;
+
+ @Implementation
+ public static long clearCallingIdentity() {
+ originalCallingUid = getCallingUid();
+ setCallingUid(LOCAL_UID);
+ return 1L;
+ }
+
+ @Implementation
+ public static void restoreCallingIdentity(long token) {
+ setCallingUid(originalCallingUid);
+ }
+}
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java
new file mode 100644
index 0000000..3941f17
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 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.testing.shadows;
+
+import android.content.Context;
+import android.os.Binder;
+import com.android.server.backup.BackupManagerConstants;
+import com.android.server.backup.KeyValueBackupJob;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(KeyValueBackupJob.class)
+public class ShadowKeyValueBackupJob {
+ private static int callingUid;
+
+ public static int getCallingUid() {
+ return callingUid;
+ }
+
+ @Implementation
+ public static void schedule(Context ctx, long delay, BackupManagerConstants constants) {
+ callingUid = Binder.getCallingUid();
+ }
+}