#!/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()