blob: 7d2b1da6aa0b97ee132cf349c532bcd6702766e2 [file] [log] [blame]
Paul Lawrenceeabc3522016-11-11 11:33:42 -08001#!/usr/bin/env python
2import os
3from subprocess import Popen, PIPE
4import textwrap
5from gensyscalls import SysCallsTxtParser
6
7
Paul Lawrence7ea40902017-02-14 13:32:23 -08008BPF_JGE = "BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, {0}, {1}, {2})"
9BPF_ALLOW = "BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW)"
Paul Lawrenceeabc3522016-11-11 11:33:42 -080010
11
12class SyscallRange(object):
13 def __init__(self, name, value):
14 self.names = [name]
15 self.begin = value
16 self.end = self.begin + 1
17
Paul Lawrence7ea40902017-02-14 13:32:23 -080018 def __str__(self):
19 return "(%s, %s, %s)" % (self.begin, self.end, self.names)
20
Paul Lawrenceeabc3522016-11-11 11:33:42 -080021 def add(self, name, value):
22 if value != self.end:
23 raise ValueError
24 self.end += 1
25 self.names.append(name)
26
27
Paul Lawrence7ea40902017-02-14 13:32:23 -080028def get_names(syscall_files, architecture):
29 syscalls = []
30 for syscall_file in syscall_files:
31 parser = SysCallsTxtParser()
32 parser.parse_open_file(syscall_file)
33 syscalls += parser.syscalls
Paul Lawrenceeabc3522016-11-11 11:33:42 -080034
35 # Select only elements matching required architecture
36 syscalls = [x for x in syscalls if architecture in x and x[architecture]]
37
38 # We only want the name
Paul Lawrence7ea40902017-02-14 13:32:23 -080039 return [x["name"] for x in syscalls]
Paul Lawrenceeabc3522016-11-11 11:33:42 -080040
Paul Lawrence7ea40902017-02-14 13:32:23 -080041
42def convert_names_to_NRs(names, header_dir):
Paul Lawrenceeabc3522016-11-11 11:33:42 -080043 # Run preprocessor over the __NR_syscall symbols, including unistd.h,
44 # to get the actual numbers
45 prefix = "__SECCOMP_" # prefix to ensure no name collisions
46 cpp = Popen(["../../prebuilts/clang/host/linux-x86/clang-stable/bin/clang",
47 "-E", "-nostdinc", "-I" + header_dir, "-Ikernel/uapi/", "-"],
48 stdin=PIPE, stdout=PIPE)
49 cpp.stdin.write("#include <asm/unistd.h>\n")
50 for name in names:
51 # In SYSCALLS.TXT, there are two arm-specific syscalls whose names start
52 # with __ARM__NR_. These we must simply write out as is.
53 if not name.startswith("__ARM_NR_"):
54 cpp.stdin.write(prefix + name + ", __NR_" + name + "\n")
55 else:
56 cpp.stdin.write(prefix + name + ", " + name + "\n")
57 content = cpp.communicate()[0].split("\n")
58
59 # The input is now the preprocessed source file. This will contain a lot
60 # of junk from the preprocessor, but our lines will be in the format:
61 #
62 # __SECCOMP_${NAME}, (0 + value)
63
64 syscalls = []
65 for line in content:
66 if not line.startswith(prefix):
67 continue
68
69 # We might pick up extra whitespace during preprocessing, so best to strip.
70 name, value = [w.strip() for w in line.split(",")]
71 name = name[len(prefix):]
72
73 # Note that some of the numbers were expressed as base + offset, so we
74 # need to eval, not just int
75 value = eval(value)
76 syscalls.append((name, value))
77
Paul Lawrence7ea40902017-02-14 13:32:23 -080078 return syscalls
79
80
81def convert_NRs_to_ranges(syscalls):
Paul Lawrenceeabc3522016-11-11 11:33:42 -080082 # Sort the values so we convert to ranges and binary chop
83 syscalls = sorted(syscalls, lambda x, y: cmp(x[1], y[1]))
84
85 # Turn into a list of ranges. Keep the names for the comments
86 ranges = []
87 for name, value in syscalls:
88 if not ranges:
89 ranges.append(SyscallRange(name, value))
90 continue
91
92 last_range = ranges[-1]
93 if last_range.end == value:
94 last_range.add(name, value)
95 else:
96 ranges.append(SyscallRange(name, value))
Paul Lawrence7ea40902017-02-14 13:32:23 -080097 return ranges
Paul Lawrenceeabc3522016-11-11 11:33:42 -080098
Paul Lawrence7ea40902017-02-14 13:32:23 -080099
100# Converts the sorted ranges of allowed syscalls to a binary tree bpf
101# For a single range, output a simple jump to {fail} or {allow}. We can't set
102# the jump ranges yet, since we don't know the size of the filter, so use a
103# placeholder
104# For multiple ranges, split into two, convert the two halves and output a jump
105# to the correct half
106def convert_to_intermediate_bpf(ranges):
107 if len(ranges) == 1:
108 # We will replace {fail} and {allow} with appropriate range jumps later
109 return [BPF_JGE.format(ranges[0].end, "{fail}", "{allow}") +
110 ", //" + "|".join(ranges[0].names)]
111 else:
112 half = (len(ranges) + 1) / 2
113 first = convert_to_intermediate_bpf(ranges[:half])
114 second = convert_to_intermediate_bpf(ranges[half:])
115 jump = [BPF_JGE.format(ranges[half].begin, len(first), 0) + ","]
116 return jump + first + second
117
118
119def convert_ranges_to_bpf(ranges):
120 bpf = convert_to_intermediate_bpf(ranges)
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800121
122 # Now we know the size of the tree, we can substitute the {fail} and {allow}
123 # placeholders
124 for i, statement in enumerate(bpf):
125 # Replace placeholder with
126 # "distance to jump to fail, distance to jump to allow"
127 # We will add a kill statement and an allow statement after the tree
128 # With bpfs jmp 0 means the next statement, so the distance to the end is
129 # len(bpf) - i - 1, which is where we will put the kill statement, and
130 # then the statement after that is the allow statement
131 if "{fail}" in statement and "{allow}" in statement:
Paul Lawrencebe8a2af2017-01-25 15:20:52 -0800132 bpf[i] = statement.format(fail=str(len(bpf) - i),
133 allow=str(len(bpf) - i - 1))
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800134
135 # Add check that we aren't off the bottom of the syscalls
Paul Lawrence7ea40902017-02-14 13:32:23 -0800136 bpf.insert(0, BPF_JGE.format(ranges[0].begin, 0, str(len(bpf))) + ',')
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800137
Paul Lawrencebe8a2af2017-01-25 15:20:52 -0800138 # Add the allow calls at the end. If the syscall is not matched, we will
139 # continue. This allows the user to choose to match further syscalls, and
140 # also to choose the action when we want to block
Paul Lawrence7ea40902017-02-14 13:32:23 -0800141 bpf.append(BPF_ALLOW + ",")
142 return bpf
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800143
Paul Lawrence7ea40902017-02-14 13:32:23 -0800144
145def convert_bpf_to_output(bpf, architecture):
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800146 header = textwrap.dedent("""\
147 // Autogenerated file - edit at your peril!!
148
149 #include <linux/filter.h>
150 #include <errno.h>
151
152 #include "seccomp_policy.h"
153 const struct sock_filter {architecture}_filter[] = {{
154 """).format(architecture=architecture)
155
156 footer = textwrap.dedent("""\
157
158 }};
159
160 const size_t {architecture}_filter_size = sizeof({architecture}_filter) / sizeof(struct sock_filter);
161 """).format(architecture=architecture)
Paul Lawrence7ea40902017-02-14 13:32:23 -0800162 return header + "\n".join(bpf) + footer
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800163
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800164
Paul Lawrence7ea40902017-02-14 13:32:23 -0800165def construct_bpf(syscall_files, architecture, header_dir):
166 names = get_names(syscall_files, architecture)
167 syscalls = convert_names_to_NRs(names, header_dir)
168 ranges = convert_NRs_to_ranges(syscalls)
169 bpf = convert_ranges_to_bpf(ranges)
170 return convert_bpf_to_output(bpf, architecture)
171
172
173android_syscall_files = ["SYSCALLS.TXT", "SECCOMP_WHITELIST.TXT"]
174arm_headers = "kernel/uapi/asm-arm"
175arm64_headers = "kernel/uapi/asm-arm64"
176arm_architecture = "arm"
177arm64_architecture = "arm64"
178
179
180ANDROID_SYSCALL_FILES = ["SYSCALLS.TXT", "SECCOMP_WHITELIST.TXT"]
181
182POLICY_CONFIGS = [("arm", "kernel/uapi/asm-arm"),
183 ("arm64", "kernel/uapi/asm-arm64")]
184
185
186def set_dir():
187 # Set working directory for predictable results
188 os.chdir(os.path.join(os.environ["ANDROID_BUILD_TOP"], "bionic/libc"))
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800189
190
191def main():
Paul Lawrence7ea40902017-02-14 13:32:23 -0800192 set_dir()
193 for arch, header_path in POLICY_CONFIGS:
194 files = [open(filename) for filename in ANDROID_SYSCALL_FILES]
195 output = construct_bpf(files, arch, header_path)
196
197 # And output policy
198 existing = ""
199 output_path = "seccomp/{}_policy.c".format(arch)
200 if os.path.isfile(output_path):
201 existing = open(output_path).read()
202 if output == existing:
203 print "File " + output_path + " not changed."
204 else:
205 with open(output_path, "w") as output_file:
206 output_file.write(output)
207 print "Generated file " + output_path
Paul Lawrenceeabc3522016-11-11 11:33:42 -0800208
209
210if __name__ == "__main__":
211 main()