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

Commit f6729fae authored by Dianne Hackborn's avatar Dianne Hackborn
Browse files

Initial impl issue #143085640: Per-process network access control

Add new manifest tags for declaring processes that an app
will use.  While declaring these processes, one can specify
specific permissions that will be denied to that process.
Doing so will result in any gids associated with that permission
from being given to that process, and any permission checks
that include a pid (which is not all of them) will fail when
checking a permission for that process.

For now, we limit these declarations to only the internet
permission, since we really need to do a lot of auditing to
determine how many other permissions can be denied (based on
how many permission checks for it are including the pid).
That said, this is explicitly not a security guarantee, so it
isn't a problem if there are ways around it (the process could
always IPC to another of the app's processes to do the same
thing).

One thing to be done is have the parser enforce that once an
app declares processes, it can only run things in those processes
and no others.

At this point the code is not yet tested at all.  That will be
coming in later.  This gets the APIs and various infrastructure
in place.

Bug: 143085640
Test: not yet tested

Change-Id: I27e8d0c811a5004fe251883f243517bb00d32d67
parent 9670c9e1
Loading
Loading
Loading
Loading
+88 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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 android.content.pm;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.ArraySet;

/**
 * Information about a process an app may run.  This corresponds to information collected from the
 * AndroidManifest.xml's <permission-group> tags.
 * @hide
 */
public class ProcessInfo implements Parcelable {
    /**
     * The name of the process, fully-qualified based on the app's package name.
     */
    public String name;

    /**
     * If non-null, these are permissions that are not allowed in this process.
     */
    @Nullable
    public ArraySet<String> deniedPermissions;

    public ProcessInfo(String name, ArraySet<String> deniedPermissions) {
        this.name = name;
        this.deniedPermissions = deniedPermissions;
    }

