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_blocklist.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', 'riscv64']
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      'riscv64': {
98          'uapi_class': 'asm-riscv64',
99          'extra_cflags': [],
100      },
101  }
102
103  # Run preprocessor over the __NR_syscall symbols, including unistd.h,
104  # to get the actual numbers
105  # TODO: The following code is forked from bionic/libc/tools/genseccomp.py.
106  # Figure out if we can de-duplicate them crossing cts project boundary.
107  prefix = '__SECCOMP_'  # prefix to ensure no name collisions
108  kernel_uapi_path = os.path.join(os.getenv('ANDROID_BUILD_TOP'),
109                                  'bionic/libc/kernel/uapi')
110  cpp = subprocess.Popen(
111      [get_latest_clang_path(),
112       '-E', '-nostdinc',
113       '-I' + os.path.join(kernel_uapi_path,
114                           arch_config[arch]['uapi_class']),
115       '-I' + os.path.join(kernel_uapi_path)
116       ]
117      + arch_config[arch]['extra_cflags']
118      + ['-'],
119      universal_newlines=True,
120      stdin=subprocess.PIPE, stdout=subprocess.PIPE)
121  cpp.stdin.write('#include <asm/unistd.h>\n')
122  for name in names:
123    # In SYSCALLS.TXT, there are two arm-specific syscalls whose names start
124    # with __ARM__NR_. These we must simply write out as is.
125    if not name.startswith('__ARM_NR_'):
126      cpp.stdin.write(prefix + name + ', __NR_' + name + '\n')
127    else:
128      cpp.stdin.write(prefix + name + ', ' + name + '\n')
129  content = cpp.communicate()[0].split('\n')
130
131  # The input is now the preprocessed source file. This will contain a lot
132  # of junk from the preprocessor, but our lines will be in the format:
133  #
134  #     __SECCOMP_${NAME}, (0 + value)
135  syscalls = {}
136  for line in content:
137    if not line.startswith(prefix):
138      continue
139    # We might pick up extra whitespace during preprocessing, so best to strip.
140    name, value = [w.strip() for w in line.split(',')]
141    name = name[len(prefix):]
142    # Note that some of the numbers were expressed as base + offset, so we
143    # need to eval, not just int
144    value = eval(value)
145    if name in syscalls:
146      raise Exception('syscall %s is re-defined' % name)
147    syscalls[name] = value
148  return syscalls
149
150def get_latest_clang_path():
151  candidates = sorted(glob.glob(os.path.join(os.getenv('ANDROID_BUILD_TOP'),
152      'prebuilts/clang/host/linux-x86/clang-*')), reverse=True)
153  for clang_dir in candidates:
154    clang_exe = os.path.join(clang_dir, 'bin/clang')
155    if os.path.exists(clang_exe):
156      return clang_exe
157  raise FileNotFoundError('Cannot locate clang executable')
158
159def collect_syscall_names_for_arch(syscall_map, arch):
160  syscall_names = []
161  for syscall in syscall_map.keys():
162    if (arch in syscall_map[syscall] or
163        'all' == syscall_map[syscall]):
164      syscall_names.append(syscall)
165  return syscall_names
166
167def main():
168  parser = argparse.ArgumentParser('syscall name to number generator')
169  parser.add_argument('--allowed', metavar='path/to/json', type=str)
170  parser.add_argument('--blocked', metavar='path/to/json', type=str)
171  args = parser.parse_args()
172
173  allowed = {}
174  blocked = {}
175  for arch in _SUPPORTED_ARCHS:
176    blocked[arch] = create_syscall_name_to_number_map(
177        arch,
178        collect_syscall_names_for_arch(_SYSCALLS_BLOCKED_IN_CTS, arch))
179    allowed[arch] = create_syscall_name_to_number_map(
180        arch,
181        collect_syscall_names_for_arch(_SYSCALLS_ALLOWED_IN_CTS, arch))
182
183  msg_do_not_modify = '# DO NOT MODIFY.  CHANGE gen_blocklist.py INSTEAD.'
184  with open(args.allowed, 'w') as f:
185    print(msg_do_not_modify, file=f)
186    json.dump(allowed, f, sort_keys=True, indent=2)
187
188  with open(args.blocked, 'w') as f:
189    print(msg_do_not_modify, file=f)
190    json.dump(blocked, f, sort_keys=True, indent=2)
191
192if __name__ == '__main__':
193  main()
194