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

Commit 476138cc authored by Alex Buynytskyy's avatar Alex Buynytskyy
Browse files

Implementing installation using DataLoader.

Fix for stdin installation.
Fix for file access.
Allow DataLoader to write only added files.

Test: atest PackageManagerShellCommandTest
Bug: b/136132412 b/146080380

Change-Id: I060792a157d40d6601eb7bd4cf11fda4a49c71e0
parent 04c349e7
Loading
Loading
Loading
Loading
+37 −15
Original line number Diff line number Diff line
@@ -119,7 +119,6 @@ import com.android.internal.content.PackageHelper;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.dex.DexManager;
@@ -141,6 +140,7 @@ import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@@ -210,6 +210,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
    private static final int[] EMPTY_CHILD_SESSION_ARRAY = {};

    private static final String SYSTEM_DATA_LOADER_PACKAGE = "android";

    // TODO: enforce INSTALL_ALLOW_TEST
    // TODO: enforce INSTALL_ALLOW_DOWNGRADE

@@ -556,6 +558,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                                params.dataLoaderParams);
            }
        }

        if (isStreamingInstallation()
                && this.params.dataLoaderParams.getComponentName().getPackageName()
                == SYSTEM_DATA_LOADER_PACKAGE) {
            assertShellOrSystemCalling("System data loaders");
        }
    }

    public SessionInfo generateInfo() {
@@ -771,6 +779,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }
    }

    private void assertShellOrSystemCalling(String operation) {
        switch (Binder.getCallingUid()) {
            case android.os.Process.SHELL_UID:
            case android.os.Process.ROOT_UID:
            case android.os.Process.SYSTEM_UID:
                break;
            default:
                throw new SecurityException(operation + " only supported from shell or system");
        }
    }

    private void assertCanWrite(boolean reverseMode) {
        if (isDataLoaderInstallation()) {
            throw new IllegalStateException(
@@ -781,15 +800,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            assertPreparedAndNotSealedLocked("assertCanWrite");
        }
        if (reverseMode) {
            switch (Binder.getCallingUid()) {
                case android.os.Process.SHELL_UID:
                case android.os.Process.ROOT_UID:
                case android.os.Process.SYSTEM_UID:
                    break;
                default:
                    throw new SecurityException(
                            "Reverse mode only supported from shell or system");
            }
            assertShellOrSystemCalling("Reverse mode");
        }
    }

@@ -1026,13 +1037,24 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
    }

    private class FileSystemConnector extends IPackageInstallerSessionFileSystemConnector.Stub {
    private final class FileSystemConnector extends
            IPackageInstallerSessionFileSystemConnector.Stub {
        final Set<String> mAddedFiles;

        FileSystemConnector(List<InstallationFile> addedFiles) {
            mAddedFiles = addedFiles.stream().map(file -> file.getName()).collect(
                    Collectors.toSet());
        }

        @Override
        public void writeData(String name, long offsetBytes, long lengthBytes,
                ParcelFileDescriptor incomingFd) {
            if (incomingFd == null) {
                throw new IllegalArgumentException("incomingFd can't be null");
            }
            if (!mAddedFiles.contains(name)) {
                throw new SecurityException("File name is not in the list of added files.");
            }
            try {
                doWriteInternal(name, offsetBytes, lengthBytes, incomingFd);
            } catch (IOException e) {
@@ -2468,8 +2490,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            return;
        }

        FileSystemConnector connector = new FileSystemConnector();

        List<InstallationFile> addedFiles = mFiles.stream().filter(
                file -> sAddedFilter.accept(new File(file.name))).map(
                    file -> new InstallationFile(
@@ -2481,6 +2501,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                            0, file.name.length() - REMOVE_MARKER_EXTENSION.length())).collect(
                Collectors.toList());

        final FileSystemConnector connector = new FileSystemConnector(addedFiles);

        DataLoaderManager dataLoaderManager = mContext.getSystemService(DataLoaderManager.class);
        if (dataLoaderManager == null) {
            throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
@@ -2516,11 +2538,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            }
        };

        final DataLoaderParams params = this.params.dataLoaderParams;

        final FileSystemControlParcel control = new FileSystemControlParcel();
        control.callback = connector;

        final DataLoaderParams params = this.params.dataLoaderParams;

        Bundle dataLoaderParams = new Bundle();
        dataLoaderParams.putParcelable("componentName", params.getComponentName());
        dataLoaderParams.putParcelable("control", control);
+3 −9
Original line number Diff line number Diff line
@@ -34,7 +34,6 @@ import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.DataLoaderParams;
import android.content.pm.FeatureInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageInstaller;
@@ -137,10 +136,6 @@ class PackageManagerShellCommand extends ShellCommand {
    private final static String ART_PROFILE_SNAPSHOT_DEBUG_LOCATION = "/data/misc/profman/";
    private static final int DEFAULT_WAIT_MS = 60 * 1000;

    private static final String DATA_LOADER_PACKAGE = "android";
    private static final String DATA_LOADER_CLASS =
            "com.android.server.pm.PackageManagerShellCommandDataLoader";

    final IPackageManager mInterface;
    final IPermissionManager mPermissionManager;
    final private WeakHashMap<String, Resources> mResourceCache =
@@ -1163,9 +1158,8 @@ class PackageManagerShellCommand extends ShellCommand {
    private int runStreamingInstall() throws RemoteException {
        final InstallParams params = makeInstallParams();
        if (params.sessionParams.dataLoaderParams == null) {
            final DataLoaderParams dataLoaderParams = DataLoaderParams.forStreaming(
                    new ComponentName(DATA_LOADER_PACKAGE, DATA_LOADER_CLASS), "");
            params.sessionParams.setDataLoaderParams(dataLoaderParams);
            params.sessionParams.setDataLoaderParams(
                    PackageManagerShellCommandDataLoader.getDataLoaderParams(this));
        }
        return doRunInstall(params);
    }
+88 −16
Original line number Diff line number Diff line
@@ -17,18 +17,22 @@
package com.android.server.pm;

import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.pm.DataLoaderParams;
import android.content.pm.InstallationFile;
import android.os.ParcelFileDescriptor;
import android.os.ShellCommand;
import android.service.dataloader.DataLoaderService;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;

import libcore.io.IoUtils;

import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Collection;

/**
@@ -37,41 +41,109 @@ import java.util.Collection;
public class PackageManagerShellCommandDataLoader extends DataLoaderService {
    public static final String TAG = "PackageManagerShellCommandDataLoader";

    static class DataLoader implements DataLoaderService.DataLoader {
        private ParcelFileDescriptor mInFd = null;
        private FileSystemConnector mConnector = null;
    private static final String PACKAGE = "android";
    private static final String CLASS = PackageManagerShellCommandDataLoader.class.getName();

    static final SecureRandom sRandom = new SecureRandom();
    static final SparseArray<WeakReference<ShellCommand>> sShellCommands = new SparseArray<>();

    private static final char ARGS_DELIM = '&';
    private static final String SHELL_COMMAND_ID_PREFIX = "shellCommandId=";
    private static final int INVALID_SHELL_COMMAND_ID = -1;
    private static final int TOO_MANY_PENDING_SHELL_COMMANDS = 10;

    private static final String STDIN_PATH = "-";

    static DataLoaderParams getDataLoaderParams(ShellCommand shellCommand) {
        int commandId;
        synchronized (sShellCommands) {
            // Clean up old references.
            for (int i = sShellCommands.size() - 1; i >= 0; i--) {
                WeakReference<ShellCommand> oldRef = sShellCommands.valueAt(i);
                if (oldRef.get() == null) {
                    sShellCommands.removeAt(i);
                }
            }

            // Sanity check.
            if (sShellCommands.size() > TOO_MANY_PENDING_SHELL_COMMANDS) {
                Slog.e(TAG, "Too many pending shell commands: " + sShellCommands.size());
            }

            // Generate new id and put ref to the array.
            do {
                commandId = sRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
            } while (sShellCommands.contains(commandId));

            sShellCommands.put(commandId, new WeakReference<>(shellCommand));
        }

        final String args = SHELL_COMMAND_ID_PREFIX + commandId;
        return DataLoaderParams.forStreaming(new ComponentName(PACKAGE, CLASS), args);
    }

    private static int extractShellCommandId(String args) {
        int sessionIdIdx = args.indexOf(SHELL_COMMAND_ID_PREFIX);
        if (sessionIdIdx < 0) {
            Slog.e(TAG, "Missing shell command id param.");
            return INVALID_SHELL_COMMAND_ID;
        }
        sessionIdIdx += SHELL_COMMAND_ID_PREFIX.length();
        int delimIdx = args.indexOf(ARGS_DELIM, sessionIdIdx);
        try {
            if (delimIdx < 0) {
                return Integer.parseInt(args.substring(sessionIdIdx));
            } else {
                return Integer.parseInt(args.substring(sessionIdIdx, delimIdx));
            }
        } catch (NumberFormatException e) {
            Slog.e(TAG, "Incorrect shell command id format.", e);
            return INVALID_SHELL_COMMAND_ID;
        }
    }

    static class DataLoader implements DataLoaderService.DataLoader {
        private DataLoaderParams mParams = null;
        private FileSystemConnector mConnector = null;

        @Override
        public boolean onCreate(@NonNull DataLoaderParams dataLoaderParams,
                @NonNull FileSystemConnector connector) {
            mParams = dataLoaderParams;
            mConnector = connector;
            return true;
        }

        @Override
        public boolean onPrepareImage(Collection<InstallationFile> addedFiles,
                Collection<String> removedFiles) {
            final int commandId = extractShellCommandId(mParams.getArguments());
            if (commandId == INVALID_SHELL_COMMAND_ID) {
                return false;
            }

            final WeakReference<ShellCommand> shellCommandRef;
            synchronized (sShellCommands) {
                shellCommandRef = sShellCommands.get(commandId, null);
            }
            final ShellCommand shellCommand =
                    shellCommandRef != null ? shellCommandRef.get() : null;
            if (shellCommand == null) {
                Slog.e(TAG, "Missing shell command.");
                return false;
            }
            try {
                for (InstallationFile fileInfo : addedFiles) {
                    String filePath = new String(fileInfo.getMetadata(), StandardCharsets.UTF_8);
                    if (STDIN_PATH.equals(filePath) || TextUtils.isEmpty(filePath)) {
                        // TODO(b/146080380): add support for STDIN installations.
                        if (mInFd == null) {
                            Slog.e(TAG, "Invalid stdin file descriptor.");
                            return false;
                        }
                        ParcelFileDescriptor inFd = ParcelFileDescriptor.dup(
                                mInFd.getFileDescriptor());
                        final ParcelFileDescriptor inFd = ParcelFileDescriptor.dup(
                                shellCommand.getInFileDescriptor());
                        mConnector.writeData(fileInfo.getName(), 0, fileInfo.getSize(), inFd);
                    } else {
                        File localFile = new File(filePath);
                        ParcelFileDescriptor incomingFd = null;
                        try {
                            // TODO(b/146080380): open files via callback into shell command.
                            incomingFd = ParcelFileDescriptor.open(localFile,
                                    ParcelFileDescriptor.MODE_READ_ONLY);
                            mConnector.writeData(fileInfo.getName(), 0, localFile.length(),
                            incomingFd = shellCommand.openFileForSystem(filePath, "r");
                            mConnector.writeData(fileInfo.getName(), 0, incomingFd.getStatSize(),
                                    incomingFd);
                        } finally {
                            IoUtils.closeQuietly(incomingFd);