    @Deprecated
    public ProcessInfo(@NonNull ProcessInfo orig) {
        this.name = orig.name;
        this.deniedPermissions = orig.deniedPermissions;
    }

    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel dest, int parcelableFlags) {
        dest.writeString(this.name);
        final int numDenied = this.deniedPermissions != null
                ? this.deniedPermissions.size() : 0;
        dest.writeInt(numDenied);
        for (int i = 0; i < numDenied; i++) {
            dest.writeString(this.deniedPermissions.valueAt(i));
        }
    }

    public static final @NonNull Creator<ProcessInfo> CREATOR =
            new Creator<ProcessInfo>() {
                public ProcessInfo createFromParcel(Parcel source) {
                    return new ProcessInfo(source);
                }
                public ProcessInfo[] newArray(int size) {
                    return new ProcessInfo[size];
                }
            };

    private ProcessInfo(Parcel source) {
        this.name = source.readString();
        final int numDenied = source.readInt();
        if (numDenied > 0) {
            this.deniedPermissions = new ArraySet<>(numDenied);
            for (int i = numDenied - 1; i >= 0; i--) {
                this.deniedPermissions.add(TextUtils.safeIntern(source.readString()));
            }
        }
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.content.pm.parsing.ComponentParseUtils.ParsedService;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;

@@ -379,6 +380,9 @@ public interface AndroidPackage extends Parcelable {
    @Nullable
    long[] getUsesStaticLibrariesVersions();

    @Nullable
    ArrayMap<String, ComponentParseUtils.ParsedProcess> getProcesses();

    int getVersionCode();

    int getVersionCodeMajor();
+15 −0
Original line number Diff line number Diff line
@@ -2363,6 +2363,21 @@ public class ApkParseUtils {

                    XmlUtils.skipCurrentTag(parser);

                    break;
                case "processes":
                    ArrayMap<String, ComponentParseUtils.ParsedProcess> processes =
                            ComponentParseUtils.parseProcesses(separateProcesses,
                                    parsingPackage,
                                    res, parser, flags,
                                    outError);
                    if (processes == null) {
                        return parseInput.error(
                                PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                                outError[0]
                        );
                    }

                    parsingPackage.setProcesses(processes);
                    break;
                case "uses-package":
                    // Dependencies for app installers; we don't currently try to
+250 −0
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.PatternMatcher;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
@@ -1357,6 +1358,72 @@ public class ComponentParseUtils {
                };
    }

    public static class ParsedProcess implements Parcelable {

        public String name;
        @Nullable
        public ArraySet<String> deniedPermissions;

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(this.name);
            final int numDenied = this.deniedPermissions != null
                    ? this.deniedPermissions.size() : 0;
            dest.writeInt(numDenied);
            for (int i = 0; i < numDenied; i++) {
                dest.writeString(this.deniedPermissions.valueAt(i));
            }
        }

        public ParsedProcess() {
        }

        public ParsedProcess(@NonNull ParsedProcess other) {
            name = other.name;
            if (other.deniedPermissions != null) {
                deniedPermissions = new ArraySet<>(other.deniedPermissions);
            }
        }

        public void addStateFrom(@NonNull ParsedProcess other) {
            if (other.deniedPermissions != null) {
                for (int i = other.deniedPermissions.size() - 1; i >= 0; i--) {
                    if (deniedPermissions == null) {
                        deniedPermissions = new ArraySet<>(other.deniedPermissions.size());
                    }
                    deniedPermissions.add(other.deniedPermissions.valueAt(i));
                }
            }
        }

        protected ParsedProcess(Parcel in) {
            this.name = TextUtils.safeIntern(in.readString());
            final int numDenied = in.readInt();
            if (numDenied > 0) {
                this.deniedPermissions = new ArraySet<>(numDenied);
                this.deniedPermissions.add(TextUtils.safeIntern(in.readString()));
            }
        }

        public static final Creator<ParsedProcess> CREATOR =
                new Creator<ParsedProcess>() {
                    @Override
                    public ParsedProcess createFromParcel(Parcel source) {
                        return new ParsedProcess(source);
                    }

                    @Override
                    public ParsedProcess[] newArray(int size) {
                        return new ParsedProcess[size];
                    }
                };
    }

    public static ParsedActivity parseActivity(
            String[] separateProcesses,
            ParsingPackage parsingPackage,
@@ -3253,6 +3320,189 @@ public class ComponentParseUtils {
        return result;
    }

    private static @Nullable ArraySet<String> parseDenyPermission(
            ArraySet<String> perms,
            Resources res,
            XmlResourceParser parser,
            String[] outError
    ) throws IOException, XmlPullParserException {
        TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestDenyPermission);
        if (sa == null) {
            outError[0] = "<deny-permission> could not be parsed";
            return null;
        }

        try {
            String perm = sa.getNonConfigurationString(
                    R.styleable.AndroidManifestDenyPermission_name,0);
            if (perm != null && perm.equals(android.Manifest.permission.INTERNET)) {
                if (perms == null) {
                    perms = new ArraySet<>();
                }
                perms.add(perm);
            }
        } finally {
            sa.recycle();
        }
        XmlUtils.skipCurrentTag(parser);
        return perms;
    }

    private static ArraySet<String> parseAllowPermission(
            ArraySet<String> perms,
            Resources res,
            XmlResourceParser parser,
            String[] outError
    ) throws IOException, XmlPullParserException {
        TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestAllowPermission);
        if (sa == null) {
            outError[0] = "<allow-permission> could not be parsed";
            return null;
        }

        try {
            String perm = sa.getNonConfigurationString(
                    R.styleable.AndroidManifestAllowPermission_name,0);
            if (perm != null && perm.equals(android.Manifest.permission.INTERNET)
                    && perms != null) {
                perms.remove(perm);
                if (perms.size() <= 0) {
                    perms = null;
                }
            }
        } finally {
            sa.recycle();
        }
        XmlUtils.skipCurrentTag(parser);
        return perms;
    }

    public static ParsedProcess parseProcess(
            ArraySet<String> perms,
            String[] separateProcesses,
            ParsingPackage parsingPackage,
            Resources res,
            XmlResourceParser parser,
            int flags,
            String[] outError
    ) throws IOException, XmlPullParserException {
        TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestProcess);
        if (sa == null) {
            outError[0] = "<process> could not be parsed";
            return null;
        }

        ParsedProcess proc = new ParsedProcess();
        if (perms != null) {
            proc.deniedPermissions = new ArraySet(perms);
        }

        try {
            proc.name = sa.getNonConfigurationString(
                    R.styleable.AndroidManifestProcess_process,0);
            proc.name = PackageParser.buildProcessName(parsingPackage.getPackageName(),
                    null, proc.name, flags, separateProcesses, outError);

            if (proc.name == null || proc.name.length() <= 0) {
                outError[0] = "<process> does not specify android:process";
                return null;
            }
            proc.name = PackageParser.buildProcessName(parsingPackage.getPackageName(),
                    parsingPackage.getPackageName(), proc.name,
                    flags, separateProcesses, outError);
            if (outError[0] != null) {
                return null;
            }
        } finally {
            sa.recycle();
        }

        int type;
        final int innerDepth = parser.getDepth();
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }

            String tagName = parser.getName();
            if (tagName.equals("deny-permission")) {
                proc.deniedPermissions = parseDenyPermission(proc.deniedPermissions, res, parser,
                        outError);
                if (outError[0] != null) {
                    return null;
                }
            } else if (tagName.equals("allow-permission")) {
                proc.deniedPermissions = parseAllowPermission(proc.deniedPermissions, res, parser,
                        outError);
                if (outError[0] != null) {
                    return null;
                }
            } else {
                Slog.w(TAG, "Unknown element under <process>: " + tagName
                        + " at " + parsingPackage.getBaseCodePath() + " "
                        + parser.getPositionDescription());
                XmlUtils.skipCurrentTag(parser);
                continue;
            }
        }

        return proc;
    }

    public static ArrayMap<String, ParsedProcess> parseProcesses(
            String[] separateProcesses,
            ParsingPackage parsingPackage,
            Resources res,
            XmlResourceParser parser,
            int flags,
            String[] outError
    ) throws IOException, XmlPullParserException {
        ArraySet<String> deniedPerms = null;
        ArrayMap<String, ParsedProcess> processes = new ArrayMap<>();

        int type;
        final int innerDepth = parser.getDepth();
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }

            String tagName = parser.getName();
            if (tagName.equals("deny-permission")) {
                deniedPerms = parseDenyPermission(deniedPerms, res, parser, outError);
                if (outError[0] != null) {
                    return null;
                }
            } else if (tagName.equals("allow-permission")) {
                deniedPerms = parseAllowPermission(deniedPerms, res, parser, outError);
                if (outError[0] != null) {
                    return null;
                }
            } else if (tagName.equals("process")) {
                ParsedProcess proc = parseProcess(deniedPerms, separateProcesses, parsingPackage,
                        res, parser, flags, outError);
                if (outError[0] != null) {
                    return null;
                }
                if (processes.get(proc.name) != null) {
                    outError[0] = "<process> specified existing name '" + proc.name + "'";
                    return null;
                }
                processes.put(proc.name, proc);
            } else {
                Slog.w(TAG, "Unknown element under <processes>: " + tagName
                        + " at " + parsingPackage.getBaseCodePath() + " "
                        + parser.getPositionDescription());
                XmlUtils.skipCurrentTag(parser);
                continue;
            }
        }

        return processes;
    }

    public static ActivityInfo.WindowLayout parseLayout(Resources res, AttributeSet attrs) {
        TypedArray sw = res.obtainAttributes(attrs,
                R.styleable.AndroidManifestLayout);
+30 −0
Original line number Diff line number Diff line
@@ -215,6 +215,9 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android
    @Nullable
    private ArrayList<String> queriesPackages;

    @Nullable
    private ArrayMap<String, ComponentParseUtils.ParsedProcess> processes;

    private String[] splitClassLoaderNames;
    private String[] splitCodePaths;
    private SparseArray<int[]> splitDependencies;
@@ -527,6 +530,12 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android
        return usesStaticLibraries;
    }

    @Nullable
    @Override
    public ArrayMap<String, ComponentParseUtils.ParsedProcess> getProcesses() {
        return processes;
    }

    @Override
    public boolean isBaseHardwareAccelerated() {
        return baseHardwareAccelerated;
@@ -947,6 +956,12 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android
        return this;
    }

    @Override
    public PackageImpl setProcesses(ArrayMap<String, ComponentParseUtils.ParsedProcess> processes) {
        this.processes = processes;
        return this;
    }

    @Override
    public PackageImpl setSupportsSmallScreens(int supportsSmallScreens) {
        if (supportsSmallScreens == 1) {
@@ -3010,6 +3025,11 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android
        dest.writeStringList(this.usesOptionalLibraries);
        dest.writeStringList(this.usesStaticLibraries);
        dest.writeLongArray(this.usesStaticLibrariesVersions);
        final int numProcesses = this.processes != null ? this.processes.size() : 0;
        dest.writeInt(numProcesses);
        for (int i = 0; i < numProcesses; i++) {
            this.processes.valueAt(i).writeToParcel(dest, 0);
        }

        if (this.usesStaticLibrariesCertDigests == null) {
            dest.writeInt(-1);
@@ -3161,6 +3181,16 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android
        this.usesStaticLibraries = in.createStringArrayList();
        internStringArrayList(usesStaticLibraries);
        this.usesStaticLibrariesVersions = in.createLongArray();
        final int numProcesses = in.readInt();
        if (numProcesses > 0) {
            this.processes = new ArrayMap<>(numProcesses);
            for (int i = 0; i < numProcesses; i++) {
                ComponentParseUtils.ParsedProcess proc = new ComponentParseUtils.ParsedProcess(in);
                this.processes.put(proc.name, proc);
            }
        } else {
            this.processes = null;
        }

        int digestsSize = in.readInt();
        if (digestsSize >= 0) {
Loading