1 /*
2  * Copyright (C) 2012 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.server.webkit;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.content.pm.PackageInfo;
24 import android.content.pm.PackageManager;
25 import android.content.pm.PackageManagerInternal;
26 import android.os.Binder;
27 import android.os.PatternMatcher;
28 import android.os.Process;
29 import android.os.ResultReceiver;
30 import android.os.ShellCallback;
31 import android.os.UserHandle;
32 import android.util.Slog;
33 import android.webkit.IWebViewUpdateService;
34 import android.webkit.WebViewProviderInfo;
35 import android.webkit.WebViewProviderResponse;
36 
37 import com.android.internal.util.DumpUtils;
38 import com.android.server.LocalServices;
39 import com.android.server.SystemService;
40 
41 import java.io.FileDescriptor;
42 import java.io.PrintWriter;
43 import java.util.Arrays;
44 
45 /**
46  * Private service to wait for the updatable WebView to be ready for use.
47  * @hide
48  */
49 public class WebViewUpdateService extends SystemService {
50 
51     private static final String TAG = "WebViewUpdateService";
52 
53     private BroadcastReceiver mWebViewUpdatedReceiver;
54     private WebViewUpdateServiceImpl mImpl;
55 
56     static final int PACKAGE_CHANGED = 0;
57     static final int PACKAGE_ADDED = 1;
58     static final int PACKAGE_ADDED_REPLACED = 2;
59     static final int PACKAGE_REMOVED = 3;
60 
WebViewUpdateService(Context context)61     public WebViewUpdateService(Context context) {
62         super(context);
63         mImpl = new WebViewUpdateServiceImpl(context, SystemImpl.getInstance());
64     }
65 
66     @Override
onStart()67     public void onStart() {
68         mWebViewUpdatedReceiver = new BroadcastReceiver() {
69                 @Override
70                 public void onReceive(Context context, Intent intent) {
71                     int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
72                     switch (intent.getAction()) {
73                         case Intent.ACTION_PACKAGE_REMOVED:
74                             // When a package is replaced we will receive two intents, one
75                             // representing the removal of the old package and one representing the
76                             // addition of the new package.
77                             // In the case where we receive an intent to remove the old version of
78                             // the package that is being replaced we early-out here so that we don't
79                             // run the update-logic twice.
80                             if (intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)) return;
81                             mImpl.packageStateChanged(packageNameFromIntent(intent),
82                                     PACKAGE_REMOVED, userId);
83                             break;
84                         case Intent.ACTION_PACKAGE_CHANGED:
85                             // Ensure that we only heed PACKAGE_CHANGED intents if they change an
86                             // entire package, not just a component
87                             if (entirePackageChanged(intent)) {
88                                 mImpl.packageStateChanged(packageNameFromIntent(intent),
89                                         PACKAGE_CHANGED, userId);
90                             }
91                             break;
92                         case Intent.ACTION_PACKAGE_ADDED:
93                             mImpl.packageStateChanged(packageNameFromIntent(intent),
94                                     (intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)
95                                      ? PACKAGE_ADDED_REPLACED : PACKAGE_ADDED), userId);
96                             break;
97                         case Intent.ACTION_USER_STARTED:
98                             mImpl.handleNewUser(userId);
99                             break;
100                         case Intent.ACTION_USER_REMOVED:
101                             mImpl.handleUserRemoved(userId);
102                             break;
103                     }
104                 }
105         };
106         IntentFilter filter = new IntentFilter();
107         filter.addAction(Intent.ACTION_PACKAGE_ADDED);
108         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
109         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
110         filter.addDataScheme("package");
111         // Make sure we only receive intents for WebView packages from our config file.
112         for (WebViewProviderInfo provider : mImpl.getWebViewPackages()) {
113             filter.addDataSchemeSpecificPart(provider.packageName, PatternMatcher.PATTERN_LITERAL);
114         }
115 
116         getContext().registerReceiverAsUser(mWebViewUpdatedReceiver, UserHandle.ALL, filter,
117                 null /* broadcast permission */, null /* handler */);
118 
119         IntentFilter userAddedFilter = new IntentFilter();
120         userAddedFilter.addAction(Intent.ACTION_USER_STARTED);
121         userAddedFilter.addAction(Intent.ACTION_USER_REMOVED);
122         getContext().registerReceiverAsUser(mWebViewUpdatedReceiver, UserHandle.ALL,
123                 userAddedFilter, null /* broadcast permission */, null /* handler */);
124 
125         publishBinderService("webviewupdate", new BinderService(), true /*allowIsolated*/);
126     }
127 
prepareWebViewInSystemServer()128     public void prepareWebViewInSystemServer() {
129         mImpl.prepareWebViewInSystemServer();
130     }
131 
packageNameFromIntent(Intent intent)132     private static String packageNameFromIntent(Intent intent) {
133         return intent.getDataString().substring("package:".length());
134     }
135 
136     /**
137      * Returns whether the entire package from an ACTION_PACKAGE_CHANGED intent was changed (rather
138      * than just one of its components).
139      * @hide
140      */
entirePackageChanged(Intent intent)141     public static boolean entirePackageChanged(Intent intent) {
142         String[] componentList =
143             intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
144         return Arrays.asList(componentList).contains(
145                 intent.getDataString().substring("package:".length()));
146     }
147 
148     private class BinderService extends IWebViewUpdateService.Stub {
149 
150         @Override
onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver)151         public void onShellCommand(FileDescriptor in, FileDescriptor out,
152                 FileDescriptor err, String[] args, ShellCallback callback,
153                 ResultReceiver resultReceiver) {
154             (new WebViewUpdateServiceShellCommand(this)).exec(
155                     this, in, out, err, args, callback, resultReceiver);
156         }
157 
158 
159         /**
160          * The shared relro process calls this to notify us that it's done trying to create a relro
161          * file. This method gets called even if the relro creation has failed or the process
162          * crashed.
163          */
164         @Override // Binder call
notifyRelroCreationCompleted()165         public void notifyRelroCreationCompleted() {
166             // Verify that the caller is either the shared relro process (nominal case) or the
167             // system server (only in the case the relro process crashes and we get here via the
168             // crashHandler).
169             if (Binder.getCallingUid() != Process.SHARED_RELRO_UID &&
170                     Binder.getCallingUid() != Process.SYSTEM_UID) {
171                 return;
172             }
173 
174             final long callingId = Binder.clearCallingIdentity();
175             try {
176                 WebViewUpdateService.this.mImpl.notifyRelroCreationCompleted();
177             } finally {
178                 Binder.restoreCallingIdentity(callingId);
179             }
180         }
181 
182         /**
183          * WebViewFactory calls this to block WebView loading until the relro file is created.
184          * Returns the WebView provider for which we create relro files.
185          */
186         @Override // Binder call
waitForAndGetProvider()187         public WebViewProviderResponse waitForAndGetProvider() {
188             // The WebViewUpdateService depends on the prepareWebViewInSystemServer call, which
189             // happens later (during the PHASE_ACTIVITY_MANAGER_READY) in SystemServer.java. If
190             // another service there tries to bring up a WebView in the between, the wait below
191             // would deadlock without the check below.
192             if (Binder.getCallingPid() == Process.myPid()) {
193                 throw new IllegalStateException("Cannot create a WebView from the SystemServer");
194             }
195 
196             final WebViewProviderResponse webViewProviderResponse =
197                     WebViewUpdateService.this.mImpl.waitForAndGetProvider();
198             if (webViewProviderResponse.packageInfo != null) {
199                 grantVisibilityToCaller(
200                         webViewProviderResponse.packageInfo.packageName, Binder.getCallingUid());
201             }
202             return webViewProviderResponse;
203         }
204 
205         /**
206          * Grants app visibility of the webViewPackageName to the currently bound caller.
207          * @param webViewPackageName
208          */
grantVisibilityToCaller(String webViewPackageName, int callingUid)209         private void grantVisibilityToCaller(String webViewPackageName, int callingUid) {
210             final PackageManagerInternal pmInternal = LocalServices.getService(
211                     PackageManagerInternal.class);
212             final int webviewUid = pmInternal.getPackageUid(
213                     webViewPackageName, 0 /* flags */, UserHandle.getUserId(callingUid));
214             pmInternal.grantImplicitAccess(UserHandle.getUserId(callingUid), null,
215                     UserHandle.getAppId(callingUid), webviewUid,
216                     true /*direct*/);
217         }
218 
219         /**
220          * This is called from DeveloperSettings when the user changes WebView provider.
221          */
222         @Override // Binder call
changeProviderAndSetting(String newProvider)223         public String changeProviderAndSetting(String newProvider) {
224             if (getContext().checkCallingPermission(
225                         android.Manifest.permission.WRITE_SECURE_SETTINGS)
226                     != PackageManager.PERMISSION_GRANTED) {
227                 String msg = "Permission Denial: changeProviderAndSetting() from pid="
228                         + Binder.getCallingPid()
229                         + ", uid=" + Binder.getCallingUid()
230                         + " requires " + android.Manifest.permission.WRITE_SECURE_SETTINGS;
231                 Slog.w(TAG, msg);
232                 throw new SecurityException(msg);
233             }
234 
235             final long callingId = Binder.clearCallingIdentity();
236             try {
237                 return WebViewUpdateService.this.mImpl.changeProviderAndSetting(
238                         newProvider);
239             } finally {
240                 Binder.restoreCallingIdentity(callingId);
241             }
242         }
243 
244         @Override // Binder call
getValidWebViewPackages()245         public WebViewProviderInfo[] getValidWebViewPackages() {
246             return WebViewUpdateService.this.mImpl.getValidWebViewPackages();
247         }
248 
249         @Override // Binder call
getAllWebViewPackages()250         public WebViewProviderInfo[] getAllWebViewPackages() {
251             return WebViewUpdateService.this.mImpl.getWebViewPackages();
252         }
253 
254         @Override // Binder call
getCurrentWebViewPackageName()255         public String getCurrentWebViewPackageName() {
256             PackageInfo pi = getCurrentWebViewPackage();
257             return pi == null ? null : pi.packageName;
258         }
259 
260         @Override // Binder call
getCurrentWebViewPackage()261         public PackageInfo getCurrentWebViewPackage() {
262             final PackageInfo currentWebViewPackage =
263                     WebViewUpdateService.this.mImpl.getCurrentWebViewPackage();
264             if (currentWebViewPackage != null) {
265                 grantVisibilityToCaller(currentWebViewPackage.packageName, Binder.getCallingUid());
266             }
267             return currentWebViewPackage;
268         }
269 
270         @Override // Binder call
isMultiProcessEnabled()271         public boolean isMultiProcessEnabled() {
272             return WebViewUpdateService.this.mImpl.isMultiProcessEnabled();
273         }
274 
275         @Override // Binder call
enableMultiProcess(boolean enable)276         public void enableMultiProcess(boolean enable) {
277             if (getContext().checkCallingPermission(
278                         android.Manifest.permission.WRITE_SECURE_SETTINGS)
279                     != PackageManager.PERMISSION_GRANTED) {
280                 String msg = "Permission Denial: enableMultiProcess() from pid="
281                         + Binder.getCallingPid()
282                         + ", uid=" + Binder.getCallingUid()
283                         + " requires " + android.Manifest.permission.WRITE_SECURE_SETTINGS;
284                 Slog.w(TAG, msg);
285                 throw new SecurityException(msg);
286             }
287 
288             final long callingId = Binder.clearCallingIdentity();
289             try {
290                 WebViewUpdateService.this.mImpl.enableMultiProcess(enable);
291             } finally {
292                 Binder.restoreCallingIdentity(callingId);
293             }
294         }
295 
296         @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)297         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
298             if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
299             WebViewUpdateService.this.mImpl.dumpState(pw);
300         }
301     }
302 }
303