blob: 23293567be468ddddddc1dd930cc3bfb6c0fa3a5 [file] [log] [blame]
/*
* 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.pm;
import android.annotation.NonNull;
import android.apex.ApexInfo;
import android.apex.IApexService;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageParser.SigningDetails;
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
import android.content.pm.ParceledListSlice;
import android.content.pm.Signature;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.apk.ApkSignatureVerifier;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
import java.util.ArrayList;
import java.util.List;
/**
* This class handles staged install sessions, i.e. install sessions that require packages to
* be installed only after a reboot.
*/
public class StagingManager {
private static final String TAG = "StagingManager";
private final PackageManagerService mPm;
private final Handler mBgHandler;
// STOPSHIP: This is a temporary mock implementation of staged sessions. This variable
// shouldn't be needed at all.
// TODO(b/118865310): Implement staged sessions logic.
@GuardedBy("mStagedSessions")
private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
StagingManager(PackageManagerService pm) {
mPm = pm;
mBgHandler = BackgroundThread.getHandler();
}
private void updateStoredSession(@NonNull PackageInstallerSession sessionInfo) {
synchronized (mStagedSessions) {
PackageInstallerSession storedSession = mStagedSessions.get(sessionInfo.sessionId);
// storedSession might be null if a call to abortSession was made before the session
// is updated.
if (storedSession != null) {
mStagedSessions.put(sessionInfo.sessionId, sessionInfo);
}
}
}
ParceledListSlice<PackageInstaller.SessionInfo> getSessions() {
final List<PackageInstaller.SessionInfo> result = new ArrayList<>();
synchronized (mStagedSessions) {
for (int i = 0; i < mStagedSessions.size(); i++) {
result.add(mStagedSessions.valueAt(i).generateInfo(false));
}
}
return new ParceledListSlice<>(result);
}
private static boolean validateApexSignatureLocked(String apexPath, String packageName) {
final SigningDetails signingDetails;
try {
signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR);
} catch (PackageParserException e) {
Slog.e(TAG, "Unable to parse APEX package: " + apexPath, e);
return false;
}
final IApexService apex = IApexService.Stub.asInterface(
ServiceManager.getService("apexservice"));
final ApexInfo apexInfo;
try {
apexInfo = apex.getActivePackage(packageName);
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact APEXD", re);
return false;
}
if (apexInfo == null || TextUtils.isEmpty(apexInfo.packageName)) {
// TODO: What is the right thing to do here ? This implies there's no active package
// with the given name. This should never be the case in production (where we only
// accept updates to existing APEXes) but may be required for testing.
return true;
}
final SigningDetails existingSigningDetails;
try {
existingSigningDetails = ApkSignatureVerifier.verify(
apexInfo.packagePath, SignatureSchemeVersion.JAR);
} catch (PackageParserException e) {
Slog.e(TAG, "Unable to parse APEX package: " + apexInfo.packagePath, e);
return false;
}
// Now that we have both sets of signatures, demand that they're an exact match.
if (Signature.areExactMatch(existingSigningDetails.signatures, signingDetails.signatures)) {
return true;
}
return false;
}
void commitSession(@NonNull PackageInstallerSession sessionInfo) {
updateStoredSession(sessionInfo);
mBgHandler.post(() -> {
sessionInfo.setStagedSessionReady();
SessionInfo session = sessionInfo.generateInfo(false);
// For APEXes, we validate the signature here before we write the package to the
// staging directory. For APKs, the signature verification will be done by the package
// manager at the point at which it applies the staged install.
//
// TODO: Decide whether we want to fail fast by detecting signature mismatches right
// away.
if ((sessionInfo.params.installFlags & PackageManager.INSTALL_APEX) != 0) {
if (!validateApexSignatureLocked(session.resolvedBaseCodePath,
session.appPackageName)) {
sessionInfo.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED);
}
}
mPm.sendSessionUpdatedBroadcast(sessionInfo.generateInfo(false), sessionInfo.userId);
});
}
void createSession(@NonNull PackageInstallerSession sessionInfo) {
synchronized (mStagedSessions) {
mStagedSessions.append(sessionInfo.sessionId, sessionInfo);
}
}
void abortSession(@NonNull PackageInstallerSession sessionInfo) {
updateStoredSession(sessionInfo);
synchronized (mStagedSessions) {
mStagedSessions.remove(sessionInfo.sessionId);
}
}
}