| /* |
| * 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); |
| } |
| } |
| } |