Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7d038cd0 authored by Dario Freni's avatar Dario Freni Committed by Bill Lin
Browse files

Support non-privileged APKs in APEX.

This change adds initial support for scanning APEX directories looking
for APK, and make them available to the system as normal system APKs.
For now privileged apps and resource overlay are not supported, and the
support will be added in a later CL.

Scanning features are inherited from the partition in which the
preinstalled version of a given APEX module is located.

Supports both flattened and un-flattened APEX configurations.

Bug: 138429615
Test: Compiled a test apex with the Snake app installed in it. Verified
app is correctly scanned and usable. Tried also installing the apex in
/vendor and /product.
Exempt-From-Owner-Approval: Approved in
https://googleplex-android-review.git.corp.google.com/c/platform/frameworks/base/+/9533445
Merged-In: I8fb91317da0296567403b2b62babbb71e4b48938
Change-Id: I8fb91317da0296567403b2b62babbb71e4b48938
parent ecccf009
Loading
Loading
Loading
Loading
+14 −1
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ public class Environment {
    private static final String ENV_VENDOR_ROOT = "VENDOR_ROOT";
    private static final String ENV_PRODUCT_ROOT = "PRODUCT_ROOT";
    private static final String ENV_SYSTEM_EXT_ROOT = "SYSTEM_EXT_ROOT";
    private static final String ENV_APEX_ROOT = "APEX_ROOT";

    /** {@hide} */
    public static final String DIR_ANDROID = "Android";
@@ -79,6 +80,8 @@ public class Environment {
    private static final File DIR_PRODUCT_ROOT = getDirectory(ENV_PRODUCT_ROOT, "/product");
    private static final File DIR_SYSTEM_EXT_ROOT = getDirectory(ENV_SYSTEM_EXT_ROOT,
            "/system_ext");
    private static final File DIR_APEX_ROOT = getDirectory(ENV_APEX_ROOT,
            "/apex");

    @UnsupportedAppUsage
    private static UserEnvironment sCurrentUser;
@@ -245,6 +248,16 @@ public class Environment {
        return DIR_SYSTEM_EXT_ROOT;
    }

    /**
     * Return root directory of the apex mount point, where all the apex modules are made available
     * to the rest of the system.
     *
     * @hide
     */
    public static @NonNull File getApexDirectory() {
        return DIR_APEX_ROOT;
    }

    /**
     * Return the system directory for a user. This is for use by system
     * services to store files relating to the user. This directory will be
+74 −5
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.os.Environment;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.sysprop.ApexProperties;
@@ -47,6 +48,7 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -64,9 +66,9 @@ abstract class ApexManager {
    static final int MATCH_FACTORY_PACKAGE = 1 << 1;

    /**
     * Returns an instance of either {@link ApexManagerImpl} or {@link ApexManagerNoOp} depending
     * on whenever this device supports APEX, i.e. {@link ApexProperties#updatable()} evaluates to
     * {@code true}.
     * Returns an instance of either {@link ApexManagerImpl} or {@link ApexManagerFlattenedApex}
     * depending on whether this device supports APEX, i.e. {@link ApexProperties#updatable()}
     * evaluates to {@code true}.
     */
    static ApexManager create(Context systemContext) {
        if (ApexProperties.updatable().orElse(false)) {
@@ -77,10 +79,28 @@ abstract class ApexManager {
                throw new IllegalStateException("Required service apexservice not available");
            }
        } else {
            return new ApexManagerNoOp();
            return new ApexManagerFlattenedApex();
        }
    }

    /**
     * Minimal information about APEX mount points and the original APEX package they refer to.
     */
    static class ActiveApexInfo {
        public final File apexDirectory;
        public final File preinstalledApexPath;

        private ActiveApexInfo(File apexDirectory, File preinstalledApexPath) {
            this.apexDirectory = apexDirectory;
            this.preinstalledApexPath = preinstalledApexPath;
        }
    }

    /**
     * Returns {@link ActiveApexInfo} records relative to all active APEX packages.
     */
    abstract List<ActiveApexInfo> getActiveApexInfos();

    abstract void systemReady();

    /**
@@ -258,6 +278,22 @@ abstract class ApexManager {
            return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
        }

        @Override
        List<ActiveApexInfo> getActiveApexInfos() {
            try {
                return Arrays.stream(mApexService.getActivePackages())
                        .map(apexInfo -> new ActiveApexInfo(
                                new File(
                                Environment.getApexDirectory() + File.separator
                                        + apexInfo.moduleName),
                                new File(apexInfo.modulePath))).collect(
                                Collectors.toList());
            } catch (RemoteException e) {
                Slog.e(TAG, "Unable to retrieve packages from apexservice", e);
            }
            return Collections.emptyList();
        }

        @Override
        void systemReady() {
            mContext.registerReceiver(new BroadcastReceiver() {
@@ -549,7 +585,40 @@ abstract class ApexManager {
     * An implementation of {@link ApexManager} that should be used in case device does not support
     * updating APEX packages.
     */
    private static final class ApexManagerNoOp extends ApexManager {
    private static final class ApexManagerFlattenedApex extends ApexManager {

        @Override
        List<ActiveApexInfo> getActiveApexInfos() {
            // There is no apexd running in case of flattened apex
            // We look up the /apex directory and identify the active APEX modules from there.
            // As "preinstalled" path, we just report /system since in the case of flattened APEX
            // the /apex directory is just a symlink to /system/apex.
            List<ActiveApexInfo> result = new ArrayList<>();
            File apexDir = Environment.getApexDirectory();
            // In flattened configuration, init special-case the art directory and bind-mounts
            // com.android.art.{release|debug} to com.android.art. At the time of writing, these
            // directories are copied from the kArtApexDirNames variable in
            // system/core/init/mount_namespace.cpp.
            String[] skipDirs = {"com.android.art.release", "com.android.art.debug"};
            if (apexDir.isDirectory()) {
                File[] files = apexDir.listFiles();
                // listFiles might be null if system server doesn't have permission to read
                // a directory.
                if (files != null) {
                    for (File file : files) {
                        if (file.isDirectory() && !file.getName().contains("@")) {
                            for (String skipDir : skipDirs) {
                                if (file.getName().equals(skipDir)) {
                                    continue;
                                }
                            }
                            result.add(new ActiveApexInfo(file, Environment.getRootDirectory()));
                        }
                    }
                }
            }
            return result;
        }

        @Override
        void systemReady() {
+9 −0
Original line number Diff line number Diff line
@@ -67,6 +67,15 @@ final class PackageAbiHelperImpl implements PackageAbiHelper {
            codeRoot = Environment.getSystemExtDirectory();
        } else if (FileUtils.contains(Environment.getOdmDirectory(), codePath)) {
            codeRoot = Environment.getOdmDirectory();
        } else if (FileUtils.contains(Environment.getApexDirectory(), codePath)) {
            String fullPath = codePath.getAbsolutePath();
            String[] parts = fullPath.split(File.separator);
            if (parts.length > 2) {
                codeRoot = new File(parts[1] + File.separator + parts[2]);
            } else {
                Slog.w(PackageManagerService.TAG, "Can't canonicalize code path " + codePath);
                codeRoot = Environment.getApexDirectory();
            }
        } else {
            // Unrecognized code path; take its top real segment as the apk root:
            // e.g. /something/app/blah.apk => /something
+36 −9
Original line number Diff line number Diff line
@@ -375,6 +375,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
 * Keep track of all those APKs everywhere.
@@ -767,6 +768,8 @@ public class PackageManagerService extends IPackageManager.Stub
                    new SystemPartition(Environment.getSystemExtDirectory(), SCAN_AS_SYSTEM_EXT,
                            true /* hasPriv */, true /* hasOverlays */)));
    private final List<SystemPartition> mDirsToScanAsSystem;
    /**
     * Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors.
     *
@@ -2563,7 +2566,18 @@ public class PackageManagerService extends IPackageManager.Stub
        mProtectedPackages = new ProtectedPackages(mContext);
        mApexManager = ApexManager.create(context);
        mApexManager = ApexManager.create(mContext);
        mDirsToScanAsSystem = new ArrayList<>();
        mDirsToScanAsSystem.addAll(SYSTEM_PARTITIONS);
        mDirsToScanAsSystem.addAll(mApexManager.getActiveApexInfos().stream()
                .map(ai -> resolveApexToSystemPartition(ai))
                .filter(Objects::nonNull).collect(Collectors.toList()));
        Slog.d(TAG,
                "Directories scanned as system partitions: [" + mDirsToScanAsSystem.stream().map(
                        d -> (d.folder.getAbsolutePath() + ":" + d.scanFlag))
                        .collect(Collectors.joining(",")) + "]");
        // CHECKSTYLE:OFF IndentationCheck
        synchronized (mInstallLock) {
        // writer
@@ -2700,8 +2714,8 @@ public class PackageManagerService extends IPackageManager.Stub
            // reside in the right directory.
            final int systemParseFlags = mDefParseFlags | PackageParser.PARSE_IS_SYSTEM_DIR;
            final int systemScanFlags = scanFlags | SCAN_AS_SYSTEM;
            for (int i = SYSTEM_PARTITIONS.size() - 1; i >= 0; i--) {
                final SystemPartition partition = SYSTEM_PARTITIONS.get(i);
            for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) {
                final SystemPartition partition = mDirsToScanAsSystem.get(i);
                if (partition.overlayFolder == null) {
                    continue;
                }
@@ -2717,8 +2731,8 @@ public class PackageManagerService extends IPackageManager.Stub
                throw new IllegalStateException(
                        "Failed to load frameworks package; check log for warnings");
            }
            for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
                final SystemPartition partition = SYSTEM_PARTITIONS.get(i);
            for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
                final SystemPartition partition = mDirsToScanAsSystem.get(i);
                if (partition.privAppFolder != null) {
                    scanDirTracedLI(partition.privAppFolder, systemParseFlags,
                            systemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0);
@@ -2896,8 +2910,8 @@ public class PackageManagerService extends IPackageManager.Stub
                        @ParseFlags int reparseFlags = 0;
                        @ScanFlags int rescanFlags = 0;
                        for (int i1 = 0, size = SYSTEM_PARTITIONS.size(); i1 < size; i1++) {
                            SystemPartition partition = SYSTEM_PARTITIONS.get(i1);
                        for (int i1 = 0, size = mDirsToScanAsSystem.size(); i1 < size; i1++) {
                            SystemPartition partition = mDirsToScanAsSystem.get(i1);
                            if (partition.containsPrivApp(scanFile)) {
                                reparseFlags = systemParseFlags;
                                rescanFlags = systemScanFlags | SCAN_AS_PRIVILEGED
@@ -18375,6 +18389,7 @@ public class PackageManagerService extends IPackageManager.Stub
    }
    static boolean locationIsPrivileged(String path) {
        // TODO(dariofreni): include APEX partitions when they will support priv apps.
        for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
            SystemPartition partition = SYSTEM_PARTITIONS.get(i);
            if (partition.containsPrivPath(path)) {
@@ -18384,6 +18399,18 @@ public class PackageManagerService extends IPackageManager.Stub
        return false;
    }
    private static @Nullable SystemPartition resolveApexToSystemPartition(
            ApexManager.ActiveApexInfo apexInfo) {
        for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
            SystemPartition sp = SYSTEM_PARTITIONS.get(i);
            if (apexInfo.preinstalledApexPath.getAbsolutePath().startsWith(
                    sp.folder.getAbsolutePath())) {
                return new SystemPartition(apexInfo.apexDirectory, sp.scanFlag,
                        false /* hasPriv */, false /* hasOverlays */);
            }
        }
        return null;
    }
    /*
     * Tries to delete system package.
@@ -18490,8 +18517,8 @@ public class PackageManagerService extends IPackageManager.Stub
                | PackageParser.PARSE_MUST_BE_APK
                | PackageParser.PARSE_IS_SYSTEM_DIR;
        @ScanFlags int scanFlags = SCAN_AS_SYSTEM;
        for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
            SystemPartition partition = SYSTEM_PARTITIONS.get(i);
        for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
            SystemPartition partition = mDirsToScanAsSystem.get(i);
            if (partition.containsPath(codePathString)) {
                scanFlags |= partition.scanFlag;
                if (partition.containsPrivPath(codePathString)) {