• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ondevicepersonalization.libraries.plugin.internal;
18 
19 import androidx.annotation.NonNull;
20 
21 import com.google.common.collect.ImmutableSet;
22 
23 /**
24  * An isolation {@link ClassLoader} layer between plugin container's class loader and plugin's class
25  * loader. The layer mediates class loading requests and responses. By default it only allows
26  * classes at Android framework as well as explicitly specified at the {@link
27  * IsolationClassLoader#containerClassesAllowlist} to be loaded outside plugin's class loader to
28  * secure class loading boundary.
29  */
30 public final class IsolationClassLoader extends ClassLoader {
31     private final ImmutableSet<String> mContainerClassesAllowlist;
32     private final ImmutableSet<String> mContainerPackagesAllowlist;
33     private ImmutableSet<String> mRejectedClassesList = ImmutableSet.of();
34 
35     /**
36      * Create an isolation layer to manage class loading requests/responses to/from plugin
37      * container's class loader.
38      *
39      * <p>Callers can optionally supply a classes allow list to specify additional classes the layer
40      * allows for passing its isolation boundary.
41      *
42      * @param containerClassLoader The plugin container's class loader to be isolated and managed.
43      * @param containerClassesAllowlist The classes list to allow pass-through of class loading
44      *     requests/responses.
45      */
IsolationClassLoader( @onNull ClassLoader containerClassLoader, @NonNull ImmutableSet<String> containerClassesAllowlist, @NonNull ImmutableSet<String> containerPackagesAllowlist)46     public IsolationClassLoader(
47             @NonNull ClassLoader containerClassLoader,
48             @NonNull ImmutableSet<String> containerClassesAllowlist,
49             @NonNull ImmutableSet<String> containerPackagesAllowlist) {
50         super(containerClassLoader);
51         this.mContainerClassesAllowlist = containerClassesAllowlist;
52         this.mContainerPackagesAllowlist = containerPackagesAllowlist;
53     }
54 
55     /**
56      * The same as {@link IsolationClassLoader#IsolationClassLoader(ClassLoader, String...)} with an
57      * additional rejected-classes-list to specify what classes should be forbidden for use.
58      *
59      * <p>This is useful for callers to implement their own runtime polices checking if there are
60      * any disallowed java classes being used unexpectedly in code. E.g., a policy does not allow
61      * thread creation nor tasks scheduled in different thread context, simply specify {@link
62      * java.util.concurrent.ExecutorService} and {@link Thread} in rejectedClassesList to fulfill
63      * the policy requirement.
64      *
65      * @param containerClassLoader The plugin container's class loader to be isolated and managed.
66      * @param rejectedClassesList The classes list to be rejected if being used.
67      * @param containerClassesAllowlist The extra classes list to allow pass-through of * class
68      *     loading requests/responses.
69      */
IsolationClassLoader( @onNull ClassLoader containerClassLoader, @NonNull ImmutableSet<String> containerClassesAllowlist, @NonNull ImmutableSet<String> containerPackagesAllowlist, @NonNull ImmutableSet<String> rejectedClassesList)70     public IsolationClassLoader(
71             @NonNull ClassLoader containerClassLoader,
72             @NonNull ImmutableSet<String> containerClassesAllowlist,
73             @NonNull ImmutableSet<String> containerPackagesAllowlist,
74             @NonNull ImmutableSet<String> rejectedClassesList) {
75         this(containerClassLoader, containerClassesAllowlist, containerPackagesAllowlist);
76         this.mRejectedClassesList = rejectedClassesList;
77     }
78 
startsWithAnyPrefixFromSet(String name, ImmutableSet<String> prefixes)79     private static boolean startsWithAnyPrefixFromSet(String name, ImmutableSet<String> prefixes) {
80         for (String prefix : prefixes) {
81             if (name.startsWith(prefix)) {
82                 return true;
83             }
84         }
85         return false;
86     }
87 
88     @Override
loadClass(String name, boolean resolve)89     protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
90         Class<?> clazz = null;
91 
92         // Reject forbidden classes to be loaded unexpectedly.
93         if (mRejectedClassesList.contains(name)) {
94             throw new AssertionError("class " + name + " is forbidden!");
95         }
96 
97         // Try loading classes from Android boot classes (Android framework).
98         try {
99             clazz = getSystemClassLoader().loadClass(name);
100         } catch (ClassNotFoundException ignored) {
101             // Non-Android-framework classes would go through the following process, so continue.
102         }
103 
104         // Allow allow-listed container classes to be loaded outside plugin's class loader.
105         if (clazz == null
106                 && (mContainerClassesAllowlist.contains(name)
107                         || startsWithAnyPrefixFromSet(name, mContainerPackagesAllowlist))) {
108             clazz = super.loadClass(name, resolve);
109         }
110 
111         // Push back to use plugin's class loader.
112         if (clazz == null) {
113             throw new ClassNotFoundException("Isolation: deferring to plugin's class loader");
114         }
115 
116         return clazz;
117     }
118 }
119