#!/usr/bin/python # # Copyright (C) 2016 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # This script will take any number of trace files generated by strace(1) # and output a system call filtering policy suitable for use with Minijail. from collections import namedtuple import sys NOTICE = """# Copyright (C) 2016 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ ALLOW = "%s: 1" SOCKETCALLS = ["accept", "bind", "connect", "getpeername", "getsockname", "getsockopt", "listen", "recv", "recvfrom", "recvmsg", "send", "sendmsg", "sendto", "setsockopt", "shutdown", "socket", "socketpair"] # /* Protocol families. */ # #define PF_UNSPEC 0 /* Unspecified. */ # #define PF_LOCAL 1 /* Local to host (pipes and file-domain). */ # #define PF_UNIX PF_LOCAL /* POSIX name for PF_LOCAL. */ # #define PF_FILE PF_LOCAL /* Another non-standard name for PF_LOCAL. */ # #define PF_INET 2 /* IP protocol family. */ # #define PF_AX25 3 /* Amateur Radio AX.25. */ # #define PF_IPX 4 /* Novell Internet Protocol. */ # #define PF_APPLETALK 5 /* Appletalk DDP. */ # #define PF_NETROM 6 /* Amateur radio NetROM. */ # #define PF_BRIDGE 7 /* Multiprotocol bridge. */ # #define PF_ATMPVC 8 /* ATM PVCs. */ # #define PF_X25 9 /* Reserved for X.25 project. */ # #define PF_INET6 10 /* IP version 6. */ # #define PF_ROSE 11 /* Amateur Radio X.25 PLP. */ # #define PF_DECnet 12 /* Reserved for DECnet project. */ # #define PF_NETBEUI 13 /* Reserved for 802.2LLC project. */ # #define PF_SECURITY 14 /* Security callback pseudo AF. */ # #define PF_KEY 15 /* PF_KEY key management API. */ # #define PF_NETLINK 16 ArgInspectionEntry = namedtuple("ArgInspectionEntry", "arg_index value_set") def usage(argv): print "%s <trace file> [trace files...]" % argv[0] def main(traces): syscalls = {} uses_socketcall = False basic_set = ["restart_syscall", "exit", "exit_group", "rt_sigreturn"] frequent_set = [] syscall_sets = {} syscall_set_list = [["sigreturn", "rt_sigreturn"], ["sigaction", "rt_sigaction"], ["sigprocmask", "rt_sigprocmask"], ["open", "openat"], ["mmap", "mremap"], ["mmap2", "mremap"]] arg_inspection = { "socket": ArgInspectionEntry(0, set([])), # int domain "ioctl": ArgInspectionEntry(1, set([])), # int request "prctl": ArgInspectionEntry(0, set([])) # int option } for syscall_list in syscall_set_list: for syscall in syscall_list: other_syscalls = syscall_list[:] other_syscalls.remove(syscall) syscall_sets[syscall] = other_syscalls for trace_filename in traces: if "i386" in trace_filename or ("x86" in trace_filename and "64" not in trace_filename): uses_socketcall = True trace_file = open(trace_filename) for line in trace_file: if "---" in line or '(' not in line: continue syscall, args = line.strip().split('(', 1) if uses_socketcall and syscall in SOCKETCALLS: syscall = "socketcall" if syscall in syscalls: syscalls[syscall] += 1 else: syscalls[syscall] = 1 args = [arg.strip() for arg in args.split(')', 1)[0].split(',')] if syscall in arg_inspection: arg_value = args[arg_inspection[syscall].arg_index] arg_inspection[syscall].value_set.add(arg_value) sorted_syscalls = list(zip(*sorted(syscalls.iteritems(), key=lambda pair: pair[1], reverse=True))[0]) print NOTICE # Add frequent syscalls first. for frequent_syscall in frequent_set: sorted_syscalls.remove(frequent_syscall) all_syscalls = frequent_set + sorted_syscalls # Add the basic set once the frequency drops below 2. below_ten_index = -1 for sorted_syscall in sorted_syscalls: if syscalls[sorted_syscall] < 2: below_ten_index = all_syscalls.index(sorted_syscall) break first_half = all_syscalls[:below_ten_index] for basic_syscall in basic_set: if basic_syscall not in all_syscalls: first_half.append(basic_syscall) all_syscalls = first_half + all_syscalls[below_ten_index:] for syscall in all_syscalls: if syscall in arg_inspection: arg_index = arg_inspection[syscall].arg_index arg_values = arg_inspection[syscall].value_set arg_filter = " || ".join(["arg%d == %s" % (arg_index, arg_value) for arg_value in arg_values]) print syscall + ": " + arg_filter else: print ALLOW % syscall if __name__ == "__main__": if len(sys.argv) < 2: usage(sys.argv) sys.exit(1) main(sys.argv[1:])