1#!/usr/bin/env python
2
3#
4# Copyright 2016, The Android Open Source Project
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#     http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
18
19"""
20  privapp_permission.py: Generates privapp-permissions.xml file for
21  apps in system/priv-app directory
22
23  Usage:
24  . build/envsetup.sh
25  lunch product_name
26  m -j32
27  development/tools/privapp_permissions/privapp_permissions.py
28
29"""
30
31import os
32import re
33import subprocess
34from xml.dom import minidom
35
36try:
37    ANDROID_PRODUCT_OUT = os.environ['ANDROID_PRODUCT_OUT']
38    ANDROID_HOST_OUT = os.environ['ANDROID_HOST_OUT']
39except KeyError as e:
40    exit("Build environment not set up - " + str(e))
41BASE_XML_FNAME = "privapp-permissions-platform.xml"
42
43def main():
44    # Parse base XML files in /etc dir, permissions listed there don't have to be re-added
45    base_permissions = {}
46    for xml_file in list_config_xml_files():
47        parse_config_xml(xml_file, base_permissions)
48
49    # Extract signature|privileged permissions available in the platform
50    framework_apk = os.path.join(ANDROID_PRODUCT_OUT, 'system/framework/framework-res.apk')
51    platform_priv_permissions = extract_priv_permissions(framework_apk)
52
53    priv_apps = list_privapps()
54    apps_redefine_base = []
55    results = {}
56    for priv_app in priv_apps:
57        pkg_info = extract_pkg_and_requested_permissions(priv_app)
58        pkg_name = pkg_info['package_name']
59        priv_perms = get_priv_permissions(pkg_info['permissions'], platform_priv_permissions)
60        # Compute diff against permissions defined in base file
61        if base_permissions and (pkg_name in base_permissions):
62            base_permissions_pkg = base_permissions[pkg_name]
63            priv_perms = remove_base_permissions(priv_perms, base_permissions_pkg)
64            if priv_perms:
65                apps_redefine_base.append(pkg_name)
66        if priv_perms:
67            results[pkg_name] = sorted(priv_perms)
68
69    print_xml(results, apps_redefine_base)
70
71def print_xml(results, apps_redefine_base):
72    """
73    Print results to xml file
74    """
75    print """\
76<?xml version="1.0" encoding="utf-8"?>
77<permissions>"""
78    for package_name in sorted(results):
79        if package_name in apps_redefine_base:
80            print '    <!-- Additional permissions on top of %s -->' % BASE_XML_FNAME
81        print '    <privapp-permissions package="%s">' % package_name
82        for p in results[package_name]:
83            print '        <permission name="%s"/>' % p
84        print '    </privapp-permissions>'
85        print
86
87    print "</permissions>"
88
89def remove_base_permissions(priv_perms, base_perms):
90    """
91    Removes set of base_perms from set of priv_perms
92    """
93    if (not priv_perms) or (not base_perms): return priv_perms
94    return set(priv_perms) - set(base_perms)
95
96def get_priv_permissions(requested_perms, priv_perms):
97    """
98    Return only permissions that are in priv_perms set
99    """
100    return set(requested_perms).intersection(set(priv_perms))
101
102def list_privapps():
103    """
104    Extract package name and requested permissions.
105    """
106    priv_app_dir = os.path.join(ANDROID_PRODUCT_OUT, 'system/priv-app')
107    apks = []
108    for dirName, subdirList, fileList in os.walk(priv_app_dir):
109        for fname in fileList:
110            if fname.endswith(".apk"):
111                file_path = os.path.join(dirName, fname)
112                apks.append(file_path)
113
114    return apks
115
116def list_config_xml_files():
117    """
118    Extract package name and requested permissions.
119    """
120    perm_dir = os.path.join(ANDROID_PRODUCT_OUT, 'system/etc/permissions')
121    conf_dir = os.path.join(ANDROID_PRODUCT_OUT, 'system/etc/sysconfig')
122
123    xml_files = []
124    for root_dir in [perm_dir, conf_dir]:
125        for dirName, subdirList, fileList in os.walk(root_dir):
126            for fname in fileList:
127                if fname.endswith(".xml"):
128                    file_path = os.path.join(dirName, fname);
129                    xml_files.append(file_path)
130    return xml_files
131
132
133def extract_pkg_and_requested_permissions(apk_path):
134    """
135    Extract package name and list of requested permissions from the
136    dump of manifest file
137    """
138    aapt_args = ["d", "permissions", apk_path]
139    txt = aapt(aapt_args)
140
141    permissions = []
142    package_name = None
143    rawLines = txt.split('\n')
144    for line in rawLines:
145        regex = r"uses-permission: name='([\S]+)'"
146        matches = re.search(regex, line)
147        if matches:
148            name = matches.group(1)
149            permissions.append(name)
150        regex = r"package: ([\S]+)"
151        matches = re.search(regex, line)
152        if matches:
153            package_name = matches.group(1)
154
155    return {'package_name': package_name, 'permissions' : permissions}
156
157def extract_priv_permissions(apk_path):
158    """
159    Extract list signature|privileged permissions from the dump of
160    manifest file
161    """
162    aapt_args = ["d", "xmltree", apk_path, "AndroidManifest.xml"]
163    txt = aapt(aapt_args)
164    rawLines = txt.split('\n')
165    n = len(rawLines)
166    i = 0
167    permissions_list = []
168    while i<n:
169        line = rawLines[i]
170        if line.find("E: permission (") != -1:
171            i+=1
172            name = None
173            level = None
174            while i<n:
175                line = rawLines[i];
176                if line.find("E: ") != -1:
177                    break
178                regex = r'A: android:name\([\S]+\)=\"([\S]+)\"';
179                matches = re.search(regex, line);
180                if matches:
181                    name = matches.group(1)
182                    i+=1
183                    continue
184                regex = r'A: android:protectionLevel\([^\)]+\)=\(type [\S]+\)0x([\S]+)';
185                matches = re.search(regex, line);
186                if matches:
187                    level = int(matches.group(1), 16)
188                    i+=1
189                    continue
190                i+=1
191            if name and level and level & 0x12 == 0x12:
192                    permissions_list.append(name)
193        else:
194            i+=1
195
196    return permissions_list
197
198def parse_config_xml(base_xml, results):
199    """
200    Parse an XML file that will be used as base.
201    """
202    dom = minidom.parse(base_xml)
203    nodes =  dom.getElementsByTagName("privapp-permissions")
204    for node in nodes:
205        permissions = node.getElementsByTagName("permission")
206        package_name = node.getAttribute('package');
207        plist = []
208        if package_name in results:
209            plist = results[package_name]
210        for p in permissions:
211            perm_name = p.getAttribute('name')
212            if perm_name:
213                plist.append(perm_name)
214        results[package_name] = plist
215    return results
216
217def aapt(args):
218    """
219    Run aapt command
220    """
221    return subprocess.check_output([ANDROID_HOST_OUT + '/bin/aapt'] + args,
222        stderr=subprocess.STDOUT)
223
224if __name__ == '__main__':
225    main()
226