#!/usr/bin/env python # # Copyright 2016, The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # """ privapp_permission.py: Generates privapp-permissions.xml file for apps in system/priv-app directory Usage: . build/envsetup.sh lunch product_name m -j32 development/tools/privapp_permissions/privapp_permissions.py """ import os import re import subprocess from xml.dom import minidom try: ANDROID_PRODUCT_OUT = os.environ['ANDROID_PRODUCT_OUT'] ANDROID_HOST_OUT = os.environ['ANDROID_HOST_OUT'] except KeyError as e: exit("Build environment not set up - " + str(e)) BASE_XML_FNAME = "privapp-permissions-platform.xml" def main(): # Parse base XML files in /etc dir, permissions listed there don't have to be re-added base_permissions = {} for xml_file in list_config_xml_files(): parse_config_xml(xml_file, base_permissions) # Extract signature|privileged permissions available in the platform framework_apk = os.path.join(ANDROID_PRODUCT_OUT, 'system/framework/framework-res.apk') platform_priv_permissions = extract_priv_permissions(framework_apk) priv_apps = list_privapps() apps_redefine_base = [] results = {} for priv_app in priv_apps: pkg_info = extract_pkg_and_requested_permissions(priv_app) pkg_name = pkg_info['package_name'] priv_perms = get_priv_permissions(pkg_info['permissions'], platform_priv_permissions) # Compute diff against permissions defined in base file if base_permissions and (pkg_name in base_permissions): base_permissions_pkg = base_permissions[pkg_name] priv_perms = remove_base_permissions(priv_perms, base_permissions_pkg) if priv_perms: apps_redefine_base.append(pkg_name) if priv_perms: results[pkg_name] = sorted(priv_perms) print_xml(results, apps_redefine_base) def print_xml(results, apps_redefine_base): """ Print results to xml file """ print """\ <?xml version="1.0" encoding="utf-8"?> <permissions>""" for package_name in sorted(results): if package_name in apps_redefine_base: print ' <!-- Additional permissions on top of %s -->' % BASE_XML_FNAME print ' <privapp-permissions package="%s">' % package_name for p in results[package_name]: print ' <permission name="%s"/>' % p print ' </privapp-permissions>' print print "</permissions>" def remove_base_permissions(priv_perms, base_perms): """ Removes set of base_perms from set of priv_perms """ if (not priv_perms) or (not base_perms): return priv_perms return set(priv_perms) - set(base_perms) def get_priv_permissions(requested_perms, priv_perms): """ Return only permissions that are in priv_perms set """ return set(requested_perms).intersection(set(priv_perms)) def list_privapps(): """ Extract package name and requested permissions. """ priv_app_dir = os.path.join(ANDROID_PRODUCT_OUT, 'system/priv-app') apks = [] for dirName, subdirList, fileList in os.walk(priv_app_dir): for fname in fileList: if fname.endswith(".apk"): file_path = os.path.join(dirName, fname) apks.append(file_path) return apks def list_config_xml_files(): """ Extract package name and requested permissions. """ perm_dir = os.path.join(ANDROID_PRODUCT_OUT, 'system/etc/permissions') conf_dir = os.path.join(ANDROID_PRODUCT_OUT, 'system/etc/sysconfig') xml_files = [] for root_dir in [perm_dir, conf_dir]: for dirName, subdirList, fileList in os.walk(root_dir): for fname in fileList: if fname.endswith(".xml"): file_path = os.path.join(dirName, fname); xml_files.append(file_path) return xml_files def extract_pkg_and_requested_permissions(apk_path): """ Extract package name and list of requested permissions from the dump of manifest file """ aapt_args = ["d", "permissions", apk_path] txt = aapt(aapt_args) permissions = [] package_name = None rawLines = txt.split('\n') for line in rawLines: regex = r"uses-permission: name='([\S]+)'" matches = re.search(regex, line) if matches: name = matches.group(1) permissions.append(name) regex = r"package: ([\S]+)" matches = re.search(regex, line) if matches: package_name = matches.group(1) return {'package_name': package_name, 'permissions' : permissions} def extract_priv_permissions(apk_path): """ Extract list signature|privileged permissions from the dump of manifest file """ aapt_args = ["d", "xmltree", apk_path, "AndroidManifest.xml"] txt = aapt(aapt_args) rawLines = txt.split('\n') n = len(rawLines) i = 0 permissions_list = [] while i<n: line = rawLines[i] if line.find("E: permission (") != -1: i+=1 name = None level = None while i<n: line = rawLines[i]; if line.find("E: ") != -1: break regex = r'A: android:name\([\S]+\)=\"([\S]+)\"'; matches = re.search(regex, line); if matches: name = matches.group(1) i+=1 continue regex = r'A: android:protectionLevel\([^\)]+\)=\(type [\S]+\)0x([\S]+)'; matches = re.search(regex, line); if matches: level = int(matches.group(1), 16) i+=1 continue i+=1 if name and level and level & 0x12 == 0x12: permissions_list.append(name) else: i+=1 return permissions_list def parse_config_xml(base_xml, results): """ Parse an XML file that will be used as base. """ dom = minidom.parse(base_xml) nodes = dom.getElementsByTagName("privapp-permissions") for node in nodes: permissions = node.getElementsByTagName("permission") package_name = node.getAttribute('package'); plist = [] if package_name in results: plist = results[package_name] for p in permissions: perm_name = p.getAttribute('name') if perm_name: plist.append(perm_name) results[package_name] = plist return results def aapt(args): """ Run aapt command """ return subprocess.check_output([ANDROID_HOST_OUT + '/bin/aapt'] + args, stderr=subprocess.STDOUT) if __name__ == '__main__': main()