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
17package com.android.server;
18
19import static com.android.internal.util.ArrayUtils.appendInt;
20
21import android.app.ActivityManager;
22import android.content.ComponentName;
23import android.content.pm.FeatureInfo;
24import android.content.pm.PackageManager;
25import android.os.Environment;
26import android.os.Process;
27import android.os.storage.StorageManager;
28import android.util.ArrayMap;
29import android.util.ArraySet;
30import android.util.Slog;
31import android.util.SparseArray;
32import android.util.Xml;
33
34import com.android.internal.util.XmlUtils;
35
36import libcore.io.IoUtils;
37
38import org.xmlpull.v1.XmlPullParser;
39import org.xmlpull.v1.XmlPullParserException;
40
41import java.io.File;
42import java.io.FileNotFoundException;
43import java.io.FileReader;
44import java.io.IOException;
45
46/**
47 * Loads global system configuration info.
48 */
49public 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
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
125    public static SystemConfig getInstance() {
126        synchronized (SystemConfig.class) {
127            if (sInstance == null) {
128                sInstance = new SystemConfig();
129            }
130            return sInstance;
131        }
132    }
133
134    public int[] getGlobalGids() {
135        return mGlobalGids;
136    }
137
138    public SparseArray<ArraySet<String>> getSystemPermissions() {
139        return mSystemPermissions;
140    }
141
142    public ArrayMap<String, String> getSharedLibraries() {
143        return mSharedLibraries;
144    }
145
146    public ArrayMap<String, FeatureInfo> getAvailableFeatures() {
147        return mAvailableFeatures;
148    }
149
150    public ArrayMap<String, PermissionEntry> getPermissions() {
151        return mPermissions;
152    }
153
154    public ArraySet<String> getAllowInPowerSaveExceptIdle() {
155        return mAllowInPowerSaveExceptIdle;
156    }
157
158    public ArraySet<String> getAllowInPowerSave() {
159        return mAllowInPowerSave;
160    }
161
162    public ArraySet<String> getAllowInDataUsageSave() {
163        return mAllowInDataUsageSave;
164    }
165
166    public ArraySet<String> getLinkedApps() {
167        return mLinkedApps;
168    }
169
170    public ArraySet<String> getSystemUserWhitelistedApps() {
171        return mSystemUserWhitelistedApps;
172    }
173
174    public ArraySet<String> getSystemUserBlacklistedApps() {
175        return mSystemUserBlacklistedApps;
176    }
177
178    public ArraySet<ComponentName> getDefaultVrComponents() {
179        return mDefaultVrComponents;
180    }
181
182    public ArraySet<ComponentName> getBackupTransportWhitelist() {
183        return mBackupTransportWhitelist;
184    }
185
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
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
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
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
516    private void removeFeature(String name) {
517        if (mAvailableFeatures.remove(name) != null) {
518            Slog.d(TAG, "Removed unavailable feature " + name);
519        }
520    }
521
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