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