blob: bb887d66e8d907f3a2dbabade20861dc6fbb686f [file] [log] [blame]
Paul Lawrenceeabc3522016-11-11 11:33:42 -08001#!/usr/bin/env python
Paul Lawrence89fa81f2017-02-17 10:22:03 -08002import collections
Paul Lawrenceeabc3522016-11-11 11:33:42 -08003import os
Luis Hector Chavezfd3f6d72018-08-03 10:38:41 -07004import re
Paul Lawrenceeabc3522016-11-11 11:33:42 -08005import textwrap
6from gensyscalls import SysCallsTxtParser
Paul Lawrence89fa81f2017-02-17 10:22:03 -08007from subprocess import Popen, PIPE
Paul Lawrenceeabc3522016-11-11 11:33:42 -08008
9
Paul Lawrence7ea40902017-02-14 13:32:23 -080010BPF_JGE = "BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, {0}, {1}, {2})"
11BPF_ALLOW = "BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW)"
Paul Lawrenceeabc3522016-11-11 11:33:42 -080012
13
14class SyscallRange(object):
15 def __init__(self, name, value):
16 self.names = [name]
17 self.begin = value
18 self.end = self.begin + 1
19
Paul Lawrence7ea40902017-02-14 13:32:23 -080020 def __str__(self):
21 return "(%s, %s, %s)" % (self.begin, self.end, self.names)
22
Paul Lawrenceeabc3522016-11-11 11:33:42 -080023 def add(self, name, value):
24 if value != self.end:
25 raise ValueError
26 self.end += 1
27 self.names.append(name)
28
29
Victor Hsieh4f02dd52017-12-20 09:19:22 -080030def load_syscall_names_from_file(file_path, architecture):
31 parser = SysCallsTxtParser()
32 parser.parse_open_file(open(file_path))
33 return set([x["name"] for x in parser.syscalls if x.get(architecture)])
Paul Lawrence3dd3d552017-04-12 10:02:54 -070034
Steve Muckleaa3f96c2017-07-20 13:11:54 -070035
Victor Hsieh4f02dd52017-12-20 09:19:22 -080036def merge_names(base_names, whitelist_names, blacklist_names):
37 if bool(blacklist_names - base_names):
38 raise RuntimeError("Blacklist item not in bionic - aborting " + str(
39 blacklist_name - base_names))
Paul Lawrence3dd3d552017-04-12 10:02:54 -070040
Victor Hsieh4f02dd52017-12-20 09:19:22 -080041 return (base_names - blacklist_names) | whitelist_names
Paul Lawrenceeabc3522016-11-11 11:33:42 -080042
Paul Lawrence7ea40902017-02-14 13:32:23 -080043
Luis Hector Chavezfd3f6d72018-08-03 10:38:41 -070044def get_clang_path():
45 # Inspect the global soong config to figure out the default version of clang.
46 global_go_path = os.path.join(os.environ["ANDROID_BUILD_TOP"],
47 "build/soong/cc/config/global.go")
48 clang_default_version = None
49 CLANG_DEFAULT_VERSION_RE = re.compile(
50 r'^\s*ClangDefaultVersion\s*=\s*"([^"]+)"\s*$')
51 with open(global_go_path) as f:
52 for line in f:
53 m = CLANG_DEFAULT_VERSION_RE.match(line)
54 if not m:
55 continue
56 clang_default_version = m.group(1)
57 break
58 else:
59 raise Exception('Could not find ClangDefaultVersion in %s' %
60 global_go_path)
61
62 # Gets the path of the clang prebuilt binary.
63 return os.path.join(os.environ["ANDROID_BUILD_TOP"],
64 "prebuilts/clang/host/linux-x86", clang_default_version,
65 "bin/clang")
66
67
Paul Lawrence89fa81f2017-02-17 10:22:03 -080068def convert_names_to_NRs(names, header_dir, extra_switches):
Paul Lawrenceeabc3522016-11-11 11:33:42 -080069 # Run preprocessor over the __NR_syscall symbols, including unistd.h,
70 # to get the actual numbers
71 prefix = "__SECCOMP_" # prefix to ensure no name collisions
Luis Hector Chavezfd3f6d72018-08-03 10:38:41 -070072 cpp = Popen([get_clang_path(),
Paul Lawrence89fa81f2017-02-17 10:22:03 -080073 "-E", "-nostdinc", "-I" + header_dir, "-Ikernel/uapi/"]
74 + extra_switches
75 + ["-"],
Paul Lawrenceeabc3522016-11-11 11:33:42 -080076 stdin=PIPE, stdout=PIPE)
77 cpp.stdin.write("#include <asm/unistd.h>\n")
78 for name in names:
79 # In SYSCALLS.TXT, there are two arm-specific syscalls whose names start
80 # with __ARM__NR_. These we must simply write out as is.
81 if not name.startswith("__ARM_NR_"):
82 cpp.stdin.write(prefix + name + ", __NR_" + name + "\n")
83 else:
84 cpp.stdin.write(prefix + name + ", " + name + "\n")
85 content = cpp.communicate()[0].split("\n")
86
87 # The input is now the preprocessed source file. This will contain a lot
88 # of junk from the preprocessor, but our lines will be in the format:
89 #
90 # __SECCOMP_${NAME}, (0 + value)
91
92 syscalls = []
93 for line in content:
94 if not line.startswith(prefix):
95 continue
96
97 # We might pick up extra whitespace during preprocessing, so best to strip.
98 name, value = [w.strip() for w in line.split(",")]
99 name = name[len(prefix):]
100
101 # Note that some of the numbers were expressed as base + offset, so we
102 # need to eval, not just int
103 value = eval(value)
104 syscalls.append((name, value))
105
Paul Lawrence7ea40902017-02-14 13:32:23 -0800106 return syscalls
107
108
109def convert_NRs_to_ranges(syscalls):
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800110 # Sort the values so we convert to ranges and binary chop
111 syscalls = sorted(syscalls, lambda x, y: cmp(x[1], y[1]))
112
113 # Turn into a list of ranges. Keep the names for the comments
114 ranges = []
115 for name, value in syscalls:
116 if not ranges:
117 ranges.append(SyscallRange(name, value))
118 continue
119
120 last_range = ranges[-1]
121 if last_range.end == value:
122 last_range.add(name, value)
123 else:
124 ranges.append(SyscallRange(name, value))
Paul Lawrence7ea40902017-02-14 13:32:23 -0800125 return ranges
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800126
Paul Lawrence7ea40902017-02-14 13:32:23 -0800127
128# Converts the sorted ranges of allowed syscalls to a binary tree bpf
129# For a single range, output a simple jump to {fail} or {allow}. We can't set
130# the jump ranges yet, since we don't know the size of the filter, so use a
131# placeholder
132# For multiple ranges, split into two, convert the two halves and output a jump
133# to the correct half
134def convert_to_intermediate_bpf(ranges):
135 if len(ranges) == 1:
136 # We will replace {fail} and {allow} with appropriate range jumps later
137 return [BPF_JGE.format(ranges[0].end, "{fail}", "{allow}") +
138 ", //" + "|".join(ranges[0].names)]
139 else:
140 half = (len(ranges) + 1) / 2
141 first = convert_to_intermediate_bpf(ranges[:half])
142 second = convert_to_intermediate_bpf(ranges[half:])
143 jump = [BPF_JGE.format(ranges[half].begin, len(first), 0) + ","]
144 return jump + first + second
145
146
147def convert_ranges_to_bpf(ranges):
148 bpf = convert_to_intermediate_bpf(ranges)
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800149
150 # Now we know the size of the tree, we can substitute the {fail} and {allow}
151 # placeholders
152 for i, statement in enumerate(bpf):
153 # Replace placeholder with
154 # "distance to jump to fail, distance to jump to allow"
155 # We will add a kill statement and an allow statement after the tree
156 # With bpfs jmp 0 means the next statement, so the distance to the end is
157 # len(bpf) - i - 1, which is where we will put the kill statement, and
158 # then the statement after that is the allow statement
159 if "{fail}" in statement and "{allow}" in statement:
Paul Lawrencebe8a2af2017-01-25 15:20:52 -0800160 bpf[i] = statement.format(fail=str(len(bpf) - i),
161 allow=str(len(bpf) - i - 1))
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800162
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800163
Paul Lawrencebe8a2af2017-01-25 15:20:52 -0800164 # Add the allow calls at the end. If the syscall is not matched, we will
165 # continue. This allows the user to choose to match further syscalls, and
166 # also to choose the action when we want to block
Paul Lawrence7ea40902017-02-14 13:32:23 -0800167 bpf.append(BPF_ALLOW + ",")
Paul Lawrence65b47c92017-03-22 08:03:51 -0700168
169 # Add check that we aren't off the bottom of the syscalls
170 bpf.insert(0, BPF_JGE.format(ranges[0].begin, 0, str(len(bpf))) + ',')
Paul Lawrence7ea40902017-02-14 13:32:23 -0800171 return bpf
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800172
Paul Lawrence7ea40902017-02-14 13:32:23 -0800173
Victor Hsieh4f02dd52017-12-20 09:19:22 -0800174def convert_bpf_to_output(bpf, architecture, name_modifier):
175 if name_modifier:
176 name_modifier = name_modifier + "_"
177 else:
178 name_modifier = ""
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800179 header = textwrap.dedent("""\
180 // Autogenerated file - edit at your peril!!
181
182 #include <linux/filter.h>
183 #include <errno.h>
184
Paul Lawrencedfe84342017-02-16 09:24:39 -0800185 #include "seccomp_bpfs.h"
Steve Muckleaa3f96c2017-07-20 13:11:54 -0700186 const sock_filter {architecture}_{suffix}filter[] = {{
Victor Hsieh4f02dd52017-12-20 09:19:22 -0800187 """).format(architecture=architecture,suffix=name_modifier)
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800188
189 footer = textwrap.dedent("""\
190
191 }};
192
Steve Muckleaa3f96c2017-07-20 13:11:54 -0700193 const size_t {architecture}_{suffix}filter_size = sizeof({architecture}_{suffix}filter) / sizeof(struct sock_filter);
Victor Hsieh4f02dd52017-12-20 09:19:22 -0800194 """).format(architecture=architecture,suffix=name_modifier)
Paul Lawrence7ea40902017-02-14 13:32:23 -0800195 return header + "\n".join(bpf) + footer
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800196
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800197
Victor Hsieh4f02dd52017-12-20 09:19:22 -0800198def construct_bpf(names, architecture, header_dir, extra_switches,
199 name_modifier):
Paul Lawrence89fa81f2017-02-17 10:22:03 -0800200 syscalls = convert_names_to_NRs(names, header_dir, extra_switches)
Paul Lawrence7ea40902017-02-14 13:32:23 -0800201 ranges = convert_NRs_to_ranges(syscalls)
202 bpf = convert_ranges_to_bpf(ranges)
Victor Hsieh4f02dd52017-12-20 09:19:22 -0800203 return convert_bpf_to_output(bpf, architecture, name_modifier)
Paul Lawrence7ea40902017-02-14 13:32:23 -0800204
205
Victor Hsieh4f02dd52017-12-20 09:19:22 -0800206# final syscalls = base - blacklists + whitelists
207ANDROID_SYSTEM_SYSCALL_FILES = {
208 "base": "SYSCALLS.TXT",
209 "whitelists": [
210 "SECCOMP_WHITELIST_COMMON.TXT",
211 "SECCOMP_WHITELIST_SYSTEM.TXT"],
212 "blacklists": ["SECCOMP_BLACKLIST_COMMON.TXT"]
213}
214
215ANDROID_APP_SYSCALL_FILES = {
216 "base": "SYSCALLS.TXT",
217 "whitelists": [
218 "SECCOMP_WHITELIST_COMMON.TXT",
219 "SECCOMP_WHITELIST_APP.TXT"],
Victor Hsieh2f23ced2018-01-17 16:59:12 -0800220 "blacklists": [
221 "SECCOMP_BLACKLIST_COMMON.TXT",
222 "SECCOMP_BLACKLIST_APP.TXT"]
Victor Hsieh4f02dd52017-12-20 09:19:22 -0800223}
224
225ANDROID_GLOBAL_SYSCALL_FILES = {
226 "base": "SYSCALLS.TXT",
227 "whitelists": [
228 "SECCOMP_WHITELIST_COMMON.TXT",
229 "SECCOMP_WHITELIST_SYSTEM.TXT",
230 "SECCOMP_WHITELIST_APP.TXT",
231 "SECCOMP_WHITELIST_GLOBAL.TXT"],
232 "blacklists": ["SECCOMP_BLACKLIST_COMMON.TXT"]
233}
Paul Lawrence7ea40902017-02-14 13:32:23 -0800234
Paul Lawrence89fa81f2017-02-17 10:22:03 -0800235
236POLICY_CONFIGS = [("arm", "kernel/uapi/asm-arm", []),
237 ("arm64", "kernel/uapi/asm-arm64", []),
238 ("x86", "kernel/uapi/asm-x86", ["-D__i386__"]),
239 ("x86_64", "kernel/uapi/asm-x86", []),
240 ("mips", "kernel/uapi/asm-mips", ["-D_MIPS_SIM=_MIPS_SIM_ABI32"]),
241 ("mips64", "kernel/uapi/asm-mips", ["-D_MIPS_SIM=_MIPS_SIM_ABI64"])]
Paul Lawrence7ea40902017-02-14 13:32:23 -0800242
243
244def set_dir():
245 # Set working directory for predictable results
246 os.chdir(os.path.join(os.environ["ANDROID_BUILD_TOP"], "bionic/libc"))
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800247
248
Victor Hsieh4f02dd52017-12-20 09:19:22 -0800249def gen_policy(syscall_files, name_modifier):
Paul Lawrence89fa81f2017-02-17 10:22:03 -0800250 for arch, header_path, switches in POLICY_CONFIGS:
Victor Hsieh4f02dd52017-12-20 09:19:22 -0800251 base_names = load_syscall_names_from_file(syscall_files["base"], arch)
252 whitelist_names = set()
253 for f in syscall_files["whitelists"]:
254 whitelist_names |= load_syscall_names_from_file(f, arch)
255 blacklist_names = set()
256 for f in syscall_files["blacklists"]:
257 blacklist_names |= load_syscall_names_from_file(f, arch)
258
259 names = merge_names(base_names, whitelist_names, blacklist_names)
260 output = construct_bpf(names, arch, header_path, switches, name_modifier)
Paul Lawrence7ea40902017-02-14 13:32:23 -0800261
262 # And output policy
263 existing = ""
Victor Hsieh4f02dd52017-12-20 09:19:22 -0800264 filename_modifier = "_" + name_modifier if name_modifier else ""
265 output_path = "seccomp/{}{}_policy.cpp".format(arch, filename_modifier)
Paul Lawrence7ea40902017-02-14 13:32:23 -0800266 if os.path.isfile(output_path):
267 existing = open(output_path).read()
268 if output == existing:
269 print "File " + output_path + " not changed."
270 else:
271 with open(output_path, "w") as output_file:
272 output_file.write(output)
273 print "Generated file " + output_path
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800274
Steve Muckleaa3f96c2017-07-20 13:11:54 -0700275
276def main():
277 set_dir()
Victor Hsieh4f02dd52017-12-20 09:19:22 -0800278 gen_policy(ANDROID_SYSTEM_SYSCALL_FILES, 'system')
279 gen_policy(ANDROID_APP_SYSCALL_FILES, 'app')
280 gen_policy(ANDROID_GLOBAL_SYSCALL_FILES, 'global')
281
Steve Muckleaa3f96c2017-07-20 13:11:54 -0700282
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800283if __name__ == "__main__":
284 main()