verify-permissions.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. #!/usr/bin/python3
  2. #
  3. # Copyright (C) 2021 Paul Keith <[email protected]>
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License version 2 and
  7. # only version 2 as published by the Free Software Foundation.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. import errno
  14. from glob import glob
  15. import os
  16. import subprocess
  17. import sys
  18. from xml.etree import ElementTree
  19. # Get external packages
  20. try:
  21. from parse import parse
  22. except ImportError:
  23. print('Please install the "parse" package via pip3.')
  24. exit(errno.ENOPKG)
  25. try:
  26. import requests
  27. except ImportError:
  28. print('Please install the "requests" package via pip3.')
  29. exit(errno.ENOPKG)
  30. # Change working directory to the location of this script
  31. # This fixes relative path references when calling this script from
  32. # outside of the directory containing it
  33. os.chdir(sys.path[0])
  34. # Definitions for privileged permissions
  35. ANDROID_MANIFEST_XML = \
  36. 'https://raw.githubusercontent.com/LineageOS/android_frameworks_base/lineage-22.0/core/res/AndroidManifest.xml'
  37. ANDROID_XML_NS = '{http://schemas.android.com/apk/res/android}'
  38. privileged_permissions = set()
  39. privileged_permission_mask = {'privileged', 'signature'}
  40. # Get AndroidManifest.xml
  41. req = requests.get(ANDROID_MANIFEST_XML)
  42. # Parse AndroidManifest.xml to get signature|privileged permissions
  43. root = ElementTree.fromstring(req.text)
  44. for perm in root.findall('permission'):
  45. # Get name of permission
  46. name = perm.get(f'{ANDROID_XML_NS}name')
  47. # Get the protection levels on the permission
  48. levels = set(
  49. perm.get(f'{ANDROID_XML_NS}protectionLevel').split('|'))
  50. # Check if the protections include signature and privileged
  51. levels_masked = levels & privileged_permission_mask
  52. if len(levels_masked) == len(privileged_permission_mask):
  53. privileged_permissions.add(name)
  54. # List of partitions to check priv-app permissions on
  55. partitions = ['product', 'system_ext']
  56. # Definitions for privapp-permissions
  57. # Dictionary with structure:
  58. # partition: permissions_dictionary
  59. # Where permissions_dictionary has the structure:
  60. # package_name : (set(allowed_permissions), set(requested_permissions))
  61. privapp_permissions_dict = {x: {} for x in partitions}
  62. # Definitions for privapp-permission allowlists
  63. GLOB_XML_STR = '../*/proprietary/{}/etc/permissions/privapp-permissions*.xml'
  64. # Parse allowlists to extract allowed privileged permissions
  65. for partition in partitions:
  66. # Get pointer to permissions_dictionary for the partition
  67. perm_dict = privapp_permissions_dict[partition]
  68. # Loop over all the XMLs in the partition we want
  69. for allowlist in glob(GLOB_XML_STR.format(partition)):
  70. # Get root of XML
  71. tree = ElementTree.parse(allowlist)
  72. root = tree.getroot()
  73. # Loop through and find packages
  74. for package in root.findall('privapp-permissions'):
  75. name = package.get('package')
  76. # Create empty entry if it's not in the dictionary
  77. if name not in perm_dict:
  78. perm_dict[name] = (set(), set())
  79. # Get all permissions and add them to dictionary
  80. for permission in package.findall('permission'):
  81. perm_dict[name][0].add(permission.get('name'))
  82. for permission in package.findall('deny-permission'):
  83. perm_dict[name][0].add(permission.get('name'))
  84. # Definitions for parsing APKs
  85. GLOB_APK_STR = '../*/proprietary/{}/priv-app/*/*.apk'
  86. AAPT_CMD = ['aapt', 'd', 'permissions']
  87. # Extract requested privileged permissions from all priv-app APKs
  88. for partition in partitions:
  89. # Get pointer to permissions_dictionary for the partition
  90. perm_dict = privapp_permissions_dict[partition]
  91. # Loop over all the APKs in the partition we want
  92. for apk in glob(GLOB_APK_STR.format(partition)):
  93. # Run 'aapt d permissions' on APK
  94. aapt_output = subprocess.check_output(AAPT_CMD + [apk]).decode(encoding='UTF-8')
  95. lines = aapt_output.splitlines()
  96. # Extract package name from the output
  97. # Output looks like:
  98. # package: my.package.name
  99. package_name = parse('package: {}', lines[0])[0]
  100. # Create empty entry if package is not in dict
  101. if package_name not in perm_dict:
  102. perm_dict[package_name] = (set(), set())
  103. # Extract 'uses-permission' lines from the rest of the output
  104. # Relevant output looks like:
  105. # uses-permission: name='permission'
  106. for line in lines[1:]:
  107. # Extract permission name and add it to the dictionary if it's
  108. # one of the privileged permissions we extracted earlier
  109. if perm_name := parse('uses-permission: name=\'{}\'', line):
  110. if perm_name[0] in privileged_permissions:
  111. perm_dict[package_name][1].add(perm_name[0])
  112. # Keep track of exit code
  113. rc = 0
  114. # Find all the missing permissions
  115. for partition in partitions:
  116. # Get pointer to permissions_dictionary for the partition
  117. perm_dict = privapp_permissions_dict[partition]
  118. # Loop through all the packages and compare permission sets
  119. for package in perm_dict:
  120. # Get the sets of permissions
  121. # Format is (allowed, requested)
  122. perm_sets = perm_dict[package]
  123. # Compute the set difference requested - allowed
  124. # This gives us all the permissions requested that were not allowed
  125. perm_diff = perm_sets[1] - perm_sets[0]
  126. # If any permissions are left, set exit code to EPERM and print output
  127. if len(perm_diff) > 0:
  128. rc = errno.EPERM
  129. sys.stderr.write(
  130. f"Package {package} on partition {partition} is missing these permissions:\n")
  131. for perm in perm_diff:
  132. sys.stderr.write(f" - {perm}\n")
  133. # Exit program
  134. exit(rc)