1#!/usr/bin/env python3
2#
3# This script generates syscall name to number mapping for supported
4# architectures.  To update the output, runs:
5#
6#  $ app/gen_blacklist.py --allowed app/assets/syscalls_allowed.json \
7#      --blocked app/assets/syscalls_blocked.json
8#
9# Note that these are just syscalls that explicitly allowed and blocked in CTS
10# currently.
11#
12# TODO: Consider generating it in Android.mk/bp.
13
14import argparse
15import glob
16import json
17import os
18import subprocess
19
20_SUPPORTED_ARCHS = ['arm', 'arm64', 'x86', 'x86_64', 'mips', 'mips64']
21
22# Syscalls that are currently explicitly allowed in CTS
23_SYSCALLS_ALLOWED_IN_CTS = {
24    'openat': 'all',
25
26    # b/35034743 - do not remove test without reading bug.
27    'syncfs': 'arm64',
28
29    # b/35906875 - do not remove test without reading bug
30    'inotify_init': 'arm',
31}
32
33# Syscalls that are currently explicitly blocked in CTS
34_SYSCALLS_BLOCKED_IN_CTS = {
35    'acct': 'all',
36    'add_key': 'all',
37    'adjtimex': 'all',
38    'chroot': 'all',
39    'clock_adjtime': 'all',
40    'clock_settime': 'all',
41    'delete_module': 'all',
42    'init_module': 'all',
43    'keyctl': 'all',
44    'mount': 'all',
45    'reboot': 'all',
46    'setdomainname': 'all',
47    'sethostname': 'all',
48    'settimeofday': 'all',
49    'setfsgid': 'all',
50    'setfsuid': 'all',
51    'setgid': 'all',
52    'setgid32': 'x86,arm',
53    'setgroups': 'all',
54    'setgroups32': 'x86,arm',
55    'setregid': 'all',
56    'setregid32': 'x86,arm',
57    'setresgid': 'all',
58    'setresgid32': 'x86,arm',
59    'setreuid': 'all',
60    'setreuid32': 'x86,arm',
61    'setuid': 'all',
62    'setuid32': 'x86,arm',
63    'swapoff': 'all',
64    'swapoff': 'all',
65    'swapon': 'all',
66    'swapon': 'all',
67    'syslog': 'all',
68    'umount2': 'all',
69}
70
71def create_syscall_name_to_number_map(arch, names):
72  arch_config = {
73      'arm': {
74          'uapi_class': 'asm-arm',
75          'extra_cflags': [],
76      },
77      'arm64': {
78          'uapi_class': 'asm-arm64',
79          'extra_cflags': [],
80      },
81      'x86': {
82          'uapi_class': 'asm-x86',
83          'extra_cflags': ['-D__i386__'],
84      },
85      'x86_64': {
86          'uapi_class': 'asm-x86',
87          'extra_cflags': [],
88      },
89      'mips': {
90          'uapi_class': 'asm-mips',
91          'extra_cflags': ['-D_MIPS_SIM=_MIPS_SIM_ABI32'],
92      },
93      'mips64': {
94          'uapi_class': 'asm-mips',
95          'extra_cflags': ['-D_MIPS_SIM=_MIPS_SIM_ABI64'],
96      }
97  }
98
99  # Run preprocessor over the __NR_syscall symbols, including unistd.h,
100  # to get the actual numbers
101  # TODO: The following code is forked from bionic/libc/tools/genseccomp.py.
102  # Figure out if we can de-duplicate them crossing cts project boundary.
103  prefix = '__SECCOMP_'  # prefix to ensure no name collisions
104  kernel_uapi_path = os.path.join(os.getenv('ANDROID_BUILD_TOP'),
105                                  'bionic/libc/kernel/uapi')
106  cpp = subprocess.Popen(
107      [get_latest_clang_path(),
108       '-E', '-nostdinc',
109       '-I' + os.path.join(kernel_uapi_path,
110                           arch_config[arch]['uapi_class']),
111       '-I' + os.path.join(kernel_uapi_path)
112       ]
113      + arch_config[arch]['extra_cflags']
114      + ['-'],
115      universal_newlines=True,
116      stdin=subprocess.PIPE, stdout=subprocess.PIPE)
117  cpp.stdin.write('#include <asm/unistd.h>\n')
118  for name in names:
119    # In SYSCALLS.TXT, there are two arm-specific syscalls whose names start
120    # with __ARM__NR_. These we must simply write out as is.
121    if not name.startswith('__ARM_NR_'):
122      cpp.stdin.write(prefix + name + ', __NR_' + name + '\n')
123    else:
124      cpp.stdin.write(prefix + name + ', ' + name + '\n')
125  content = cpp.communicate()[0].split('\n')
126
127  # The input is now the preprocessed source file. This will contain a lot
128  # of junk from the preprocessor, but our lines will be in the format:
129  #
130  #     __SECCOMP_${NAME}, (0 + value)
131  syscalls = {}
132  for line in content:
133    if not line.startswith(prefix):
134      continue
135    # We might pick up extra whitespace during preprocessing, so best to strip.
136    name, value = [w.strip() for w in line.split(',')]
137    name = name[len(prefix):]
138    # Note that some of the numbers were expressed as base + offset, so we
139    # need to eval, not just int
140    value = eval(value)
141    if name in syscalls:
142      raise Exception('syscall %s is re-defined' % name)
143    syscalls[name] = value
144  return syscalls
145
146def get_latest_clang_path():
147  candidates = sorted(glob.glob(os.path.join(os.getenv('ANDROID_BUILD_TOP'),
148      'prebuilts/clang/host/linux-x86/clang-*')), reverse=True)
149  for clang_dir in candidates:
150    clang_exe = os.path.join(clang_dir, 'bin/clang')
151    if os.path.exists(clang_exe):
152      return clang_exe
153  raise FileNotFoundError('Cannot locate clang executable')
154
155def collect_syscall_names_for_arch(syscall_map, arch):
156  syscall_names = []
157  for syscall in syscall_map.keys():
158    if (arch in syscall_map[syscall] or
159        'all' == syscall_map[syscall]):
160      syscall_names.append(syscall)
161  return syscall_names
162
163def main():
164  parser = argparse.ArgumentParser('syscall name to number generator')
165  parser.add_argument('--allowed', metavar='path/to/json', type=str)
166  parser.add_argument('--blocked', metavar='path/to/json', type=str)
167  args = parser.parse_args()
168
169  allowed = {}
170  blocked = {}
171  for arch in _SUPPORTED_ARCHS:
172    blocked[arch] = create_syscall_name_to_number_map(
173        arch,
174        collect_syscall_names_for_arch(_SYSCALLS_BLOCKED_IN_CTS, arch))
175    allowed[arch] = create_syscall_name_to_number_map(
176        arch,
177        collect_syscall_names_for_arch(_SYSCALLS_ALLOWED_IN_CTS, arch))
178
179  msg_do_not_modify = '# DO NOT MODIFY.  CHANGE gen_blacklist.py INSTEAD.'
180  with open(args.allowed, 'w') as f:
181    print(msg_do_not_modify, file=f)
182    json.dump(allowed, f, sort_keys=True, indent=2)
183
184  with open(args.blocked, 'w') as f:
185    print(msg_do_not_modify, file=f)
186    json.dump(blocked, f, sort_keys=True, indent=2)
187
188if __name__ == '__main__':
189  main()
190