1 /*
2  * Copyright (C) 2014 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;
18 
19 import static com.android.internal.util.ArrayUtils.appendInt;
20 
21 import android.app.ActivityManager;
22 import android.content.ComponentName;
23 import android.content.pm.FeatureInfo;
24 import android.content.pm.PackageManager;
25 import android.os.Environment;
26 import android.os.Process;
27 import android.os.storage.StorageManager;
28 import android.util.ArrayMap;
29 import android.util.ArraySet;
30 import android.util.Slog;
31 import android.util.SparseArray;
32 import android.util.Xml;
33 
34 import com.android.internal.util.XmlUtils;
35 
36 import libcore.io.IoUtils;
37 
38 import org.xmlpull.v1.XmlPullParser;
39 import org.xmlpull.v1.XmlPullParserException;
40 
41 import java.io.File;
42 import java.io.FileNotFoundException;
43 import java.io.FileReader;
44 import java.io.IOException;
45 
46 /**
47  * Loads global system configuration info.
48  */
49 public class SystemConfig {
50     static final String TAG = "SystemConfig";
51 
52     static SystemConfig sInstance;
53 
54     // permission flag, determines which types of configuration are allowed to be read
55     private static final int ALLOW_FEATURES = 0x01;
56     private static final int ALLOW_LIBS = 0x02;
57     private static final int ALLOW_PERMISSIONS = 0x04;
58     private static final int ALLOW_APP_CONFIGS = 0x08;
59     private static final int ALLOW_ALL = ~0;
60 
61     // Group-ids that are given to all packages as read from etc/permissions/*.xml.
62     int[] mGlobalGids;
63 
64     // These are the built-in uid -> permission mappings that were read from the
65     // system configuration files.
66     final SparseArray<ArraySet<String>> mSystemPermissions = new SparseArray<>();
67 
68     // These are the built-in shared libraries that were read from the
69     // system configuration files.  Keys are the library names; strings are the
70     // paths to the libraries.
71     final ArrayMap<String, String> mSharedLibraries  = new ArrayMap<>();
72 
73     // These are the features this devices supports that were read from the
74     // system configuration files.
75     final ArrayMap<String, FeatureInfo> mAvailableFeatures = new ArrayMap<>();
76 
77     // These are the features which this device doesn't support; the OEM
78     // partition uses these to opt-out of features from the system image.
79     final ArraySet<String> mUnavailableFeatures = new ArraySet<>();
80 
81     public static final class PermissionEntry {
82         public final String name;
83         public int[] gids;
84         public boolean perUser;
85 
PermissionEntry(String name, boolean perUser)86         PermissionEntry(String name, boolean perUser) {
87             this.name = name;
88             this.perUser = perUser;
89         }
90     }
91 
92     // These are the permission -> gid mappings that were read from the
93     // system configuration files.
94     final ArrayMap<String, PermissionEntry> mPermissions = new ArrayMap<>();
95 
96     // These are the packages that are white-listed to be able to run in the
97     // background while in power save mode (but not whitelisted from device idle modes),
98     // as read from the configuration files.
99     final ArraySet<String> mAllowInPowerSaveExceptIdle = new ArraySet<>();
100 
101     // These are the packages that are white-listed to be able to run in the
102     // background while in power save mode, as read from the configuration files.
103     final ArraySet<String> mAllowInPowerSave = new ArraySet<>();
104 
105     // These are the packages that are white-listed to be able to run in the
106     // background while in data-usage save mode, as read from the configuration files.
107     final ArraySet<String> mAllowInDataUsageSave = new ArraySet<>();
108 
109     // These are the package names of apps which should be in the 'always'
110     // URL-handling state upon factory reset.
111     final ArraySet<String> mLinkedApps = new ArraySet<>();
112 
113     // These are the packages that are whitelisted to be able to run as system user
114     final ArraySet<String> mSystemUserWhitelistedApps = new ArraySet<>();
115 
116     // These are the packages that should not run under system user
117     final ArraySet<String> mSystemUserBlacklistedApps = new ArraySet<>();
118 
119     // These are the components that are enabled by default as VR mode listener services.
120     final ArraySet<ComponentName> mDefaultVrComponents = new ArraySet<>();
121 
122     // These are the permitted backup transport service components
123     final ArraySet<ComponentName> mBackupTransportWhitelist = new ArraySet<>();
124 
getInstance()125     public static SystemConfig getInstance() {
126         synchronized (SystemConfig.class) {
127             if (sInstance == null) {
128                 sInstance = new SystemConfig();
129             }
130             return sInstance;
131         }
132     }
133 
getGlobalGids()134     public int[] getGlobalGids() {
135         return mGlobalGids;
136     }
137 
getSystemPermissions()138     public SparseArray<ArraySet<String>> getSystemPermissions() {
139         return mSystemPermissions;
140     }
141 
getSharedLibraries()142     public ArrayMap<String, String> getSharedLibraries() {
143         return mSharedLibraries;
144     }
145 
getAvailableFeatures()146     public ArrayMap<String, FeatureInfo> getAvailableFeatures() {
147         return mAvailableFeatures;
148     }
149 
getPermissions()150     public ArrayMap<String, PermissionEntry> getPermissions() {
151         return mPermissions;
152     }
153 
getAllowInPowerSaveExceptIdle()154     public ArraySet<String> getAllowInPowerSaveExceptIdle() {
155         return mAllowInPowerSaveExceptIdle;
156     }
157 
getAllowInPowerSave()158     public ArraySet<String> getAllowInPowerSave() {
159         return mAllowInPowerSave;
160     }
161 
getAllowInDataUsageSave()162     public ArraySet<String> getAllowInDataUsageSave() {
163         return mAllowInDataUsageSave;
164     }
165 
getLinkedApps()166     public ArraySet<String> getLinkedApps() {
167         return mLinkedApps;
168     }
169 
getSystemUserWhitelistedApps()170     public ArraySet<String> getSystemUserWhitelistedApps() {
171         return mSystemUserWhitelistedApps;
172     }
173 
getSystemUserBlacklistedApps()174     public ArraySet<String> getSystemUserBlacklistedApps() {
175         return mSystemUserBlacklistedApps;
176     }
177 
getDefaultVrComponents()178     public ArraySet<ComponentName> getDefaultVrComponents() {
179         return mDefaultVrComponents;
180     }
181 
getBackupTransportWhitelist()182     public ArraySet<ComponentName> getBackupTransportWhitelist() {
183         return mBackupTransportWhitelist;
184     }
185 
SystemConfig()186     SystemConfig() {
187         // Read configuration from system
188         readPermissions(Environment.buildPath(
189                 Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);
190         // Read configuration from the old permissions dir
191         readPermissions(Environment.buildPath(
192                 Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL);
193         // Allow ODM to customize system configs around libs, features and apps
194         int odmPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_APP_CONFIGS;
195         readPermissions(Environment.buildPath(
196                 Environment.getOdmDirectory(), "etc", "sysconfig"), odmPermissionFlag);
197         readPermissions(Environment.buildPath(
198                 Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag);
199         // Only allow OEM to customize features
200         readPermissions(Environment.buildPath(
201                 Environment.getOemDirectory(), "etc", "sysconfig"), ALLOW_FEATURES);
202         readPermissions(Environment.buildPath(
203                 Environment.getOemDirectory(), "etc", "permissions"), ALLOW_FEATURES);
204     }
205 
readPermissions(File libraryDir, int permissionFlag)206     void readPermissions(File libraryDir, int permissionFlag) {
207         // Read permissions from given directory.
208         if (!libraryDir.exists() || !libraryDir.isDirectory()) {
209             if (permissionFlag == ALLOW_ALL) {
210                 Slog.w(TAG, "No directory " + libraryDir + ", skipping");
211             }
212             return;
213         }
214         if (!libraryDir.canRead()) {
215             Slog.w(TAG, "Directory " + libraryDir + " cannot be read");
216             return;
217         }
218 
219         // Iterate over the files in the directory and scan .xml files
220         File platformFile = null;
221         for (File f : libraryDir.listFiles()) {
222             // We'll read platform.xml last
223             if (f.getPath().endsWith("etc/permissions/platform.xml")) {
224                 platformFile = f;
225                 continue;
226             }
227 
228             if (!f.getPath().endsWith(".xml")) {
229                 Slog.i(TAG, "Non-xml file " + f + " in " + libraryDir + " directory, ignoring");
230                 continue;
231             }
232             if (!f.canRead()) {
233                 Slog.w(TAG, "Permissions library file " + f + " cannot be read");
234                 continue;
235             }
236 
237             readPermissionsFromXml(f, permissionFlag);
238         }
239 
240         // Read platform permissions last so it will take precedence
241         if (platformFile != null) {
242             readPermissionsFromXml(platformFile, permissionFlag);
243         }
244     }
245 
readPermissionsFromXml(File permFile, int permissionFlag)246     private void readPermissionsFromXml(File permFile, int permissionFlag) {
247         FileReader permReader = null;
248         try {
249             permReader = new FileReader(permFile);
250         } catch (FileNotFoundException e) {
251             Slog.w(TAG, "Couldn't find or open permissions file " + permFile);
252             return;
253         }
254 
255         final boolean lowRam = ActivityManager.isLowRamDeviceStatic();
256 
257         try {
258             XmlPullParser parser = Xml.newPullParser();
259             parser.setInput(permReader);
260 
261             int type;
262             while ((type=parser.next()) != parser.START_TAG
263                        && type != parser.END_DOCUMENT) {
264                 ;
265             }
266 
267             if (type != parser.START_TAG) {
268                 throw new XmlPullParserException("No start tag found");
269             }
270 
271             if (!parser.getName().equals("permissions") && !parser.getName().equals("config")) {
272                 throw new XmlPullParserException("Unexpected start tag in " + permFile
273                         + ": found " + parser.getName() + ", expected 'permissions' or 'config'");
274             }
275 
276             boolean allowAll = permissionFlag == ALLOW_ALL;
277             boolean allowLibs = (permissionFlag & ALLOW_LIBS) != 0;
278             boolean allowFeatures = (permissionFlag & ALLOW_FEATURES) != 0;
279             boolean allowPermissions = (permissionFlag & ALLOW_PERMISSIONS) != 0;
280             boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0;
281             while (true) {
282                 XmlUtils.nextElement(parser);
283                 if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
284                     break;
285                 }
286 
287                 String name = parser.getName();
288                 if ("group".equals(name) && allowAll) {
289                     String gidStr = parser.getAttributeValue(null, "gid");
290                     if (gidStr != null) {
291                         int gid = android.os.Process.getGidForName(gidStr);
292                         mGlobalGids = appendInt(mGlobalGids, gid);
293                     } else {
294                         Slog.w(TAG, "<group> without gid in " + permFile + " at "
295                                 + parser.getPositionDescription());
296                     }
297 
298                     XmlUtils.skipCurrentTag(parser);
299                     continue;
300                 } else if ("permission".equals(name) && allowPermissions) {
301                     String perm = parser.getAttributeValue(null, "name");
302                     if (perm == null) {
303                         Slog.w(TAG, "<permission> without name in " + permFile + " at "
304                                 + parser.getPositionDescription());
305                         XmlUtils.skipCurrentTag(parser);
306                         continue;
307                     }
308                     perm = perm.intern();
309                     readPermission(parser, perm);
310 
311                 } else if ("assign-permission".equals(name) && allowPermissions) {
312                     String perm = parser.getAttributeValue(null, "name");
313                     if (perm == null) {
314                         Slog.w(TAG, "<assign-permission> without name in " + permFile + " at "
315                                 + parser.getPositionDescription());
316                         XmlUtils.skipCurrentTag(parser);
317                         continue;
318                     }
319                     String uidStr = parser.getAttributeValue(null, "uid");
320                     if (uidStr == null) {
321                         Slog.w(TAG, "<assign-permission> without uid in " + permFile + " at "
322                                 + parser.getPositionDescription());
323                         XmlUtils.skipCurrentTag(parser);
324                         continue;
325                     }
326                     int uid = Process.getUidForName(uidStr);
327                     if (uid < 0) {
328                         Slog.w(TAG, "<assign-permission> with unknown uid \""
329                                 + uidStr + "  in " + permFile + " at "
330                                 + parser.getPositionDescription());
331                         XmlUtils.skipCurrentTag(parser);
332                         continue;
333                     }
334                     perm = perm.intern();
335                     ArraySet<String> perms = mSystemPermissions.get(uid);
336                     if (perms == null) {
337                         perms = new ArraySet<String>();
338                         mSystemPermissions.put(uid, perms);
339                     }
340                     perms.add(perm);
341                     XmlUtils.skipCurrentTag(parser);
342 
343                 } else if ("library".equals(name) && allowLibs) {
344                     String lname = parser.getAttributeValue(null, "name");
345                     String lfile = parser.getAttributeValue(null, "file");
346                     if (lname == null) {
347                         Slog.w(TAG, "<library> without name in " + permFile + " at "
348                                 + parser.getPositionDescription());
349                     } else if (lfile == null) {
350                         Slog.w(TAG, "<library> without file in " + permFile + " at "
351                                 + parser.getPositionDescription());
352                     } else {
353                         //Log.i(TAG, "Got library " + lname + " in " + lfile);
354                         mSharedLibraries.put(lname, lfile);
355                     }
356                     XmlUtils.skipCurrentTag(parser);
357                     continue;
358 
359                 } else if ("feature".equals(name) && allowFeatures) {
360                     String fname = parser.getAttributeValue(null, "name");
361                     int fversion = XmlUtils.readIntAttribute(parser, "version", 0);
362                     boolean allowed;
363                     if (!lowRam) {
364                         allowed = true;
365                     } else {
366                         String notLowRam = parser.getAttributeValue(null, "notLowRam");
367                         allowed = !"true".equals(notLowRam);
368                     }
369                     if (fname == null) {
370                         Slog.w(TAG, "<feature> without name in " + permFile + " at "
371                                 + parser.getPositionDescription());
372                     } else if (allowed) {
373                         addFeature(fname, fversion);
374                     }
375                     XmlUtils.skipCurrentTag(parser);
376                     continue;
377 
378                 } else if ("unavailable-feature".equals(name) && allowFeatures) {
379                     String fname = parser.getAttributeValue(null, "name");
380                     if (fname == null) {
381                         Slog.w(TAG, "<unavailable-feature> without name in " + permFile + " at "
382                                 + parser.getPositionDescription());
383                     } else {
384                         mUnavailableFeatures.add(fname);
385                     }
386                     XmlUtils.skipCurrentTag(parser);
387                     continue;
388 
389                 } else if ("allow-in-power-save-except-idle".equals(name) && allowAll) {
390                     String pkgname = parser.getAttributeValue(null, "package");
391                     if (pkgname == null) {
392                         Slog.w(TAG, "<allow-in-power-save-except-idle> without package in "
393                                 + permFile + " at " + parser.getPositionDescription());
394                     } else {
395                         mAllowInPowerSaveExceptIdle.add(pkgname);
396                     }
397                     XmlUtils.skipCurrentTag(parser);
398                     continue;
399 
400                 } else if ("allow-in-power-save".equals(name) && allowAll) {
401                     String pkgname = parser.getAttributeValue(null, "package");
402                     if (pkgname == null) {
403                         Slog.w(TAG, "<allow-in-power-save> without package in " + permFile + " at "
404                                 + parser.getPositionDescription());
405                     } else {
406                         mAllowInPowerSave.add(pkgname);
407                     }
408                     XmlUtils.skipCurrentTag(parser);
409                     continue;
410 
411                 } else if ("allow-in-data-usage-save".equals(name) && allowAll) {
412                     String pkgname = parser.getAttributeValue(null, "package");
413                     if (pkgname == null) {
414                         Slog.w(TAG, "<allow-in-data-usage-save> without package in " + permFile
415                                 + " at " + parser.getPositionDescription());
416                     } else {
417                         mAllowInDataUsageSave.add(pkgname);
418                     }
419                     XmlUtils.skipCurrentTag(parser);
420                     continue;
421 
422                 } else if ("app-link".equals(name) && allowAppConfigs) {
423                     String pkgname = parser.getAttributeValue(null, "package");
424                     if (pkgname == null) {
425                         Slog.w(TAG, "<app-link> without package in " + permFile + " at "
426                                 + parser.getPositionDescription());
427                     } else {
428                         mLinkedApps.add(pkgname);
429                     }
430                     XmlUtils.skipCurrentTag(parser);
431                 } else if ("system-user-whitelisted-app".equals(name) && allowAppConfigs) {
432                     String pkgname = parser.getAttributeValue(null, "package");
433                     if (pkgname == null) {
434                         Slog.w(TAG, "<system-user-whitelisted-app> without package in " + permFile
435                                 + " at " + parser.getPositionDescription());
436                     } else {
437                         mSystemUserWhitelistedApps.add(pkgname);
438                     }
439                     XmlUtils.skipCurrentTag(parser);
440                 } else if ("system-user-blacklisted-app".equals(name) && allowAppConfigs) {
441                     String pkgname = parser.getAttributeValue(null, "package");
442                     if (pkgname == null) {
443                         Slog.w(TAG, "<system-user-blacklisted-app without package in " + permFile
444                                 + " at " + parser.getPositionDescription());
445                     } else {
446                         mSystemUserBlacklistedApps.add(pkgname);
447                     }
448                     XmlUtils.skipCurrentTag(parser);
449                 } else if ("default-enabled-vr-app".equals(name) && allowAppConfigs) {
450                     String pkgname = parser.getAttributeValue(null, "package");
451                     String clsname = parser.getAttributeValue(null, "class");
452                     if (pkgname == null) {
453                         Slog.w(TAG, "<default-enabled-vr-app without package in " + permFile
454                                 + " at " + parser.getPositionDescription());
455                     } else if (clsname == null) {
456                         Slog.w(TAG, "<default-enabled-vr-app without class in " + permFile
457                                 + " at " + parser.getPositionDescription());
458                     } else {
459                         mDefaultVrComponents.add(new ComponentName(pkgname, clsname));
460                     }
461                     XmlUtils.skipCurrentTag(parser);
462                 } else if ("backup-transport-whitelisted-service".equals(name) && allowFeatures) {
463                     String serviceName = parser.getAttributeValue(null, "service");
464                     if (serviceName == null) {
465                         Slog.w(TAG, "<backup-transport-whitelisted-service> without service in "
466                                 + permFile + " at " + parser.getPositionDescription());
467                     } else {
468                         ComponentName cn = ComponentName.unflattenFromString(serviceName);
469                         if (cn == null) {
470                             Slog.w(TAG,
471                                     "<backup-transport-whitelisted-service> with invalid service name "
472                                     + serviceName + " in "+ permFile
473                                     + " at " + parser.getPositionDescription());
474                         } else {
475                             mBackupTransportWhitelist.add(cn);
476                         }
477                     }
478                     XmlUtils.skipCurrentTag(parser);
479                 } else {
480                     XmlUtils.skipCurrentTag(parser);
481                     continue;
482                 }
483             }
484         } catch (XmlPullParserException e) {
485             Slog.w(TAG, "Got exception parsing permissions.", e);
486         } catch (IOException e) {
487             Slog.w(TAG, "Got exception parsing permissions.", e);
488         } finally {
489             IoUtils.closeQuietly(permReader);
490         }
491 
492         // Some devices can be field-converted to FBE, so offer to splice in
493         // those features if not already defined by the static config
494         if (StorageManager.isFileEncryptedNativeOnly()) {
495             addFeature(PackageManager.FEATURE_FILE_BASED_ENCRYPTION, 0);
496             addFeature(PackageManager.FEATURE_SECURELY_REMOVES_USERS, 0);
497         }
498 
499         for (String featureName : mUnavailableFeatures) {
500             removeFeature(featureName);
501         }
502     }
503 
addFeature(String name, int version)504     private void addFeature(String name, int version) {
505         FeatureInfo fi = mAvailableFeatures.get(name);
506         if (fi == null) {
507             fi = new FeatureInfo();
508             fi.name = name;
509             fi.version = version;
510             mAvailableFeatures.put(name, fi);
511         } else {
512             fi.version = Math.max(fi.version, version);
513         }
514     }
515 
removeFeature(String name)516     private void removeFeature(String name) {
517         if (mAvailableFeatures.remove(name) != null) {
518             Slog.d(TAG, "Removed unavailable feature " + name);
519         }
520     }
521 
readPermission(XmlPullParser parser, String name)522     void readPermission(XmlPullParser parser, String name)
523             throws IOException, XmlPullParserException {
524         if (mPermissions.containsKey(name)) {
525             throw new IllegalStateException("Duplicate permission definition for " + name);
526         }
527 
528         final boolean perUser = XmlUtils.readBooleanAttribute(parser, "perUser", false);
529         final PermissionEntry perm = new PermissionEntry(name, perUser);
530         mPermissions.put(name, perm);
531 
532         int outerDepth = parser.getDepth();
533         int type;
534         while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
535                && (type != XmlPullParser.END_TAG
536                        || parser.getDepth() > outerDepth)) {
537             if (type == XmlPullParser.END_TAG
538                     || type == XmlPullParser.TEXT) {
539                 continue;
540             }
541 
542             String tagName = parser.getName();
543             if ("group".equals(tagName)) {
544                 String gidStr = parser.getAttributeValue(null, "gid");
545                 if (gidStr != null) {
546                     int gid = Process.getGidForName(gidStr);
547                     perm.gids = appendInt(perm.gids, gid);
548                 } else {
549                     Slog.w(TAG, "<group> without gid at "
550                             + parser.getPositionDescription());
551                 }
552             }
553             XmlUtils.skipCurrentTag(parser);
554         }
555     }
556 }
557