├── LICENSE ├── README.md ├── mac_sshlisteners ├── mac_skconnections ├── mac_suidexec ├── mac_fileperms └── mac_killtasks /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Billy Wilson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BCC LSM Scripts (KRSI) 2 | 3 | These scripts were written to provide working examples of Kernel Runtime Security Instrumentation (KRSI). Why use KRSI? Because it lets you write custom, modular programs to roll your own mandatory access control. Think of it like using a surgeon's scalpel to carve out the behavior you don't want. It can be a valuable replacement in environments where SELinux causes too large of a performance impact. 4 | 5 | You can read my paper about KRSI in [The SANS Reading Room](https://www.sans.org/reading-room/whitepapers/linux/paper/40010). The paper explains these scripts and includes a tutorial in Appendix A. There are still very few examples of KRSI usage in the wild. This will hopefully change when `bpftrace` adds support for it and makes it easier to use. 6 | 7 | KRSI has undergone a bit of a naming crisis. It is also called "LSM Probes" and "LSM BPF Hooks" in various projects. All these names refer to the same thing. My scripts use BPF Compiler Collection (BCC) to write programs that leverage KRSI. So I decided to name this repo "BCC LSM Scripts." I know, I'm perpetuating the problem. 8 | 9 | ## Dependencies 10 | 11 | * Linux Kernel: 5.7 or newer 12 | * BPF Compiler Collection (BCC): 0.15.0 or newer 13 | 14 | The `lsm=` kernel parameter specifies a comma-delimited string of LSMs to enable at runtime. Add `bpf` to enable KRSI. 15 | 16 | ## Summary of Scripts 17 | 18 | The following table gives a quick summary of each script. 19 | 20 | | **Script Name** | **Description** | 21 | | ----------- | ----------- | 22 | | **`mac_fileperms`** | Controls the creation of files with "Set UID" or "Writeable By Others" permission bits. | 23 | | **`mac_killtasks`** | Controls where kill signals are allowed to be sent | 24 | | **`mac_skconnections`** | Controls the destinations to which IPv4 socket connections can be made | 25 | | **`mac_sshlisteners`** | Controls locally listening SSH proxies (look up the -D and -L flags of SSH) | 26 | | **`mac_suidexec`** | Controls the execution of files that have the "Set UID" permission bit set | 27 | 28 | 29 | ## Universal Options 30 | 31 | | **Short Flag** | **Long Flag** | **Description** | 32 | | ----------- | ----------- | ----------- | 33 | | **`-h`** | **`--help`** | Print all options of a script and provide example usage | 34 | | **`-A`** | **`--allow`** | Define an "Allow" policy | 35 | | **`-D`** | **`--deny`** | Define a "Deny" policy | 36 | | **`-u`** | **`--user`** | Apply the policy to a specific user | 37 | | **`-U`** | **`--exclude-user`** | Exclude a specific user from the policy | 38 | 39 | ## Script-Specific Options 40 | 41 | | **Script** | **Short Flag** | **Long Flag** | **Description** | 42 | | ----------- | ----------- | ----------- | ----------- | 43 | | **`mac_killtasks`** | **`-k`** | **`--kernel`** | Apply the policy to kernel signals as well (*dangerous*) | 44 | | **`mac_killtasks`** | **`-e`** | **`--eternal`** | Block all kill signals to the process that loaded this policy | 45 | | **`mac_killtasks`** | **`-p`** | **`--source-pid`** | Apply the policy to a source PID | 46 | | **`mac_killtasks`** | **`-P`** | **`--exclude-source-pid`** | Exclude a source PID from the policy | 47 | | **`mac_killtasks`** | **`-t`** | **`--target-pid`** | Apply the policy to a target PID | 48 | | **`mac_killtasks`** | **`-T`** | **`--exclude-target-pid`** | Exclude a target PID from the policy | 49 | | **`mac_skconnections`** | **`-m`** | **`--mask`** | The subnet mask for an allow/deny policy | 50 | | **`mac_suidexec`** | **`-f`** | **`--file`** | Apply the policy to a file | 51 | | **`mac_suidexec`** | **`-F`** | **`--exclude-file`** | Exclude a file from the policy | 52 | 53 | Also, mac_skconnections takes a required positional `[ip]` argument to define which IP or subnet the policy applies to. 54 | -------------------------------------------------------------------------------- /mac_sshlisteners: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # mac_sshlisteners Restrict SSH clients from listening on sockets. 4 | # Supports IPv4 and IPv6. 5 | # 6 | # 10-Oct-2020 Billy Wilson Created this. 7 | 8 | from bcc import BPF 9 | from bcc.utils import printb 10 | import argparse 11 | import datetime 12 | import pwd 13 | import socket 14 | from socket import inet_ntop, AF_INET, AF_INET6 15 | import struct 16 | 17 | 18 | def parse_uid(user): 19 | """ 20 | Check if valid UID or username is provided 21 | """ 22 | try: 23 | result = int(user) 24 | except ValueError: 25 | try: 26 | user_info = pwd.getpwnam(user) 27 | except KeyError: 28 | raise argparse.ArgumentTypeError( 29 | "{0!r} is not valid UID or user entry".format(user)) 30 | else: 31 | return user_info.pw_uid 32 | else: 33 | # Maybe validate if UID < 0 ? 34 | return result 35 | 36 | 37 | def ip2long(ip): 38 | """ 39 | Convert an IP string to a long 40 | """ 41 | packedIP = socket.inet_aton(ip) 42 | return struct.unpack("!L", packedIP)[0] 43 | 44 | 45 | def long2ip(num): 46 | """ 47 | Convert a long to an IP string 48 | """ 49 | return socket.inet_ntoa(struct.pack("!L", num)) 50 | 51 | 52 | # Set up argument parser 53 | examples = """examples: 54 | ./mac_sshlisteners -A # Allow all SSH listeners 55 | ./mac_sshlisteners -A -u 1000 # Allow only UID 1000 to open SSH listeners 56 | ./mac_sshlisteners -D # Deny all SSH proxies 57 | ./mac_sshlisteners -D -u 1000 # Deny only UID 1000 from opening SSH listeners 58 | """ 59 | 60 | parser = argparse.ArgumentParser( 61 | description="Dynamically load a MAC policy for SSH listeners", 62 | formatter_class=argparse.RawDescriptionHelpFormatter, 63 | epilog=examples) 64 | mode = parser.add_mutually_exclusive_group(required=True) 65 | mode.add_argument("-A", "--allow", action="store_true", 66 | help="define an allow policy") 67 | mode.add_argument("-D", "--deny", action="store_true", 68 | help="define a deny policy") 69 | user_control = parser.add_mutually_exclusive_group() 70 | user_control.add_argument("-u", "--user", type=parse_uid, metavar="USER", 71 | help="apply the policy to a specific user") 72 | user_control.add_argument("-U", "--exclude-user", type=parse_uid, metavar="EXCLUDED_USER", 73 | help="exclude a specific user from the policy") 74 | 75 | args = parser.parse_args() 76 | 77 | 78 | # BPF program (in C) 79 | bpf_text = """ 80 | #include 81 | #include 82 | #include 83 | 84 | #define __LOWER(x) (x & 0xffffffff) 85 | #define __UPPER(x) (x >> 32) 86 | #define __SWAP32(x) ((x >> 24) & 0xff) | \ 87 | ((x >> 8) & 0xff00) | \ 88 | ((x << 8) & 0xff0000) | \ 89 | ((x << 24) & 0xff000000) 90 | 91 | /* 92 | * Create a data structure for collecting event data 93 | */ 94 | struct data_t { 95 | char comm[TASK_COMM_LEN]; 96 | u32 uid; 97 | u32 gid; 98 | u32 pid; 99 | unsigned short family; 100 | u16 protocol; 101 | unsigned short lport; 102 | long laddr4; 103 | unsigned __int128 laddr6; 104 | int allowed; 105 | }; 106 | 107 | /* 108 | * Create buffers for sending event data to userspace 109 | */ 110 | BPF_PERF_OUTPUT(events); 111 | 112 | /* 113 | * Attach to the "socket_listen" LSM hook 114 | */ 115 | LSM_PROBE(socket_listen, struct socket *sock, int backlog) { 116 | 117 | u64 gid_uid; 118 | u64 pid_tgid; 119 | int allowed; 120 | struct inet_sock *sockp; 121 | 122 | struct data_t data = {}; 123 | 124 | /* 125 | * Examine IPv4 and IPv6 socket listen operations 126 | */ 127 | sockp = (struct inet_sock *)sock; 128 | data.family = sock->sk->sk_family; 129 | if (data.family == AF_INET || 130 | data.family == AF_INET6) { 131 | 132 | /* 133 | * Gather event data 134 | */ 135 | gid_uid = bpf_get_current_uid_gid(); 136 | pid_tgid = bpf_get_current_pid_tgid(); 137 | bpf_get_current_comm(&data.comm, sizeof(data.comm)); 138 | data.uid = __LOWER(gid_uid); 139 | data.gid = __UPPER(gid_uid); 140 | data.pid = __UPPER(pid_tgid); 141 | data.protocol = sock->sk->sk_protocol; 142 | data.lport = sock->sk->sk_num; 143 | if (data.family == AF_INET) 144 | data.laddr4 = __SWAP32(sock->sk->sk_rcv_saddr); 145 | else if (data.family == AF_INET6) 146 | bpf_probe_read_kernel(&data.laddr6, sizeof(data.laddr6), sock->sk->sk_v6_rcv_saddr.in6_u.u6_addr32); 147 | 148 | /* 149 | * "ssh" comm filter and optional filters (Populated by Python script) 150 | */ 151 | if ( 152 | (__builtin_memcmp("ssh", data.comm, sizeof("ssh")) == 0) 153 | UID_FILTER 154 | ) { 155 | data.allowed = 0; 156 | events.perf_submit(ctx, &data, sizeof(data)); 157 | return -EPERM; 158 | } else { 159 | data.allowed = 1; 160 | events.perf_submit(ctx, &data, sizeof(data)); 161 | } 162 | } 163 | return 0; 164 | } 165 | """ 166 | 167 | 168 | # Populate filters in the BPF program, basedon options passed to this script 169 | if args.user: 170 | bpf_text = bpf_text.replace('UID_FILTER', '&& (data.uid ALLOW_OR_DENY %s)' % args.user) 171 | elif args.exclude_user: 172 | bpf_text = bpf_text.replace('UID_FILTER', '&& (data.uid INVERSE %s)' % args.exclude_user) 173 | else: 174 | bpf_text = bpf_text.replace('UID_FILTER', '') 175 | 176 | if args.allow: 177 | bpf_text = bpf_text.replace('ALLOW_OR_DENY', '!=') 178 | bpf_text = bpf_text.replace('INVERSE', '==') 179 | elif args.deny: 180 | bpf_text = bpf_text.replace('ALLOW_OR_DENY', '==') 181 | bpf_text = bpf_text.replace('INVERSE', '!=') 182 | 183 | 184 | # Compile the BPF program and attach it to the LSM hook 185 | b = BPF(text=bpf_text) 186 | 187 | 188 | def print_event(cpu, data, size): 189 | """ 190 | Print event data for an IPv4/IPv6 socket listen attempt. 191 | """ 192 | event = b["events"].event(data) 193 | printb(b"%s type=listen comm=%s uid=%d gid=%d pid=%d proto=%d laddr=%s lport=%u action=%s" % ( 194 | datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S"), 195 | event.comm, 196 | event.uid, 197 | event.gid, 198 | event.pid, 199 | event.protocol, 200 | long2ip(event.laddr4) if event.family == AF_INET else "[" + inet_ntop(AF_INET6, event.laddr6) + "]", 201 | event.lport, 202 | "allow" if event.allowed else "deny")) 203 | 204 | 205 | # Setup callback function for the buffer 206 | b["events"].open_perf_buffer(print_event) 207 | 208 | 209 | # Poll for incoming events 210 | while 1: 211 | try: 212 | b.perf_buffer_poll() 213 | except KeyboardInterrupt: 214 | exit() 215 | -------------------------------------------------------------------------------- /mac_skconnections: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # mac_socketconnections Restrict IPv4 socket connections. 4 | # No Unix or IPv6 socket support (yet). 5 | # 6 | # 10-Oct-2020 Billy Wilson Created this. 7 | 8 | from bcc import BPF 9 | from bcc.utils import printb 10 | import argparse 11 | import datetime 12 | import pwd 13 | import socket 14 | import struct 15 | 16 | 17 | def parse_uid(user): 18 | """ 19 | Check if valid UID or username is provided 20 | """ 21 | try: 22 | result = int(user) 23 | except ValueError: 24 | try: 25 | user_info = pwd.getpwnam(user) 26 | except KeyError: 27 | raise argparse.ArgumentTypeError( 28 | "{0!r} is not valid UID or user entry".format(user)) 29 | else: 30 | return user_info.pw_uid 31 | else: 32 | # Maybe validate if UID < 0 ? 33 | return result 34 | 35 | 36 | def ip2long(ip): 37 | """ 38 | Convert an IP string to a long 39 | """ 40 | packedIP = socket.inet_aton(ip) 41 | return struct.unpack("!L", packedIP)[0] 42 | 43 | 44 | def long2ip(num): 45 | """ 46 | Convert a long to an IP string 47 | """ 48 | return socket.inet_ntoa(struct.pack("!L", num)) 49 | 50 | 51 | # Set up argument parser 52 | examples = """examples: 53 | ./mac_skconnections -A 192.168.15.0 -m 255.255.255.0 # Only allow socket connections to 192.168.15.0/24 54 | ./mac_skconnections -A 1.2.3.4 -u 1000 # Only allow UID 1000 to make socket connections to 1.2.3.4 55 | ./mac_skconnections -A 1.2.3.4 -U 1000 # Allow any UID except 1000 to make socket connections to 1.2.3.4 56 | ./mac_skconnections -D 10.1.2.3 # Deny all socket connections to 10.1.2.3 57 | """ 58 | 59 | parser = argparse.ArgumentParser( 60 | description="Dynamically load a MAC policy for socket connections", 61 | formatter_class=argparse.RawDescriptionHelpFormatter, 62 | epilog=examples) 63 | parser.add_argument("ip", type=str, 64 | help="the target IP for an allow/deny policy") 65 | parser.add_argument("-m", "--mask", metavar="MASK", default="255.255.255.255", 66 | help="the subnet mask for an allow/deny policy") 67 | mode = parser.add_mutually_exclusive_group(required=True) 68 | mode.add_argument("-A", "--allow", action="store_true", 69 | help="define an allow policy") 70 | mode.add_argument("-D", "--deny", action="store_true", 71 | help="define a deny policy") 72 | user_control = parser.add_mutually_exclusive_group() 73 | user_control.add_argument("-u", "--user", type=parse_uid, metavar="USER", 74 | help="apply the policy to a specific user") 75 | user_control.add_argument("-U", "--exclude-user", type=parse_uid, metavar="EXCLUDED_USER", 76 | help="exclude a specific user from the policy") 77 | args = parser.parse_args() 78 | 79 | 80 | # BPF program (in C) 81 | bpf_text = """ 82 | #include 83 | #include 84 | #include 85 | 86 | #define __LOWER(x) (x & 0xffffffff) 87 | #define __UPPER(x) (x >> 32) 88 | #define __SWAP16(x) (x >> 8) | ((x << 8) & 0x00ff00) 89 | #define __SWAP32(x) ((x >> 24) & 0xff) | \ 90 | ((x << 8) & 0xff0000) | \ 91 | ((x >> 8) & 0xff00) | \ 92 | ((x << 24) & 0xff000000) 93 | 94 | /* 95 | * Create a data structure for collecting event data 96 | */ 97 | struct data_t { 98 | char comm[TASK_COMM_LEN]; 99 | u32 uid; 100 | u32 gid; 101 | u32 pid; 102 | u16 sk_protocol; 103 | long daddr; 104 | unsigned short dport; 105 | int allowed; 106 | }; 107 | 108 | /* 109 | * Create a buffer for sending event data to userspace 110 | */ 111 | BPF_PERF_OUTPUT(events); 112 | 113 | /* 114 | * Attach to the "socket_connect" LSM hook 115 | */ 116 | LSM_PROBE(socket_connect, struct socket *sock, struct sockaddr *address, 117 | int addrlen) { 118 | 119 | long filter_addr; 120 | long filter_netmask; 121 | u64 gid_uid; 122 | u64 pid_tgid; 123 | int allowed; 124 | struct sockaddr_in *addr_in; 125 | 126 | /* 127 | * IP subnet filter (Populated by Python script) 128 | */ 129 | filter_addr = FILTER_ADDR; 130 | filter_netmask = FILTER_NETMASK; 131 | 132 | 133 | /* 134 | * Examine IPv4 socket connections only (for now) 135 | */ 136 | if (addrlen == sizeof(struct sockaddr_in)) { 137 | addr_in = (struct sockaddr_in *)address; 138 | 139 | /* 140 | * Gather event data 141 | */ 142 | struct data_t data = {}; 143 | gid_uid = bpf_get_current_uid_gid(); 144 | pid_tgid = bpf_get_current_pid_tgid(); 145 | bpf_get_current_comm(&data.comm, sizeof(data.comm)); 146 | data.uid = __LOWER(gid_uid); 147 | data.gid = __UPPER(gid_uid); 148 | data.pid = __UPPER(pid_tgid); 149 | data.sk_protocol = sock->sk->sk_protocol; 150 | data.daddr = __SWAP32(addr_in->sin_addr.s_addr); 151 | data.dport = __SWAP16(addr_in->sin_port); 152 | 153 | /* 154 | * Subnet filter and optional filters (Populated by Python script) 155 | */ 156 | if ( 157 | (data.daddr & filter_netmask) ALLOW_OR_DENY (filter_addr & filter_netmask) 158 | UID_FILTER 159 | ) { 160 | data.allowed = 0; 161 | events.perf_submit(ctx, &data, sizeof(data)); 162 | return -EPERM; 163 | } else { 164 | data.allowed = 1; 165 | events.perf_submit(ctx, &data, sizeof(data)); 166 | return 0; 167 | } 168 | } 169 | return 0; 170 | } 171 | """ 172 | 173 | 174 | # Convert IP and subnet to long integers for the BPF program 175 | ip = ip2long(args.ip) 176 | mask = ip2long(args.mask) 177 | bpf_text = bpf_text.replace('FILTER_ADDR', str(ip)); 178 | bpf_text = bpf_text.replace('FILTER_NETMASK', str(mask)); 179 | 180 | 181 | # Populate filters in the BPF program, based on options passed to this script 182 | if args.user: 183 | bpf_text = bpf_text.replace('UID_FILTER', '&& (data.uid ALLOW_OR_DENY %s)' % args.user) 184 | elif args.exclude_user: 185 | bpf_text = bpf_text.replace('UID_FILTER', '&& (data.uid INVERSE %s)' % args.exclude_user) 186 | else: 187 | bpf_text = bpf_text.replace('UID_FILTER', '') 188 | 189 | if args.allow: 190 | bpf_text = bpf_text.replace('ALLOW_OR_DENY', '!=') 191 | bpf_text = bpf_text.replace('INVERSE', '==') 192 | elif args.deny: 193 | bpf_text = bpf_text.replace('ALLOW_OR_DENY', '==') 194 | bpf_text = bpf_text.replace('INVERSE', '!=') 195 | 196 | 197 | # Compile the BPF program and attach it to the LSM hook 198 | b = BPF(text=bpf_text) 199 | 200 | 201 | def print_event(cpu, data, size): 202 | """ 203 | Print event data for a socket connection. 204 | """ 205 | event = b["events"].event(data) 206 | printb(b"%s type=skconn comm=%s uid=%d gid=%d pid=%d proto=%d daddr=%s dport=%u action=%s" % ( 207 | datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S"), 208 | event.comm, 209 | event.uid, 210 | event.gid, 211 | event.pid, 212 | event.sk_protocol, 213 | long2ip(event.daddr), 214 | event.dport, 215 | "allow" if event.allowed else "deny")) 216 | 217 | 218 | # Setup callback function for the buffer 219 | b["events"].open_perf_buffer(print_event) 220 | 221 | 222 | # Poll for incoming events 223 | while 1: 224 | try: 225 | b.perf_buffer_poll() 226 | except KeyboardInterrupt: 227 | exit() 228 | -------------------------------------------------------------------------------- /mac_suidexec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # mac_suidexec Restrict the execution of SUID binaries. 4 | # 5 | # 10-Oct-2020 Billy Wilson Created this. 6 | 7 | from bcc import BPF 8 | from bcc.utils import printb 9 | import argparse 10 | import datetime 11 | import os 12 | import pwd 13 | 14 | 15 | def parse_uid(user): 16 | """ 17 | Check if valid UID or username is provided 18 | """ 19 | try: 20 | result = int(user) 21 | except ValueError: 22 | try: 23 | user_info = pwd.getpwnam(user) 24 | except KeyError: 25 | raise argparse.ArgumentTypeError( 26 | "{0!r} is not valid UID or user entry".format(user)) 27 | else: 28 | return user_info.pw_uid 29 | else: 30 | # Maybe validate if UID < 0 ? 31 | return result 32 | 33 | 34 | # Set up argument parser 35 | examples = """examples: 36 | ./mac_suidexec -A # Allow execution of all SUID binaries 37 | ./mac_suidexec -A -u 1000 # Allow only UID 1000 to execute SUID binaries 38 | ./mac_suidexec -A -U 1000 # Allow all UIDs except 1000 to execute SUID binaries 39 | ./mac_suidexec -A -f /bin/passwd # Allow only the /bin/sudo SUID binary to execute 40 | ./mac_suidexec -A -F /bin/sudo # Allow all SUID binaries to execute except /bin/sudo 41 | ./mac_suidexec -D # Deny execution of all SUID binaries 42 | ./mac_suidexec -D -u 1000 # Deny UID 1000 from executing SUID binaries 43 | ./mac_suidexec -D -U 0 # Deny all UIDs except 0 from executing SUID binaries 44 | ./mac_suidexec -D -f /bin/chsh # Deny the /bin/chsh SUID binary from executing 45 | ./mac_suidexec -D -F /bin/newgrp # Deny all SUID binaries from executing except /bin/newgrp 46 | """ 47 | 48 | parser = argparse.ArgumentParser( 49 | description="Dynamically load a MAC policy for executing SUID binaries", 50 | formatter_class=argparse.RawDescriptionHelpFormatter, 51 | epilog=examples) 52 | mode = parser.add_mutually_exclusive_group(required=True) 53 | mode.add_argument("-A", "--allow", action="store_true", 54 | help="define an allow policy") 55 | mode.add_argument("-D", "--deny", action="store_true", 56 | help="define a deny policy") 57 | user_control = parser.add_mutually_exclusive_group() 58 | user_control.add_argument("-u", "--user", type=parse_uid, metavar="USER", 59 | help="apply the policy to a specific user") 60 | user_control.add_argument("-U", "--exclude-user", type=parse_uid, metavar="EXCLUDED_USER", 61 | help="exclude a specific user from the policy") 62 | file_control = parser.add_mutually_exclusive_group() 63 | file_control.add_argument("-f", "--file", metavar="FILE", dest="file_path", 64 | help="apply the policy to a file") 65 | file_control.add_argument("-F", "--exclude-file", metavar="EXCLUDED_FILE", 66 | help="exclude a file from the policy") 67 | 68 | args = parser.parse_args() 69 | 70 | 71 | # BPF program (in C) 72 | bpf_text = """ 73 | #include 74 | #include 75 | #include 76 | #include 77 | 78 | #define __LOWER(x) (x & 0xffffffff) 79 | #define __UPPER(x) (x >> 32) 80 | 81 | /* 82 | * Create a data structure for collecting event data 83 | */ 84 | struct data_t { 85 | char comm[TASK_COMM_LEN]; 86 | u32 uid; 87 | u32 gid; 88 | u32 pid; 89 | u32 dev; 90 | unsigned long inode; 91 | unsigned short mode; 92 | int allowed; 93 | }; 94 | 95 | /* 96 | * Create a buffer for sending event data to userspace 97 | */ 98 | BPF_PERF_OUTPUT(events); 99 | 100 | /* 101 | * Attach to the "bprm_check_security" LSM hook 102 | */ 103 | LSM_PROBE(bprm_check_security, struct linux_binprm *bprm) { 104 | 105 | u64 gid_uid; 106 | u64 pid_tgid; 107 | unsigned short mode; 108 | 109 | /* 110 | * Check if executable has its SUID bit set 111 | */ 112 | mode = bprm->file->f_inode->i_mode; 113 | if (mode & S_ISUID) { 114 | 115 | /* 116 | * Gather event data 117 | */ 118 | struct data_t data = {}; 119 | u64 gid_uid = bpf_get_current_uid_gid(); 120 | u64 pid_tgid = bpf_get_current_pid_tgid(); 121 | bpf_get_current_comm(&data.comm, sizeof(data.comm)); 122 | data.uid = __LOWER(gid_uid); 123 | data.gid = __UPPER(gid_uid); 124 | data.pid = __UPPER(pid_tgid); 125 | data.dev = bprm->file->f_inode->i_sb->s_dev; 126 | data.inode = bprm->file->f_inode->i_ino; 127 | data.mode = mode; 128 | 129 | /* 130 | * Optional filters (Populated by Python script) 131 | */ 132 | if ( 133 | 1 134 | UID_FILTER 135 | FILE_FILTER 136 | DEFAULT_ALLOW 137 | ) { 138 | data.allowed = 0; 139 | events.perf_submit(ctx, &data, sizeof(data)); 140 | return -EPERM; 141 | } else { 142 | data.allowed = 1; 143 | events.perf_submit(ctx, &data, sizeof(data)); 144 | } 145 | } 146 | return 0; 147 | } 148 | """ 149 | 150 | 151 | # Populate filters in the BPF program, based on options passed to this script 152 | if args.allow: 153 | if args.user: 154 | bpf_text = bpf_text.replace('UID_FILTER', 155 | '&& (data.uid != %s)' % args.user) 156 | elif args.exclude_user: 157 | bpf_text = bpf_text.replace('UID_FILTER', 158 | '&& (data.uid == %s)' % args.exclude_user) 159 | else: 160 | bpf_text = bpf_text.replace('UID_FILTER', '') 161 | 162 | if args.file_path: 163 | file_o = os.lstat(args.file_path) 164 | bpf_text = bpf_text.replace('FILE_FILTER', 165 | '&& !((data.dev == %s) && (data.inode == %s))' % (file_o.st_dev, file_o.st_ino)) 166 | elif args.exclude_file: 167 | file_o = os.lstat(args.exclude_file) 168 | bpf_text = bpf_text.replace('FILE_FILTER', 169 | '&& (data.dev == %s) && (data.inode == %s)' % (file_o.st_dev, file_o.st_ino)) 170 | else: 171 | bpf_text = bpf_text.replace('FILE_FILTER', '') 172 | 173 | # Force a universal allow if only '-A' is specified 174 | if not args.user and not args.exclude_user and not args.file_path and not args.exclude_file: 175 | bpf_text = bpf_text.replace('DEFAULT_ALLOW', '&& 0') 176 | 177 | elif args.deny: 178 | if args.user: 179 | bpf_text = bpf_text.replace('UID_FILTER', 180 | '&& (data.uid == %s)' % args.user) 181 | elif args.exclude_user: 182 | bpf_text = bpf_text.replace('UID_FILTER', 183 | '&& (data.uid != %s)' % args.exclude_user) 184 | else: 185 | bpf_text = bpf_text.replace('UID_FILTER', '') 186 | 187 | if args.file_path: 188 | file_o = os.lstat(args.file_path) 189 | bpf_text = bpf_text.replace('FILE_FILTER', 190 | '&& (data.dev == %s) && (data.inode == %s)' % (file_o.st_dev, file_o.st_ino)) 191 | elif args.exclude_file: 192 | file_o = os.lstat(args.exclude_file) 193 | bpf_text = bpf_text.replace('FILE_FILTER', 194 | '&& !((data.dev == %s) && (data.inode == %s))' % (file_o.st_dev, file_o.st_ino)) 195 | else: 196 | bpf_text = bpf_text.replace('FILE_FILTER', '') 197 | 198 | bpf_text = bpf_text.replace('DEFAULT_ALLOW', '') 199 | 200 | 201 | # Compiler the BPF program and attach it to the LSM hook 202 | b = BPF(text=bpf_text) 203 | 204 | 205 | def print_event(cpu, data, size): 206 | """ 207 | Print event data when a SUID binary is about to be 208 | executed. 209 | """ 210 | event = b["events"].event(data) 211 | printb(b"%s type=exsuid comm=%s uid=%d gid=%d pid=%d dev=%d inode=%lu mode=%o action=%s" % ( 212 | datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S"), 213 | event.comm, 214 | event.uid, 215 | event.gid, 216 | event.pid, 217 | event.dev, 218 | event.inode, 219 | event.mode, 220 | "allow" if event.allowed else "deny")) 221 | 222 | 223 | #setup callback function for the buffer 224 | b["events"].open_perf_buffer(print_event) 225 | 226 | 227 | # Poll for incoming events 228 | while 1: 229 | try: 230 | b.perf_buffer_poll() 231 | except KeyboardInterrupt: 232 | exit() 233 | -------------------------------------------------------------------------------- /mac_fileperms: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # mac_fileperms Restrict the addition of SUID and WOTH 4 | # bits to new and existing files. 5 | # 6 | # 10-Oct-2020 Billy Wilson Created this. 7 | 8 | from bcc import BPF 9 | from bcc.utils import printb 10 | import argparse 11 | import datetime 12 | import pwd 13 | 14 | 15 | def parse_uid(user): 16 | """ 17 | Check if valid UID or username is provided 18 | """ 19 | try: 20 | result = int(user) 21 | except ValueError: 22 | try: 23 | user_info = pwd.getpwnam(user) 24 | except KeyError: 25 | raise argparse.ArgumentTypeError( 26 | "{0!r} is not valid UID or user entry".format(user)) 27 | else: 28 | return user_info.pw_uid 29 | else: 30 | # Maybe validate if UID < 0 ? 31 | return result 32 | 33 | 34 | # Set up argument parser 35 | examples = """examples: 36 | ./mac_fileperms -A # Allow all SUID/WOTH permission bit additions 37 | ./mac_fileperms -A -u 1000 # Allow only UID 1000 to add SUID/WOTH permission bits 38 | ./mac_fileperms -A -U 1000 # Allow all UIDs except 1000 to add SUID/WOTH permission bits 39 | ./mac_fileperms -D # Deny all SUID/WOTH permission bit additions 40 | ./mac_fileperms -D -u 1000 # Deny only UID 1000 from adding SUID/WOTH permission bits 41 | ./mac_fileperms -D -U 1000 # Deny all UIDs except 1000 from adding SUID/WOTH permission bits 42 | """ 43 | 44 | parser = argparse.ArgumentParser( 45 | description="Dynamically load a MAC policy for SUID/WOTH permission bits", 46 | formatter_class=argparse.RawDescriptionHelpFormatter, 47 | epilog=examples) 48 | mode = parser.add_mutually_exclusive_group(required=True) 49 | mode.add_argument("-A", "--allow", action="store_true", 50 | help="define an allow policy") 51 | mode.add_argument("-D", "--deny", action="store_true", 52 | help="define a deny policy") 53 | user_control = parser.add_mutually_exclusive_group() 54 | user_control.add_argument("-u", "--user", type=parse_uid, metavar="USER", 55 | help="apply the policy to a specific user") 56 | user_control.add_argument("-U", "--exclude-user", type=parse_uid, metavar="EXCLUDED_USER", 57 | help="exclude a specific user from the policy") 58 | 59 | args = parser.parse_args() 60 | 61 | 62 | # BPF program (in C) 63 | bpf_text = """ 64 | #include 65 | #include 66 | #include 67 | 68 | #define __LOWER(x) (x & 0xffffffff) 69 | #define __UPPER(x) (x >> 32) 70 | 71 | /* 72 | * Create a data structure for collecting event data 73 | */ 74 | struct data_t { 75 | char comm[TASK_COMM_LEN]; 76 | u32 uid; 77 | u32 gid; 78 | u32 pid; 79 | unsigned short oldmode; 80 | unsigned short reqmode; 81 | unsigned short newmode; 82 | int umask; 83 | int allowed; 84 | }; 85 | 86 | /* 87 | * Create buffers for sending event data to userspace 88 | */ 89 | BPF_PERF_OUTPUT(creat_events); 90 | BPF_PERF_OUTPUT(chmod_events); 91 | 92 | /* 93 | * Attach to the "inode_create" LSM hook 94 | */ 95 | LSM_PROBE(inode_create, struct inode *dir, struct dentry *dentry, 96 | umode_t mode) { 97 | 98 | u64 gid_uid; 99 | u64 pid_tgid; 100 | int umask; 101 | int allowed; 102 | struct fs_struct *fs; 103 | struct task_struct *task; 104 | 105 | /* 106 | * Get the umask of the current task 107 | */ 108 | task = (struct task_struct *)bpf_get_current_task(); 109 | bpf_probe_read_kernel(&fs, sizeof(fs), &task->fs); 110 | bpf_probe_read_kernel(&umask, sizeof(umask), &fs->umask); 111 | 112 | /* 113 | * Check if SUID or world-writable bits will be set 114 | */ 115 | if ((mode & ~umask) & (S_ISUID | S_IWOTH)) { 116 | 117 | /* 118 | * Gather event data 119 | */ 120 | struct data_t data = {}; 121 | gid_uid = bpf_get_current_uid_gid(); 122 | pid_tgid = bpf_get_current_pid_tgid(); 123 | bpf_get_current_comm(&data.comm, sizeof(data.comm)); 124 | data.uid = __LOWER(gid_uid); 125 | data.gid = __UPPER(gid_uid); 126 | data.pid = __UPPER(pid_tgid); 127 | data.oldmode = dentry->d_inode->i_mode; 128 | data.reqmode = mode; 129 | data.umask = umask; 130 | data.newmode = mode & ~umask; 131 | 132 | /* 133 | * Optional filters (Populated by Python script) 134 | */ 135 | if ( 136 | 1 137 | UID_FILTER 138 | DEFAULT_ALLOW 139 | ) { 140 | data.allowed = 0; 141 | creat_events.perf_submit(ctx, &data, sizeof(data)); 142 | return -EPERM; 143 | } else { 144 | data.allowed = 1; 145 | creat_events.perf_submit(ctx, &data, sizeof(data)); 146 | } 147 | } 148 | return 0; 149 | } 150 | 151 | /* 152 | * Attach to the "path_chmod" LSM hook 153 | */ 154 | LSM_PROBE(path_chmod, const struct path *path, umode_t mode) { 155 | 156 | u64 gid_uid; 157 | u64 pid_tgid; 158 | int allowed; 159 | 160 | /* 161 | * Check if SUID or world-writable bits will be set 162 | */ 163 | if (mode & (S_ISUID | S_IWOTH)) { 164 | struct data_t data = {}; 165 | gid_uid = bpf_get_current_uid_gid(); 166 | pid_tgid = bpf_get_current_pid_tgid(); 167 | 168 | /* 169 | * Gather event data 170 | */ 171 | bpf_get_current_comm(&data.comm, sizeof(data.comm)); 172 | data.uid = __LOWER(gid_uid); 173 | data.gid = __UPPER(gid_uid); 174 | data.pid = __UPPER(pid_tgid); 175 | data.oldmode = path->dentry->d_inode->i_mode; 176 | data.newmode = mode; 177 | 178 | /* 179 | * Optional filters (Populated by Python script) 180 | */ 181 | if ( 182 | 1 183 | UID_FILTER 184 | DEFAULT_ALLOW 185 | ) { 186 | data.allowed = 0; 187 | chmod_events.perf_submit(ctx, &data, sizeof(data)); 188 | return -EPERM; 189 | } else { 190 | data.allowed = 1; 191 | chmod_events.perf_submit(ctx, &data, sizeof(data)); 192 | } 193 | } 194 | return 0; 195 | } 196 | """ 197 | 198 | 199 | # Populate filters in the BPF program, based on options passed to this script 200 | if args.user: 201 | bpf_text = bpf_text.replace('UID_FILTER', '&& (data.uid ALLOW_OR_DENY %s)' % args.user) 202 | elif args.exclude_user: 203 | bpf_text = bpf_text.replace('UID_FILTER', '&& (data.uid INVERSE %s)' % args.exclude_user) 204 | else: 205 | bpf_text = bpf_text.replace('UID_FILTER', '') 206 | 207 | if args.allow: 208 | bpf_text = bpf_text.replace('ALLOW_OR_DENY', '!=') 209 | bpf_text = bpf_text.replace('INVERSE', '==') 210 | 211 | #Force a universal allow if only -A is specified 212 | if not args.user and not args.exclude_user: 213 | bpf_text = bpf_text.replace('DEFAULT_ALLOW', '&& 0') 214 | 215 | elif args.deny: 216 | bpf_text = bpf_text.replace('ALLOW_OR_DENY', '==') 217 | bpf_text = bpf_text.replace('INVERSE', '!=') 218 | 219 | bpf_text = bpf_text.replace('DEFAULT_ALLOW', '') 220 | 221 | 222 | # Compile the BPF program and attach it to the LSM hooks 223 | b = BPF(text=bpf_text) 224 | 225 | 226 | def print_creat_event(cpu, data, size): 227 | """ 228 | Print event data when attempting to create an inode 229 | with SUID/WOTH bits. 230 | """ 231 | event = b["creat_events"].event(data) 232 | printb(b"%s type=create comm=%s uid=%d gid=%d pid=%d oldmode=%06o reqmode=%06o umask=%06o newmode=%06o action=%s" % ( 233 | datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S"), 234 | event.comm, 235 | event.uid, 236 | event.gid, 237 | event.pid, 238 | event.oldmode, 239 | event.reqmode, 240 | event.umask, 241 | event.newmode, 242 | "allow" if event.allowed else "deny")) 243 | 244 | 245 | def print_chmod_event(cpu, data, size): 246 | """ 247 | Print event data when attempting to add SUID/WOTH bits 248 | to an inode. 249 | """ 250 | event = b["creat_events"].event(data) 251 | printb(b"%s type=chmode comm=%s uid=%d gid=%d pid=%d oldmode=%06o newmode=%06o action=%s" % ( 252 | datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S"), 253 | event.comm, 254 | event.uid, 255 | event.gid, 256 | event.pid, 257 | event.oldmode, 258 | event.newmode, 259 | "allow" if event.allowed else "deny")) 260 | 261 | 262 | # Setup callback functions for each buffer 263 | b["creat_events"].open_perf_buffer(print_creat_event) 264 | b["chmod_events"].open_perf_buffer(print_chmod_event) 265 | 266 | 267 | # Poll for incoming events 268 | while 1: 269 | try: 270 | b.perf_buffer_poll() 271 | except KeyboardInterrupt: 272 | exit() 273 | -------------------------------------------------------------------------------- /mac_killtasks: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # mac_killtasks Restrict kill signals. 4 | # 5 | # 10-Oct-2020 Billy Wilson Created this. 6 | 7 | from bcc import BPF 8 | from bcc.utils import printb 9 | import argparse 10 | import datetime 11 | import os 12 | import pwd 13 | 14 | 15 | def parse_uid(user): 16 | """ 17 | Check if valid UID or username is provided 18 | """ 19 | try: 20 | result = int(user) 21 | except ValueError: 22 | try: 23 | user_info = pwd.getpwnam(user) 24 | except KeyError: 25 | raise argparse.ArgumentTypeError( 26 | "{0!r} is not valid UID or user entry".format(user)) 27 | else: 28 | return user_info.pw_uid 29 | else: 30 | # Maybe validate if UID < 0 ? 31 | return result 32 | 33 | 34 | # Set up argument parser 35 | examples = """examples: 36 | ./mac_killtasks -A # Allow all SIGTERM/SIGKILL signals 37 | ./mac_killtasks -A -u root # Only allow SIGTERM/SIGKILL signals from root 38 | ./mac_killtasks -D # Deny all SIGTERM/SIGKILL signals 39 | ./mac_killtasks -D -k # Deny all SIGTERM/SIGKILL signals, including from kernel processes (dangerous) 40 | ./mac_killtasks -D -t 3333 # Deny all SIGTERM/SIGKILL signals sent to PID 3333 41 | ./mac_killtasks -D -e -t 3333 # Deny all SIGTERM/SIGKILL signals sent to PID 3333, and protect mac_killtasks from termination 42 | ./mac_killtasks -D -p 1111 # Deny all SIGTERM/SIGKILL signals sent from PID 1111 43 | """ 44 | 45 | parser = argparse.ArgumentParser( 46 | description="Dynamically load a MAC policy for restricting kill signals", 47 | formatter_class=argparse.RawDescriptionHelpFormatter, 48 | epilog=examples) 49 | mode = parser.add_mutually_exclusive_group(required=True) 50 | mode.add_argument("-A", "--allow", action="store_true", 51 | help="define an allow policy") 52 | mode.add_argument("-D", "--deny", action="store_true", 53 | help="define a deny policy") 54 | parser.add_argument("-k", "--kernel", action="store_true", 55 | help="Apply the policy to kernel signals as well (dangerous)") 56 | parser.add_argument("-e", "--eternal", action="store_true", 57 | help="Block all kill signals against the process that loaded this policy") 58 | user_control = parser.add_mutually_exclusive_group() 59 | user_control.add_argument("-u", "--user", type=parse_uid, metavar="USER", 60 | help="apply the policy to a specific user") 61 | user_control.add_argument("-U", "--exclude-user", type=parse_uid, metavar="EXCLUDED_USER", 62 | help="exclude a specific user from the policy") 63 | pid_control = parser.add_mutually_exclusive_group() 64 | pid_control.add_argument("-p", "--source-pid", metavar="PID", 65 | help="apply the policy to a source pid") 66 | pid_control.add_argument("-P", "--exclude-source-pid", metavar="EXCLUDED_PID", 67 | help="exclude a source pid from the policy") 68 | target_control = parser.add_mutually_exclusive_group() 69 | target_control.add_argument("-t", "--target-pid", metavar="PID", 70 | help="apply the policy to a target pid") 71 | target_control.add_argument("-T", "--exclude-target-pid", metavar="EXCLUDED_PID", 72 | help="exclude a target pid from the policy") 73 | 74 | args = parser.parse_args() 75 | 76 | 77 | # BPF program (in C) 78 | bpf_text = """ 79 | #include 80 | #include 81 | #include 82 | #include 83 | 84 | #define __LOWER(x) (x & 0xffffffff) 85 | #define __UPPER(x) (x >> 32) 86 | 87 | /* 88 | * Create a data structure for collecting event data 89 | */ 90 | struct data_t { 91 | char comm[TASK_COMM_LEN]; 92 | u32 uid; 93 | u32 gid; 94 | u32 pid; 95 | u32 targetuid; 96 | u32 targetpid; 97 | int signo; 98 | int allowed; 99 | }; 100 | 101 | /* 102 | * Create a buffer for sending event data to userspace 103 | */ 104 | BPF_PERF_OUTPUT(events); 105 | 106 | /* 107 | * Attach to the "task_kill" LSM hook 108 | */ 109 | LSM_PROBE(task_kill, struct task_struct *p, struct kernel_siginfo *info, 110 | int sig, const struct cred *cred) { 111 | 112 | u64 gid_uid; 113 | u64 pid_tgid; 114 | int signo; 115 | 116 | /* 117 | * Check if the signal is SIGKILL or SIGTERM 118 | */ 119 | signo = info->si_signo; 120 | if (signo == SIGKILL || signo == SIGTERM) { 121 | 122 | /* 123 | * Gather event data 124 | */ 125 | struct data_t data = {}; 126 | u64 gid_uid = bpf_get_current_uid_gid(); 127 | u64 pid_tgid = bpf_get_current_pid_tgid(); 128 | bpf_get_current_comm(&data.comm, sizeof(data.comm)); 129 | data.uid = __LOWER(gid_uid); 130 | data.gid = __UPPER(gid_uid); 131 | data.pid = __UPPER(pid_tgid); 132 | data.targetuid = p->cred->uid.val; 133 | data.targetpid = p->pid; 134 | data.signo = signo; 135 | 136 | /* 137 | * Optional filters (Populated by Python script) 138 | */ 139 | KERNEL_FILTER 140 | ETERNAL_POLICY 141 | 142 | if ( 143 | 1 144 | UID_FILTER 145 | PID_FILTER 146 | TARGET_FILTER 147 | DEFAULT_ALLOW 148 | ) { 149 | data.allowed = 0; 150 | events.perf_submit(ctx, &data, sizeof(data)); 151 | return -EPERM; 152 | } else { 153 | data.allowed = 1; 154 | events.perf_submit(ctx, &data, sizeof(data)); 155 | } 156 | } 157 | return 0; 158 | } 159 | """ 160 | 161 | 162 | # Populate filters in the BPF program, based on options passed to this script 163 | if args.kernel: 164 | bpf_text = bpf_text.replace('KERNEL_FILTER', '') 165 | else: 166 | always_allow_kernel = """ 167 | /* 168 | * Allow all signals originating from the kernel 169 | */ 170 | if ((long)info == 1 || SI_FROMKERNEL(info)) { 171 | data.allowed = 1; 172 | events.perf_submit(ctx, &data, sizeof(data)); 173 | return 0; 174 | } 175 | """ 176 | bpf_text = bpf_text.replace('KERNEL_FILTER', always_allow_kernel) 177 | 178 | if args.eternal: 179 | eternal_policy = """ 180 | /* 181 | * Only allow the parent process of mac_killtasks to send SIGKILL/SIGTERM to it 182 | */ 183 | if (data.targetpid == %s && data.pid != %s) { 184 | data.allowed = 0; 185 | events.perf_submit(ctx, &data, sizeof(data)); 186 | return -EPERM; 187 | } 188 | """ % (os.getpid(), os.getppid()) 189 | bpf_text = bpf_text.replace('ETERNAL_POLICY', eternal_policy) 190 | else: 191 | bpf_text = bpf_text.replace('ETERNAL_POLICY', '') 192 | 193 | if args.user: 194 | bpf_text = bpf_text.replace('UID_FILTER', '&& (data.targetuid ALLOW_OR_DENY %s)' % args.user) 195 | elif args.exclude_user: 196 | bpf_text = bpf_text.replace('UID_FILTER', '&& (data.targetuid INVERSE %s)' % args.exclude_user) 197 | else: 198 | bpf_text = bpf_text.replace('UID_FILTER', '') 199 | 200 | if args.source_pid: 201 | bpf_text = bpf_text.replace('PID_FILTER', '&& (data.pid ALLOW_OR_DENY %s)' % args.source_pid) 202 | elif args.exclude_source_pid: 203 | bpf_text = bpf_text.replace('PID_FILTER', '&& (data.pid INVERSE %s)' % args.exclude_source_pid) 204 | else: 205 | bpf_text = bpf_text.replace('PID_FILTER', '') 206 | 207 | if args.target_pid: 208 | bpf_text = bpf_text.replace('TARGET_FILTER', '&& (data.targetpid ALLOW_OR_DENY %s)' % args.target_pid) 209 | elif args.exclude_target_pid: 210 | bpf_text = bpf_text.replace('TARGET_FILTER', '&& (data.targetpid INVERSE %s)' % args.exclude_target_pid) 211 | else: 212 | bpf_text = bpf_text.replace('TARGET_FILTER', '') 213 | 214 | if args.allow: 215 | # Force a univeral allow if only '-A' is specified 216 | if not args.user and not args.exclude_user and not args.source_pid and not args.exclude_source_pid \ 217 | and not args.target_pid and not args.exclude_target_pid: 218 | bpf_text = bpf_text.replace('DEFAULT_ALLOW', '&& 0') 219 | 220 | bpf_text = bpf_text.replace('ALLOW_OR_DENY', '!=') 221 | bpf_text = bpf_text.replace('INVERSE', '==') 222 | 223 | elif args.deny: 224 | bpf_text = bpf_text.replace('ALLOW_OR_DENY', '==') 225 | bpf_text = bpf_text.replace('INVERSE', '!=') 226 | 227 | bpf_text = bpf_text.replace('DEFAULT_ALLOW', '') 228 | 229 | # Compiler the BPF program and attach it to the LSM hook 230 | b = BPF(text=bpf_text) 231 | 232 | 233 | def print_event(cpu, data, size): 234 | """ 235 | Print event data when a kill signal is about to be 236 | sent. 237 | """ 238 | event = b["events"].event(data) 239 | printb(b"%s type=sgkill comm=%s uid=%d gid=%d pid=%d targetuid=%d targetpid=%d signo=%d action=%s" % ( 240 | datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S"), 241 | event.comm, 242 | event.uid, 243 | event.gid, 244 | event.pid, 245 | event.targetuid, 246 | event.targetpid, 247 | event.signo, 248 | "allow" if event.allowed else "deny")) 249 | 250 | 251 | #setup callback function for the buffer 252 | b["events"].open_perf_buffer(print_event) 253 | 254 | 255 | # Poll for incoming events 256 | while 1: 257 | try: 258 | b.perf_buffer_poll() 259 | except KeyboardInterrupt: 260 | exit() 261 | --------------------------------------------------------------------------------