scanner: add a simple scan for blacklisted classes after build step

add com.android.billing to blacklist, see
https://gitlab.com/fdroid/fdroiddata/-/issues/2070#note_360611289
This commit is contained in:
Marcus 2020-06-15 18:03:19 +00:00
parent 6986e73506
commit f84818c15e
7 changed files with 45 additions and 2 deletions

View File

@ -83,7 +83,7 @@ __complete_options() {
__complete_build() { __complete_build() {
opts="-v -q -l -s -t -f -a -w" opts="-v -q -l -s -t -f -a -w"
lopts="--verbose --quiet --latest --stop --test --server --reset-server --skip-scan --no-tarball --force --all --wiki --no-refresh" lopts="--verbose --quiet --latest --stop --test --server --reset-server --skip-scan --scan-binary --no-tarball --force --all --wiki --no-refresh"
case "${prev}" in case "${prev}" in
:) :)
__vercode __vercode

View File

@ -50,6 +50,9 @@
# Defaults to using an internal gradle wrapper (gradlew-fdroid). # Defaults to using an internal gradle wrapper (gradlew-fdroid).
# gradle = "gradle" # gradle = "gradle"
# Always scan the APKs produced by `fdroid build` for known non-free classes
# scan_binary = True
# Set the maximum age (in days) of an index that a client should accept from # Set the maximum age (in days) of an index that a client should accept from
# this repo. Setting it to 0 or not setting it at all disables this # this repo. Setting it to 0 or not setting it at all disables this
# functionality. If you do set this to a non-zero value, you need to ensure # functionality. If you do set this to a non-zero value, you need to ensure

View File

@ -806,6 +806,9 @@ def build_local(app, build, vcs, build_dir, output_dir, log_dir, srclib_dir, ext
" Expected: '%s' / '%s'") " Expected: '%s' / '%s'")
% (version, str(vercode), build.versionName, % (version, str(vercode), build.versionName,
str(build.versionCode))) str(build.versionCode)))
if (options.scan_binary or config.get('scan_binary')) and not options.skipscan:
if scanner.scan_binary(src):
raise BuildException("Found blacklisted packages in final apk!")
# Copy the unsigned apk to our destination directory for further # Copy the unsigned apk to our destination directory for further
# processing (by publish.py)... # processing (by publish.py)...
@ -899,6 +902,8 @@ def parse_commandline():
help=argparse.SUPPRESS) help=argparse.SUPPRESS)
parser.add_argument("--skip-scan", dest="skipscan", action="store_true", default=False, parser.add_argument("--skip-scan", dest="skipscan", action="store_true", default=False,
help=_("Skip scanning the source code for binaries and other problems")) help=_("Skip scanning the source code for binaries and other problems"))
parser.add_argument("--scan-binary", action="store_true", default=False,
help=_("Scan the resulting APK(s) for known non-free classes."))
parser.add_argument("--no-tarball", dest="notarball", action="store_true", default=False, parser.add_argument("--no-tarball", dest="notarball", action="store_true", default=False,
help=_("Don't create a source tarball, useful when testing a build")) help=_("Don't create a source tarball, useful when testing a build"))
parser.add_argument("--no-refresh", dest="refresh", action="store_false", default=True, parser.add_argument("--no-refresh", dest="refresh", action="store_false", default=True,

View File

@ -114,6 +114,7 @@ default_config = {
'build_tools': MINIMUM_AAPT_VERSION, 'build_tools': MINIMUM_AAPT_VERSION,
'force_build_tools': False, 'force_build_tools': False,
'java_paths': None, 'java_paths': None,
'scan_binary': False,
'ant': "ant", 'ant': "ant",
'mvn3': "mvn", 'mvn3': "mvn",
'gradle': os.path.join(FDROID_PATH, 'gradlew-fdroid'), 'gradle': os.path.join(FDROID_PATH, 'gradlew-fdroid'),
@ -432,6 +433,7 @@ def find_sdk_tools_cmd(cmd):
sdk_tools = os.path.join(config['sdk_path'], 'tools') sdk_tools = os.path.join(config['sdk_path'], 'tools')
if os.path.exists(sdk_tools): if os.path.exists(sdk_tools):
tooldirs.append(sdk_tools) tooldirs.append(sdk_tools)
tooldirs.append(os.path.join(sdk_tools, 'bin'))
sdk_platform_tools = os.path.join(config['sdk_path'], 'platform-tools') sdk_platform_tools = os.path.join(config['sdk_path'], 'platform-tools')
if os.path.exists(sdk_platform_tools): if os.path.exists(sdk_platform_tools):
tooldirs.append(sdk_platform_tools) tooldirs.append(sdk_platform_tools)

View File

@ -58,6 +58,36 @@ def get_gradle_compile_commands(build):
return [re.compile(r'\s*' + c, re.IGNORECASE) for c in commands] return [re.compile(r'\s*' + c, re.IGNORECASE) for c in commands]
def scan_binary(apkfile):
usual_suspects = {
# The `apkanalyzer dex packages` output looks like this:
# M d 1 1 93 <packagename> <other stuff>
# The first column has P/C/M/F for package, class, methos or field
# The second column has x/k/r/d for removed, kept, referenced and defined.
# We already filter for defined only in the apkanalyzer call. 'r' will be
# for things referenced but not distributed in the apk.
exp: re.compile(r'.[\s]*d[\s]*[0-9]*[\s]*[0-9*][\s]*[0-9]*[\s]*' + exp, re.IGNORECASE) for exp in [
r'(com\.google\.firebase[^\s]*)',
r'(com\.google\.android\.gms[^\s]*)',
r'(com\.google\.tagmanager[^\s]*)',
r'(com\.google\.analytics[^\s]*)',
r'(com\.android\.billing[^\s]*)',
]
}
logging.info("Scanning APK for known non-free classes.")
result = common.SdkToolsPopen(["apkanalyzer", "dex", "packages", "--defined-only", apkfile], output=False)
problems = 0
for suspect, regexp in usual_suspects.items():
matches = regexp.findall(result.output)
if matches:
for m in set(matches):
logging.debug("Found class '%s'" % m)
problems += 1
if problems:
logging.critical("Found problems in %s" % apkfile)
return problems
def scan_source(build_dir, build=metadata.Build()): def scan_source(build_dir, build=metadata.Build()):
"""Scan the source code in the given directory (and all subdirectories) """Scan the source code in the given directory (and all subdirectories)
and return the number of fatal problems encountered and return the number of fatal problems encountered
@ -308,7 +338,6 @@ def scan_source(build_dir, build=metadata.Build()):
def main(): def main():
global config, options, json_per_build global config, options, json_per_build
# Parse command line... # Parse command line...

View File

@ -131,7 +131,9 @@ class BuildTest(unittest.TestCase):
config = dict() config = dict()
fdroidserver.common.fill_config_defaults(config) fdroidserver.common.fill_config_defaults(config)
fdroidserver.common.config = config fdroidserver.common.config = config
fdroidserver.build.config = config
fdroidserver.build.options = mock.Mock() fdroidserver.build.options = mock.Mock()
fdroidserver.build.options.scan_binary = False
fdroidserver.build.options.notarball = True fdroidserver.build.options.notarball = True
fdroidserver.build.options.skipscan = False fdroidserver.build.options.skipscan = False

View File

@ -194,8 +194,10 @@ class ScannerTest(unittest.TestCase):
config = dict() config = dict()
fdroidserver.common.fill_config_defaults(config) fdroidserver.common.fill_config_defaults(config)
fdroidserver.common.config = config fdroidserver.common.config = config
fdroidserver.build.config = config
fdroidserver.build.options = mock.Mock() fdroidserver.build.options = mock.Mock()
fdroidserver.build.options.json = False fdroidserver.build.options.json = False
fdroidserver.build.options.scan_binary = False
fdroidserver.build.options.notarball = True fdroidserver.build.options.notarball = True
fdroidserver.build.options.skipscan = False fdroidserver.build.options.skipscan = False
fdroidserver.scanner.options = fdroidserver.build.options fdroidserver.scanner.options = fdroidserver.build.options