blob: 5c0a15a28285967114eebb887fb8ada2de05435e [file] [log] [blame]
/*
* Copyright (C) 2011 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.annotation.Nullable;
import android.content.pm.ApplicationInfo;
import android.content.pm.SigningDetails;
import android.service.pm.PackageServiceDumpProto;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.proto.ProtoOutputStream;
import com.android.internal.pm.pkg.component.ComponentMutateUtils;
import com.android.internal.pm.pkg.component.ParsedProcess;
import com.android.internal.pm.pkg.component.ParsedProcessImpl;
import com.android.internal.util.ArrayUtils;
import com.android.server.pm.permission.LegacyPermissionState;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.SharedUserApi;
import com.android.server.utils.SnapshotCache;
import com.android.server.utils.Watchable;
import com.android.server.utils.WatchedArraySet;
import com.android.server.utils.Watcher;
import libcore.util.EmptyArray;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Settings data for a particular shared user ID we know about.
*/
public final class SharedUserSetting extends SettingBase implements SharedUserApi {
final String name;
int mAppId;
/** @see SharedUserApi#getUidFlags() **/
int uidFlags;
int uidPrivateFlags;
/** @see SharedUserApi#getSeInfoTargetSdkVersion() **/
int seInfoTargetSdkVersion;
private final WatchedArraySet<PackageSetting> mPackages;
private final SnapshotCache<WatchedArraySet<PackageSetting>> mPackagesSnapshot;
// It is possible for a system app to leave shared user ID by an update.
// We need to keep track of the shadowed PackageSettings so that it is possible to uninstall
// the update and revert the system app back into the original shared user ID.
final WatchedArraySet<PackageSetting> mDisabledPackages;
private final SnapshotCache<WatchedArraySet<PackageSetting>> mDisabledPackagesSnapshot;
/**
* The observer that watches for changes from array members
*/
private final Watcher mObserver = new Watcher() {
@Override
public void onChange(@Nullable Watchable what) {
SharedUserSetting.this.onChanged();
}
};
final PackageSignatures signatures = new PackageSignatures();
Boolean signaturesChanged;
final ArrayMap<String, ParsedProcess> processes;
/**
* Snapshot support.
*/
private final SnapshotCache<SharedUserSetting> mSnapshot;
private SnapshotCache<SharedUserSetting> makeCache() {
return new SnapshotCache<SharedUserSetting>(this, this) {
@Override
public SharedUserSetting createSnapshot() {
return new SharedUserSetting(mSource);
}};
}
SharedUserSetting(String _name, int _pkgFlags, int _pkgPrivateFlags) {
super(_pkgFlags, _pkgPrivateFlags);
uidFlags = _pkgFlags;
uidPrivateFlags = _pkgPrivateFlags;
name = _name;
seInfoTargetSdkVersion = android.os.Build.VERSION_CODES.CUR_DEVELOPMENT;
mPackages = new WatchedArraySet<>();
mPackagesSnapshot = new SnapshotCache.Auto<>(mPackages, mPackages,
"SharedUserSetting.packages");
mDisabledPackages = new WatchedArraySet<>();
mDisabledPackagesSnapshot = new SnapshotCache.Auto<>(mDisabledPackages, mDisabledPackages,
"SharedUserSetting.mDisabledPackages");
processes = new ArrayMap<>();
registerObservers();
mSnapshot = makeCache();
}
// The copy constructor is used to create a snapshot
private SharedUserSetting(SharedUserSetting orig) {
super(orig);
name = orig.name;
mAppId = orig.mAppId;
uidFlags = orig.uidFlags;
uidPrivateFlags = orig.uidPrivateFlags;
mPackages = orig.mPackagesSnapshot.snapshot();
mPackagesSnapshot = new SnapshotCache.Sealed<>();
mDisabledPackages = orig.mDisabledPackagesSnapshot.snapshot();
mDisabledPackagesSnapshot = new SnapshotCache.Sealed<>();
// A SigningDetails seems to consist solely of final attributes, so
// it is safe to copy the reference.
signatures.mSigningDetails = orig.signatures.mSigningDetails;
signaturesChanged = orig.signaturesChanged;
processes = new ArrayMap<>(orig.processes);
mSnapshot = new SnapshotCache.Sealed<>();
}
private void registerObservers() {
mPackages.registerObserver(mObserver);
mDisabledPackages.registerObserver(mObserver);
}
/**
* Return a read-only snapshot of this object.
*/
public SharedUserSetting snapshot() {
return mSnapshot.snapshot();
}
@Override
public String toString() {
return "SharedUserSetting{" + Integer.toHexString(System.identityHashCode(this)) + " "
+ name + "/" + mAppId + "}";
}
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
proto.write(PackageServiceDumpProto.SharedUserProto.UID, mAppId);
proto.write(PackageServiceDumpProto.SharedUserProto.NAME, name);
proto.end(token);
}
void addProcesses(Map<String, ParsedProcess> newProcs) {
if (newProcs != null) {
for (String key : newProcs.keySet()) {
ParsedProcess newProc = newProcs.get(key);
ParsedProcess proc = processes.get(newProc.getName());
if (proc == null) {
proc = new ParsedProcessImpl(newProc);
processes.put(newProc.getName(), proc);
} else {
ComponentMutateUtils.addStateFrom(proc, newProc);
}
}
onChanged();
}
}
boolean removePackage(PackageSetting packageSetting) {
if (!mPackages.remove(packageSetting)) {
return false;
}
// recalculate the pkgFlags for this shared user if needed
if ((this.getFlags() & packageSetting.getFlags()) != 0) {
int aggregatedFlags = uidFlags;
for (int i = 0; i < mPackages.size(); i++) {
PackageSetting ps = mPackages.valueAt(i);
aggregatedFlags |= ps.getFlags();
}
setFlags(aggregatedFlags);
}
if ((this.getPrivateFlags() & packageSetting.getPrivateFlags()) != 0) {
int aggregatedPrivateFlags = uidPrivateFlags;
for (int i = 0; i < mPackages.size(); i++) {
PackageSetting ps = mPackages.valueAt(i);
aggregatedPrivateFlags |= ps.getPrivateFlags();
}
setPrivateFlags(aggregatedPrivateFlags);
}
// recalculate processes.
updateProcesses();
onChanged();
return true;
}
void addPackage(PackageSetting packageSetting) {
// If this is the first package added to this shared user, temporarily (until next boot) use
// its targetSdkVersion when assigning seInfo for the shared user.
if ((mPackages.size() == 0) && (packageSetting.getPkg() != null)) {
seInfoTargetSdkVersion = packageSetting.getPkg().getTargetSdkVersion();
}
if (mPackages.add(packageSetting)) {
setFlags(this.getFlags() | packageSetting.getFlags());
setPrivateFlags(this.getPrivateFlags() | packageSetting.getPrivateFlags());
onChanged();
}
if (packageSetting.getPkg() != null) {
addProcesses(packageSetting.getPkg().getProcesses());
}
}
@NonNull
@Override
public List<AndroidPackage> getPackages() {
if (mPackages == null || mPackages.size() == 0) {
return Collections.emptyList();
}
final ArrayList<AndroidPackage> pkgList = new ArrayList<>(mPackages.size());
for (int i = 0; i < mPackages.size(); i++) {
PackageSetting ps = mPackages.valueAt(i);
if ((ps == null) || (ps.getPkg() == null)) {
continue;
}
pkgList.add(ps.getPkg());
}
return pkgList;
}
@Override
public boolean isPrivileged() {
return (this.getPrivateFlags() & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
}
/**
* A shared user is considered "single user" if there is exactly one single package
* currently using it. In the case when that package is also a system app, the APK on
* the system partition has to also leave shared UID.
*/
public boolean isSingleUser() {
if (mPackages.size() != 1) {
return false;
}
if (mDisabledPackages.size() > 1) {
return false;
}
if (mDisabledPackages.size() == 1) {
final AndroidPackage pkg = mDisabledPackages.valueAt(0).getPkg();
return pkg != null && pkg.isLeavingSharedUser();
}
return true;
}
/**
* Determine the targetSdkVersion for a sharedUser and update pkg.applicationInfo.seInfo
* to ensure that all apps within the sharedUser share an SELinux domain. Use the lowest
* targetSdkVersion of all apps within the shared user, which corresponds to the least
* restrictive selinux domain.
*/
public void fixSeInfoLocked() {
if (mPackages == null || mPackages.size() == 0) {
return;
}
for (int i = 0; i < mPackages.size(); i++) {
PackageSetting ps = mPackages.valueAt(i);
if ((ps == null) || (ps.getPkg() == null)) {
continue;
}
if (ps.getPkg().getTargetSdkVersion() < seInfoTargetSdkVersion) {
seInfoTargetSdkVersion = ps.getPkg().getTargetSdkVersion();
onChanged();
}
}
for (int i = 0; i < mPackages.size(); i++) {
PackageSetting ps = mPackages.valueAt(i);
if ((ps == null) || (ps.getPkg() == null)) {
continue;
}
final boolean isPrivileged = isPrivileged() | ps.isPrivileged();
ps.getPkgState().setOverrideSeInfo(SELinuxMMAC.getSeInfo(ps, ps.getPkg(), isPrivileged,
seInfoTargetSdkVersion));
onChanged();
}
}
/**
* Update tracked data about processes based on all known packages in the shared user ID.
*/
public void updateProcesses() {
processes.clear();
for (int i = mPackages.size() - 1; i >= 0; i--) {
final AndroidPackage pkg = mPackages.valueAt(i).getPkg();
if (pkg != null) {
addProcesses(pkg.getProcesses());
}
}
}
/** Returns userIds which doesn't have any packages with this sharedUserId */
public int[] getNotInstalledUserIds() {
int[] excludedUserIds = null;
for (int i = 0; i < mPackages.size(); i++) {
PackageSetting ps = mPackages.valueAt(i);
final int[] userIds = ps.getNotInstalledUserIds();
if (excludedUserIds == null) {
excludedUserIds = userIds;
} else {
for (int userId : excludedUserIds) {
if (!ArrayUtils.contains(userIds, userId)) {
excludedUserIds = ArrayUtils.removeInt(excludedUserIds, userId);
}
}
}
}
return excludedUserIds == null ? EmptyArray.INT : excludedUserIds;
}
/** Updates all fields in this shared user setting from another. */
public SharedUserSetting updateFrom(SharedUserSetting sharedUser) {
super.copySettingBase(sharedUser);
this.mAppId = sharedUser.mAppId;
this.uidFlags = sharedUser.uidFlags;
this.uidPrivateFlags = sharedUser.uidPrivateFlags;
this.seInfoTargetSdkVersion = sharedUser.seInfoTargetSdkVersion;
this.mPackages.clear();
this.mPackages.addAll(sharedUser.mPackages);
this.signaturesChanged = sharedUser.signaturesChanged;
if (sharedUser.processes != null) {
final int numProcs = sharedUser.processes.size();
this.processes.clear();
this.processes.ensureCapacity(numProcs);
for (int i = 0; i < numProcs; i++) {
ParsedProcess proc = new ParsedProcessImpl(sharedUser.processes.valueAt(i));
this.processes.put(proc.getName(), proc);
}
} else {
this.processes.clear();
}
onChanged();
return this;
}
@NonNull
@Override
public String getName() {
return name;
}
@Override
public int getAppId() {
return mAppId;
}
@Override
public int getUidFlags() {
return uidFlags;
}
@Override
public int getPrivateUidFlags() {
return uidPrivateFlags;
}
@Override
public int getSeInfoTargetSdkVersion() {
return seInfoTargetSdkVersion;
}
public WatchedArraySet<PackageSetting> getPackageSettings() {
return mPackages;
}
public WatchedArraySet<PackageSetting> getDisabledPackageSettings() {
return mDisabledPackages;
}
@NonNull
@Override
public ArraySet<? extends PackageStateInternal> getPackageStates() {
return mPackages.untrackedStorage();
}
@NonNull
@Override
public ArraySet<? extends PackageStateInternal> getDisabledPackageStates() {
return mDisabledPackages.untrackedStorage();
}
@NonNull
@Override
public SigningDetails getSigningDetails() {
return signatures.mSigningDetails;
}
@NonNull
@Override
public ArrayMap<String, ParsedProcess> getProcesses() {
return processes;
}
@NonNull
@Override
public LegacyPermissionState getSharedUserLegacyPermissionState() {
return super.getLegacyPermissionState();
}
}