blob: f3a232e6e9841088292ea861037164752d74d03c [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
Paul Lawrenceeabc3522016-11-11 11:33:42 -08004import textwrap
5from gensyscalls import SysCallsTxtParser
Paul Lawrence89fa81f2017-02-17 10:22:03 -08006from subprocess import Popen, PIPE
Paul Lawrenceeabc3522016-11-11 11:33:42 -08007
8
Paul Lawrence7ea40902017-02-14 13:32:23 -08009BPF_JGE = "BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, {0}, {1}, {2})"
10BPF_ALLOW = "BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW)"
Paul Lawrenceeabc3522016-11-11 11:33:42 -080011
12
13class SyscallRange(object):
14 def __init__(self, name, value):
15 self.names = [name]
16 self.begin = value
17 self.end = self.begin + 1
18
Paul Lawrence7ea40902017-02-14 13:32:23 -080019 def __str__(self):
20 return "(%s, %s, %s)" % (self.begin, self.end, self.names)
21
Paul Lawrenceeabc3522016-11-11 11:33:42 -080022 def add(self, name, value):
23 if value != self.end:
24 raise ValueError
25 self.end += 1
26 self.names.append(name)
27
28
Steve Muckleaa3f96c2017-07-20 13:11:54 -070029def get_names(syscall_files, architecture, global_policy):
Paul Lawrence3dd3d552017-04-12 10:02:54 -070030 syscall_lists = []
Paul Lawrence7ea40902017-02-14 13:32:23 -080031 for syscall_file in syscall_files:
32 parser = SysCallsTxtParser()
33 parser.parse_open_file(syscall_file)
Paul Lawrence3dd3d552017-04-12 10:02:54 -070034 syscall_lists.append(parser.syscalls)
35
36 bionic, whitelist, blacklist = syscall_lists[0], syscall_lists[1], syscall_lists[2]
Steve Muckleaa3f96c2017-07-20 13:11:54 -070037 if global_policy:
38 global_whitelist = syscall_lists[-1]
39 else:
40 global_whitelist = []
41
Paul Lawrence3dd3d552017-04-12 10:02:54 -070042 for x in blacklist:
43 if not x in bionic:
44 raise RuntimeError("Blacklist item not in bionic - aborting " + str(x))
45
46 if x in whitelist:
47 raise RuntimeError("Blacklist item in whitelist - aborting " + str(x))
48
49 bionic_minus_blacklist = [x for x in bionic if x not in blacklist]
Steve Muckleaa3f96c2017-07-20 13:11:54 -070050 syscalls = bionic_minus_blacklist + whitelist + global_whitelist
Paul Lawrenceeabc3522016-11-11 11:33:42 -080051
52 # Select only elements matching required architecture
53 syscalls = [x for x in syscalls if architecture in x and x[architecture]]
54
55 # We only want the name
Paul Lawrence89fa81f2017-02-17 10:22:03 -080056 names = [x["name"] for x in syscalls]
57
58 # Check for duplicates
59 dups = [name for name, count in collections.Counter(names).items() if count > 1]
60
61 # x86 has duplicate socketcall entries, so hard code for this
62 if architecture == "x86":
63 dups.remove("socketcall")
64
65 if len(dups) > 0:
Paul Lawrence3dd3d552017-04-12 10:02:54 -070066 raise RuntimeError("Duplicate entries found - aborting " + str(dups))
Paul Lawrence89fa81f2017-02-17 10:22:03 -080067
68 # Remove remaining duplicates
69 return list(set(names))
Paul Lawrenceeabc3522016-11-11 11:33:42 -080070
Paul Lawrence7ea40902017-02-14 13:32:23 -080071
Paul Lawrence89fa81f2017-02-17 10:22:03 -080072def convert_names_to_NRs(names, header_dir, extra_switches):
Paul Lawrenceeabc3522016-11-11 11:33:42 -080073 # Run preprocessor over the __NR_syscall symbols, including unistd.h,
74 # to get the actual numbers
75 prefix = "__SECCOMP_" # prefix to ensure no name collisions
76 cpp = Popen(["../../prebuilts/clang/host/linux-x86/clang-stable/bin/clang",
Paul Lawrence89fa81f2017-02-17 10:22:03 -080077 "-E", "-nostdinc", "-I" + header_dir, "-Ikernel/uapi/"]
78 + extra_switches
79 + ["-"],
Paul Lawrenceeabc3522016-11-11 11:33:42 -080080 stdin=PIPE, stdout=PIPE)
81 cpp.stdin.write("#include <asm/unistd.h>\n")
82 for name in names:
83 # In SYSCALLS.TXT, there are two arm-specific syscalls whose names start
84 # with __ARM__NR_. These we must simply write out as is.
85 if not name.startswith("__ARM_NR_"):
86 cpp.stdin.write(prefix + name + ", __NR_" + name + "\n")
87 else:
88 cpp.stdin.write(prefix + name + ", " + name + "\n")
89 content = cpp.communicate()[0].split("\n")
90
91 # The input is now the preprocessed source file. This will contain a lot
92 # of junk from the preprocessor, but our lines will be in the format:
93 #
94 # __SECCOMP_${NAME}, (0 + value)
95
96 syscalls = []
97 for line in content:
98 if not line.startswith(prefix):
99 continue
100
101 # We might pick up extra whitespace during preprocessing, so best to strip.
102 name, value = [w.strip() for w in line.split(",")]
103 name = name[len(prefix):]
104
105 # Note that some of the numbers were expressed as base + offset, so we
106 # need to eval, not just int
107 value = eval(value)
108 syscalls.append((name, value))
109
Paul Lawrence7ea40902017-02-14 13:32:23 -0800110 return syscalls
111
112
113def convert_NRs_to_ranges(syscalls):
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800114 # Sort the values so we convert to ranges and binary chop
115 syscalls = sorted(syscalls, lambda x, y: cmp(x[1], y[1]))
116
117 # Turn into a list of ranges. Keep the names for the comments
118 ranges = []
119 for name, value in syscalls:
120 if not ranges:
121 ranges.append(SyscallRange(name, value))
122 continue
123
124 last_range = ranges[-1]
125 if last_range.end == value:
126 last_range.add(name, value)
127 else:
128 ranges.append(SyscallRange(name, value))
Paul Lawrence7ea40902017-02-14 13:32:23 -0800129 return ranges
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800130
Paul Lawrence7ea40902017-02-14 13:32:23 -0800131
132# Converts the sorted ranges of allowed syscalls to a binary tree bpf
133# For a single range, output a simple jump to {fail} or {allow}. We can't set
134# the jump ranges yet, since we don't know the size of the filter, so use a
135# placeholder
136# For multiple ranges, split into two, convert the two halves and output a jump
137# to the correct half
138def convert_to_intermediate_bpf(ranges):
139 if len(ranges) == 1:
140 # We will replace {fail} and {allow} with appropriate range jumps later
141 return [BPF_JGE.format(ranges[0].end, "{fail}", "{allow}") +
142 ", //" + "|".join(ranges[0].names)]
143 else:
144 half = (len(ranges) + 1) / 2
145 first = convert_to_intermediate_bpf(ranges[:half])
146 second = convert_to_intermediate_bpf(ranges[half:])
147 jump = [BPF_JGE.format(ranges[half].begin, len(first), 0) + ","]
148 return jump + first + second
149
150
151def convert_ranges_to_bpf(ranges):
152 bpf = convert_to_intermediate_bpf(ranges)
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800153
154 # Now we know the size of the tree, we can substitute the {fail} and {allow}
155 # placeholders
156 for i, statement in enumerate(bpf):
157 # Replace placeholder with
158 # "distance to jump to fail, distance to jump to allow"
159 # We will add a kill statement and an allow statement after the tree
160 # With bpfs jmp 0 means the next statement, so the distance to the end is
161 # len(bpf) - i - 1, which is where we will put the kill statement, and
162 # then the statement after that is the allow statement
163 if "{fail}" in statement and "{allow}" in statement:
Paul Lawrencebe8a2af2017-01-25 15:20:52 -0800164 bpf[i] = statement.format(fail=str(len(bpf) - i),
165 allow=str(len(bpf) - i - 1))
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800166
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800167
Paul Lawrencebe8a2af2017-01-25 15:20:52 -0800168 # Add the allow calls at the end. If the syscall is not matched, we will
169 # continue. This allows the user to choose to match further syscalls, and
170 # also to choose the action when we want to block
Paul Lawrence7ea40902017-02-14 13:32:23 -0800171 bpf.append(BPF_ALLOW + ",")
Paul Lawrence65b47c92017-03-22 08:03:51 -0700172
173 # Add check that we aren't off the bottom of the syscalls
174 bpf.insert(0, BPF_JGE.format(ranges[0].begin, 0, str(len(bpf))) + ',')
Paul Lawrence7ea40902017-02-14 13:32:23 -0800175 return bpf
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800176
Paul Lawrence7ea40902017-02-14 13:32:23 -0800177
Steve Muckleaa3f96c2017-07-20 13:11:54 -0700178def convert_bpf_to_output(bpf, architecture, global_policy):
179 suffix = "global_" if global_policy else ""
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800180 header = textwrap.dedent("""\
181 // Autogenerated file - edit at your peril!!
182
183 #include <linux/filter.h>
184 #include <errno.h>
185
Paul Lawrencedfe84342017-02-16 09:24:39 -0800186 #include "seccomp_bpfs.h"
Steve Muckleaa3f96c2017-07-20 13:11:54 -0700187 const sock_filter {architecture}_{suffix}filter[] = {{
188 """).format(architecture=architecture,suffix=suffix)
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800189
190 footer = textwrap.dedent("""\
191
192 }};
193
Steve Muckleaa3f96c2017-07-20 13:11:54 -0700194 const size_t {architecture}_{suffix}filter_size = sizeof({architecture}_{suffix}filter) / sizeof(struct sock_filter);
195 """).format(architecture=architecture,suffix=suffix)
Paul Lawrence7ea40902017-02-14 13:32:23 -0800196 return header + "\n".join(bpf) + footer
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800197
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800198
Steve Muckleaa3f96c2017-07-20 13:11:54 -0700199def construct_bpf(syscall_files, architecture, header_dir, extra_switches,
200 global_policy):
201 names = get_names(syscall_files, architecture, global_policy)
Paul Lawrence89fa81f2017-02-17 10:22:03 -0800202 syscalls = convert_names_to_NRs(names, header_dir, extra_switches)
Paul Lawrence7ea40902017-02-14 13:32:23 -0800203 ranges = convert_NRs_to_ranges(syscalls)
204 bpf = convert_ranges_to_bpf(ranges)
Steve Muckleaa3f96c2017-07-20 13:11:54 -0700205 return convert_bpf_to_output(bpf, architecture, global_policy)
Paul Lawrence7ea40902017-02-14 13:32:23 -0800206
207
Paul Lawrence3dd3d552017-04-12 10:02:54 -0700208ANDROID_SYSCALL_FILES = ["SYSCALLS.TXT",
209 "SECCOMP_WHITELIST.TXT",
210 "SECCOMP_BLACKLIST.TXT"]
Paul Lawrence7ea40902017-02-14 13:32:23 -0800211
Paul Lawrence89fa81f2017-02-17 10:22:03 -0800212
213POLICY_CONFIGS = [("arm", "kernel/uapi/asm-arm", []),
214 ("arm64", "kernel/uapi/asm-arm64", []),
215 ("x86", "kernel/uapi/asm-x86", ["-D__i386__"]),
216 ("x86_64", "kernel/uapi/asm-x86", []),
217 ("mips", "kernel/uapi/asm-mips", ["-D_MIPS_SIM=_MIPS_SIM_ABI32"]),
218 ("mips64", "kernel/uapi/asm-mips", ["-D_MIPS_SIM=_MIPS_SIM_ABI64"])]
Paul Lawrence7ea40902017-02-14 13:32:23 -0800219
220
221def set_dir():
222 # Set working directory for predictable results
223 os.chdir(os.path.join(os.environ["ANDROID_BUILD_TOP"], "bionic/libc"))
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800224
225
Steve Muckleaa3f96c2017-07-20 13:11:54 -0700226def gen_policy(global_policy):
227 if global_policy:
228 ANDROID_SYSCALL_FILES.append("SECCOMP_WHITELIST_GLOBAL.TXT")
229
Paul Lawrence89fa81f2017-02-17 10:22:03 -0800230 for arch, header_path, switches in POLICY_CONFIGS:
Paul Lawrence7ea40902017-02-14 13:32:23 -0800231 files = [open(filename) for filename in ANDROID_SYSCALL_FILES]
Steve Muckleaa3f96c2017-07-20 13:11:54 -0700232 output = construct_bpf(files, arch, header_path, switches, global_policy)
Paul Lawrence7ea40902017-02-14 13:32:23 -0800233
234 # And output policy
235 existing = ""
Steve Muckleaa3f96c2017-07-20 13:11:54 -0700236 global_string = "_global" if global_policy else ""
237 output_path = "seccomp/{}{}_policy.cpp".format(arch, global_string)
Paul Lawrence7ea40902017-02-14 13:32:23 -0800238 if os.path.isfile(output_path):
239 existing = open(output_path).read()
240 if output == existing:
241 print "File " + output_path + " not changed."
242 else:
243 with open(output_path, "w") as output_file:
244 output_file.write(output)
245 print "Generated file " + output_path
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800246
Steve Muckleaa3f96c2017-07-20 13:11:54 -0700247
248def main():
249 set_dir()
250 gen_policy(False)
251 gen_policy(True)
252
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800253if __name__ == "__main__":
254 main()