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
17package com.android.server.pm;
18
19import android.content.pm.ApplicationInfo;
20import android.content.pm.PackageParser;
21import android.content.pm.Signature;
22import android.os.Environment;
23import android.util.Slog;
24import android.util.Xml;
25
26import com.android.internal.util.XmlUtils;
27
28import java.io.File;
29import java.io.FileInputStream;
30import java.io.FileNotFoundException;
31import java.io.FileReader;
32import java.io.IOException;
33
34import java.util.HashMap;
35
36import org.xmlpull.v1.XmlPullParser;
37import org.xmlpull.v1.XmlPullParserException;
38
39/**
40 * Centralized access to SELinux MMAC (middleware MAC) implementation.
41 * {@hide}
42 */
43public final class SELinuxMMAC {
44
45    private static final String TAG = "SELinuxMMAC";
46
47    private static final boolean DEBUG_POLICY = false;
48    private static final boolean DEBUG_POLICY_INSTALL = DEBUG_POLICY || false;
49
50    // Signature seinfo values read from policy.
51    private static final HashMap<Signature, String> sSigSeinfo =
52        new HashMap<Signature, String>();
53
54    // Package name seinfo values read from policy.
55    private static final HashMap<String, String> sPackageSeinfo =
56        new HashMap<String, String>();
57
58    // Locations of potential install policy files.
59    private static final File[] INSTALL_POLICY_FILE = {
60        new File(Environment.getDataDirectory(), "security/mac_permissions.xml"),
61        new File(Environment.getRootDirectory(), "etc/security/mac_permissions.xml"),
62        null};
63
64    private static void flushInstallPolicy() {
65        sSigSeinfo.clear();
66        sPackageSeinfo.clear();
67    }
68
69    /**
70     * Parses an MMAC install policy from a predefined list of locations.
71     * @param none
72     * @return boolean indicating whether an install policy was correctly parsed.
73     */
74    public static boolean readInstallPolicy() {
75
76        return readInstallPolicy(INSTALL_POLICY_FILE);
77    }
78
79    /**
80     * Parses an MMAC install policy given as an argument.
81     * @param File object representing the path of the policy.
82     * @return boolean indicating whether the install policy was correctly parsed.
83     */
84    public static boolean readInstallPolicy(File policyFile) {
85
86        return readInstallPolicy(new File[]{policyFile,null});
87    }
88
89    private static boolean readInstallPolicy(File[] policyFiles) {
90
91        FileReader policyFile = null;
92        int i = 0;
93        while (policyFile == null && policyFiles != null && policyFiles[i] != null) {
94            try {
95                policyFile = new FileReader(policyFiles[i]);
96                break;
97            } catch (FileNotFoundException e) {
98                Slog.d(TAG,"Couldn't find install policy " + policyFiles[i].getPath());
99            }
100            i++;
101        }
102
103        if (policyFile == null) {
104            Slog.d(TAG, "No policy file found. All seinfo values will be null.");
105            return false;
106        }
107
108        Slog.d(TAG, "Using install policy file " + policyFiles[i].getPath());
109
110        flushInstallPolicy();
111
112        try {
113            XmlPullParser parser = Xml.newPullParser();
114            parser.setInput(policyFile);
115
116            XmlUtils.beginDocument(parser, "policy");
117            while (true) {
118                XmlUtils.nextElement(parser);
119                if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
120                    break;
121                }
122
123                String tagName = parser.getName();
124                if ("signer".equals(tagName)) {
125                    String cert = parser.getAttributeValue(null, "signature");
126                    if (cert == null) {
127                        Slog.w(TAG, "<signer> without signature at "
128                               + parser.getPositionDescription());
129                        XmlUtils.skipCurrentTag(parser);
130                        continue;
131                    }
132                    Signature signature;
133                    try {
134                        signature = new Signature(cert);
135                    } catch (IllegalArgumentException e) {
136                        Slog.w(TAG, "<signer> with bad signature at "
137                               + parser.getPositionDescription(), e);
138                        XmlUtils.skipCurrentTag(parser);
139                        continue;
140                    }
141                    String seinfo = readSeinfoTag(parser);
142                    if (seinfo != null) {
143                        if (DEBUG_POLICY_INSTALL)
144                            Slog.i(TAG, "<signer> tag: (" + cert + ") assigned seinfo="
145                                   + seinfo);
146
147                        sSigSeinfo.put(signature, seinfo);
148                    }
149                } else if ("default".equals(tagName)) {
150                    String seinfo = readSeinfoTag(parser);
151                    if (seinfo != null) {
152                        if (DEBUG_POLICY_INSTALL)
153                            Slog.i(TAG, "<default> tag assigned seinfo=" + seinfo);
154
155                        // The 'null' signature is the default seinfo value
156                        sSigSeinfo.put(null, seinfo);
157                    }
158                } else if ("package".equals(tagName)) {
159                    String pkgName = parser.getAttributeValue(null, "name");
160                    if (pkgName == null) {
161                        Slog.w(TAG, "<package> without name at "
162                               + parser.getPositionDescription());
163                        XmlUtils.skipCurrentTag(parser);
164                        continue;
165                    }
166                    String seinfo = readSeinfoTag(parser);
167                    if (seinfo != null) {
168                        if (DEBUG_POLICY_INSTALL)
169                            Slog.i(TAG, "<package> tag: (" + pkgName +
170                                   ") assigned seinfo=" + seinfo);
171
172                        sPackageSeinfo.put(pkgName, seinfo);
173                    }
174                } else {
175                    XmlUtils.skipCurrentTag(parser);
176                    continue;
177                }
178            }
179        } catch (XmlPullParserException e) {
180            Slog.w(TAG, "Got execption parsing ", e);
181        } catch (IOException e) {
182            Slog.w(TAG, "Got execption parsing ", e);
183        }
184        try {
185            policyFile.close();
186        } catch (IOException e) {
187            //omit
188        }
189        return true;
190    }
191
192    private static String readSeinfoTag(XmlPullParser parser) throws
193            IOException, XmlPullParserException {
194
195        int type;
196        int outerDepth = parser.getDepth();
197        String seinfo = null;
198        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
199               && (type != XmlPullParser.END_TAG
200                   || parser.getDepth() > outerDepth)) {
201            if (type == XmlPullParser.END_TAG
202                || type == XmlPullParser.TEXT) {
203                continue;
204            }
205
206            String tagName = parser.getName();
207            if ("seinfo".equals(tagName)) {
208                String seinfoValue = parser.getAttributeValue(null, "value");
209                if (validateValue(seinfoValue)) {
210                    seinfo = seinfoValue;
211                } else {
212                    Slog.w(TAG, "<seinfo> without valid value at "
213                           + parser.getPositionDescription());
214                }
215            }
216            XmlUtils.skipCurrentTag(parser);
217        }
218        return seinfo;
219    }
220
221    /**
222     * General validation routine for tag values.
223     * Returns a boolean indicating if the passed string
224     * contains only letters or underscores.
225     */
226    private static boolean validateValue(String name) {
227        if (name == null)
228            return false;
229
230        final int N = name.length();
231        if (N == 0)
232            return false;
233
234        for (int i = 0; i < N; i++) {
235            final char c = name.charAt(i);
236            if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c != '_')) {
237                return false;
238            }
239        }
240        return true;
241    }
242
243    /**
244     * Labels a package based on an seinfo tag from install policy.
245     * The label is attached to the ApplicationInfo instance of the package.
246     * @param PackageParser.Package object representing the package
247     *         to labeled.
248     * @return String holding the value of the seinfo label that was assigned.
249     *         Value may be null which indicates no seinfo label was assigned.
250     */
251    public static void assignSeinfoValue(PackageParser.Package pkg) {
252
253        /*
254         * Non system installed apps should be treated the same. This
255         * means that any post-loaded apk will be assigned the default
256         * tag, if one exists in the policy, else null, without respect
257         * to the signing key.
258         */
259        if (((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) ||
260            ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) {
261
262            // We just want one of the signatures to match.
263            for (Signature s : pkg.mSignatures) {
264                if (s == null)
265                    continue;
266
267                if (sSigSeinfo.containsKey(s)) {
268                    String seinfo = pkg.applicationInfo.seinfo = sSigSeinfo.get(s);
269                    if (DEBUG_POLICY_INSTALL)
270                        Slog.i(TAG, "package (" + pkg.packageName +
271                               ") labeled with seinfo=" + seinfo);
272
273                    return;
274                }
275            }
276
277            // Check for seinfo labeled by package.
278            if (sPackageSeinfo.containsKey(pkg.packageName)) {
279                String seinfo = pkg.applicationInfo.seinfo = sPackageSeinfo.get(pkg.packageName);
280                if (DEBUG_POLICY_INSTALL)
281                    Slog.i(TAG, "package (" + pkg.packageName +
282                           ") labeled with seinfo=" + seinfo);
283                return;
284            }
285        }
286
287        // If we have a default seinfo value then great, otherwise
288        // we set a null object and that is what we started with.
289        String seinfo = pkg.applicationInfo.seinfo = sSigSeinfo.get(null);
290        if (DEBUG_POLICY_INSTALL)
291            Slog.i(TAG, "package (" + pkg.packageName +
292                   ") labeled with seinfo=" + (seinfo == null ? "null" : seinfo));
293    }
294}
295