| 1 | /** |
| 2 | * Enhanced Seccomp x86 Specific Code |
| 3 | * |
| 4 | * Copyright (c) 2012,2016 Red Hat <pmoore@redhat.com> |
| 5 | * Author: Paul Moore <paul@paul-moore.com> |
| 6 | */ |
| 7 | |
| 8 | /* |
| 9 | * This library is free software; you can redistribute it and/or modify it |
| 10 | * under the terms of version 2.1 of the GNU Lesser General Public License as |
| 11 | * published by the Free Software Foundation. |
| 12 | * |
| 13 | * This library is distributed in the hope that it will be useful, but WITHOUT |
| 14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License |
| 16 | * for more details. |
| 17 | * |
| 18 | * You should have received a copy of the GNU Lesser General Public License |
| 19 | * along with this library; if not, see <http://www.gnu.org/licenses>. |
| 20 | */ |
| 21 | |
| 22 | #include <stdlib.h> |
| 23 | #include <errno.h> |
| 24 | #include <string.h> |
| 25 | #include <linux/audit.h> |
| 26 | |
| 27 | #include "arch.h" |
| 28 | #include "arch-x86.h" |
| 29 | |
| 30 | /* x86 syscall numbers */ |
| 31 | #define __x86_NR_socketcall 102 |
| 32 | #define __x86_NR_ipc 117 |
| 33 | |
| 34 | const struct arch_def arch_def_x86 = { |
| 35 | .token = SCMP_ARCH_X86, |
| 36 | .token_bpf = AUDIT_ARCH_I386, |
| 37 | .size = ARCH_SIZE_32, |
| 38 | .endian = ARCH_ENDIAN_LITTLE, |
| 39 | .syscall_resolve_name = x86_syscall_resolve_name, |
| 40 | .syscall_resolve_num = x86_syscall_resolve_num, |
| 41 | .syscall_rewrite = x86_syscall_rewrite, |
| 42 | .rule_add = x86_rule_add, |
| 43 | }; |
| 44 | |
| 45 | /** |
| 46 | * Convert a multiplexed pseudo socket syscall into a direct syscall |
| 47 | * @param socketcall the multiplexed pseudo syscall number |
| 48 | * |
| 49 | * Return the related direct syscall number, __NR_SCMP_UNDEF is there is |
| 50 | * no related syscall, or __NR_SCMP_ERROR otherwise. |
| 51 | * |
| 52 | */ |
| 53 | int _x86_sock_demux(int socketcall) |
| 54 | { |
| 55 | switch (socketcall) { |
| 56 | case -101: |
| 57 | /* socket */ |
| 58 | return 359; |
| 59 | case -102: |
| 60 | /* bind */ |
| 61 | return 361; |
| 62 | case -103: |
| 63 | /* connect */ |
| 64 | return 362; |
| 65 | case -104: |
| 66 | /* listen */ |
| 67 | return 363; |
| 68 | case -105: |
| 69 | /* accept - not defined */ |
| 70 | return __NR_SCMP_UNDEF; |
| 71 | case -106: |
| 72 | /* getsockname */ |
| 73 | return 367; |
| 74 | case -107: |
| 75 | /* getpeername */ |
| 76 | return 368; |
| 77 | case -108: |
| 78 | /* socketpair */ |
| 79 | return 360; |
| 80 | case -109: |
| 81 | /* send - not defined */ |
| 82 | return __NR_SCMP_UNDEF; |
| 83 | case -110: |
| 84 | /* recv - not defined */ |
| 85 | return __NR_SCMP_UNDEF; |
| 86 | case -111: |
| 87 | /* sendto */ |
| 88 | return 369; |
| 89 | case -112: |
| 90 | /* recvfrom */ |
| 91 | return 371; |
| 92 | case -113: |
| 93 | /* shutdown */ |
| 94 | return 373; |
| 95 | case -114: |
| 96 | /* setsockopt */ |
| 97 | return 366; |
| 98 | case -115: |
| 99 | /* getsockopt */ |
| 100 | return 365; |
| 101 | case -116: |
| 102 | /* sendmsg */ |
| 103 | return 370; |
| 104 | case -117: |
| 105 | /* recvmsg */ |
| 106 | return 372; |
| 107 | case -118: |
| 108 | /* accept4 */ |
| 109 | return 364; |
| 110 | case -119: |
| 111 | /* recvmmsg */ |
| 112 | return 337; |
| 113 | case -120: |
| 114 | /* sendmmsg */ |
| 115 | return 345; |
| 116 | } |
| 117 | |
| 118 | return __NR_SCMP_ERROR; |
| 119 | } |
| 120 | |
| 121 | /** |
| 122 | * Convert a direct socket syscall into multiplexed pseudo socket syscall |
| 123 | * @param syscall the direct syscall |
| 124 | * |
| 125 | * Return the related multiplexed pseduo syscall number, __NR_SCMP_UNDEF is |
| 126 | * there is no related pseudo syscall, or __NR_SCMP_ERROR otherwise. |
| 127 | * |
| 128 | */ |
| 129 | int _x86_sock_mux(int syscall) |
| 130 | { |
| 131 | switch (syscall) { |
| 132 | case 337: |
| 133 | /* recvmmsg */ |
| 134 | return -119; |
| 135 | case 345: |
| 136 | /* sendmmsg */ |
| 137 | return -120; |
| 138 | case 359: |
| 139 | /* socket */ |
| 140 | return -101; |
| 141 | case 360: |
| 142 | /* socketpair */ |
| 143 | return -108; |
| 144 | case 361: |
| 145 | /* bind */ |
| 146 | return -102; |
| 147 | case 362: |
| 148 | /* connect */ |
| 149 | return -103; |
| 150 | case 363: |
| 151 | /* listen */ |
| 152 | return -104; |
| 153 | case 364: |
| 154 | /* accept4 */ |
| 155 | return -118; |
| 156 | case 365: |
| 157 | /* getsockopt */ |
| 158 | return -115; |
| 159 | case 366: |
| 160 | /* setsockopt */ |
| 161 | return -114; |
| 162 | case 367: |
| 163 | /* getsockname */ |
| 164 | return -106; |
| 165 | case 368: |
| 166 | /* getpeername */ |
| 167 | return -107; |
| 168 | case 369: |
| 169 | /* sendto */ |
| 170 | return -111; |
| 171 | case 370: |
| 172 | /* sendmsg */ |
| 173 | return -116; |
| 174 | case 371: |
| 175 | /* recvfrom */ |
| 176 | return -112; |
| 177 | case 372: |
| 178 | /* recvmsg */ |
| 179 | return -117; |
| 180 | case 373: |
| 181 | /* shutdown */ |
| 182 | return -113; |
| 183 | } |
| 184 | |
| 185 | return __NR_SCMP_ERROR; |
| 186 | } |
| 187 | |
| 188 | /** |
| 189 | * Rewrite a syscall value to match the architecture |
| 190 | * @param syscall the syscall number |
| 191 | * |
| 192 | * Syscalls can vary across different architectures so this function rewrites |
| 193 | * the syscall into the correct value for the specified architecture. Returns |
| 194 | * zero on success, negative values on failure. |
| 195 | * |
| 196 | */ |
| 197 | int x86_syscall_rewrite(int *syscall) |
| 198 | { |
| 199 | int sys = *syscall; |
| 200 | |
| 201 | if (sys <= -100 && sys >= -120) |
| 202 | *syscall = __x86_NR_socketcall; |
| 203 | else if (sys <= -200 && sys >= -224) |
| 204 | *syscall = __x86_NR_ipc; |
| 205 | else if (sys < 0) |
| 206 | return -EDOM; |
| 207 | |
| 208 | return 0; |
| 209 | } |
| 210 | |
| 211 | /** |
| 212 | * add a new rule to the x86 seccomp filter |
| 213 | * @param col the filter collection |
| 214 | * @param db the seccomp filter db |
| 215 | * @param strict the strict flag |
| 216 | * @param rule the filter rule |
| 217 | * |
| 218 | * This function adds a new syscall filter to the seccomp filter db, making any |
| 219 | * necessary adjustments for the x86 ABI. Returns zero on success, negative |
| 220 | * values on failure. |
| 221 | * |
| 222 | */ |
| 223 | int x86_rule_add(struct db_filter_col *col, struct db_filter *db, bool strict, |
| 224 | struct db_api_rule_list *rule) |
| 225 | { |
| 226 | int rc; |
| 227 | unsigned int iter; |
| 228 | size_t args_size; |
| 229 | int sys = rule->syscall; |
| 230 | int sys_a, sys_b; |
| 231 | struct db_api_rule_list *rule_a, *rule_b; |
| 232 | |
| 233 | if ((sys <= -100 && sys >= -120) || (sys >= 359 && sys <= 373)) { |
| 234 | /* (-100 to -120) : multiplexed socket syscalls |
| 235 | (359 to 373) : direct socket syscalls, Linux 4.3+ */ |
| 236 | |
| 237 | /* strict check for the multiplexed socket syscalls */ |
| 238 | for (iter = 0; iter < rule->args_cnt; iter++) { |
| 239 | if ((rule->args[iter].valid != 0) && (strict)) |
| 240 | return -EINVAL; |
| 241 | } |
| 242 | |
| 243 | /* determine both the muxed and direct syscall numbers */ |
| 244 | if (sys > 0) { |
| 245 | sys_a = _x86_sock_mux(sys); |
| 246 | if (sys_a == __NR_SCMP_ERROR) |
| 247 | return __NR_SCMP_ERROR; |
| 248 | sys_b = sys; |
| 249 | } else { |
| 250 | sys_a = sys; |
| 251 | sys_b = _x86_sock_demux(sys); |
| 252 | if (sys_b == __NR_SCMP_ERROR) |
| 253 | return __NR_SCMP_ERROR; |
| 254 | } |
| 255 | |
| 256 | /* use rule_a for the multiplexed syscall and use rule_b for |
| 257 | * the direct wired syscall */ |
| 258 | |
| 259 | if (sys_a == __NR_SCMP_UNDEF) { |
| 260 | rule_a = NULL; |
| 261 | rule_b = rule; |
| 262 | } else if (sys_b == __NR_SCMP_UNDEF) { |
| 263 | rule_a = rule; |
| 264 | rule_b = NULL; |
| 265 | } else { |
| 266 | /* need two rules, dup the first and link together */ |
| 267 | rule_a = rule; |
| 268 | rule_b = malloc(sizeof(*rule_b)); |
| 269 | if (rule_b == NULL) |
| 270 | return -ENOMEM; |
| 271 | args_size = sizeof(*rule_b->args) * rule_a->args_cnt; |
| 272 | rule_b->args = malloc(args_size); |
| 273 | if (rule_b->args == NULL) { |
| 274 | free(rule_b); |
| 275 | return -ENOMEM; |
| 276 | } |
| 277 | rule_b->action = rule_a->action; |
| 278 | rule_b->syscall = rule_a->syscall; |
| 279 | rule_b->args_cnt = rule_a->args_cnt; |
| 280 | memcpy(rule_b->args, rule_a->args, args_size); |
| 281 | rule_b->prev = rule_a; |
| 282 | rule_b->next = NULL; |
| 283 | rule_a->next = rule_b; |
| 284 | } |
| 285 | |
| 286 | /* multiplexed socket syscalls */ |
| 287 | if (rule_a != NULL) { |
| 288 | rule_a->syscall = __x86_NR_socketcall; |
| 289 | rule_a->args[0].arg = 0; |
| 290 | rule_a->args[0].op = SCMP_CMP_EQ; |
| 291 | rule_a->args[0].mask = DATUM_MAX; |
| 292 | rule_a->args[0].datum = (-sys_a) % 100; |
| 293 | rule_a->args[0].valid = 1; |
| 294 | } |
| 295 | |
| 296 | /* direct wired socket syscalls */ |
| 297 | if (rule_b != NULL) |
| 298 | rule_b->syscall = sys_b; |
| 299 | |
| 300 | /* add the rules as a single transaction */ |
| 301 | rc = db_col_transaction_start(col); |
| 302 | if (rc < 0) |
| 303 | return rc; |
| 304 | if (rule_a != NULL) { |
| 305 | rc = db_rule_add(db, rule_a); |
| 306 | if (rc < 0) |
| 307 | goto fail_transaction; |
| 308 | } |
| 309 | if (rule_b != NULL) { |
| 310 | rc = db_rule_add(db, rule_b); |
| 311 | if (rc < 0) |
| 312 | goto fail_transaction; |
| 313 | } |
| 314 | db_col_transaction_commit(col); |
| 315 | } else if (sys <= -200 && sys >= -224) { |
| 316 | /* multiplexed ipc syscalls */ |
| 317 | for (iter = 0; iter < ARG_COUNT_MAX; iter++) { |
| 318 | if ((rule->args[iter].valid != 0) && (strict)) |
| 319 | return -EINVAL; |
| 320 | } |
| 321 | rule->args[0].arg = 0; |
| 322 | rule->args[0].op = SCMP_CMP_EQ; |
| 323 | rule->args[0].mask = DATUM_MAX; |
| 324 | rule->args[0].datum = abs(sys) % 200; |
| 325 | rule->args[0].valid = 1; |
| 326 | rule->syscall = __x86_NR_ipc; |
| 327 | |
| 328 | rc = db_rule_add(db, rule); |
| 329 | if (rc < 0) |
| 330 | return rc; |
| 331 | } else if (sys >= 0) { |
| 332 | /* normal syscall processing */ |
| 333 | rc = db_rule_add(db, rule); |
| 334 | if (rc < 0) |
| 335 | return rc; |
| 336 | } else if (strict) |
| 337 | return -EDOM; |
| 338 | |
| 339 | return 0; |
| 340 | |
| 341 | fail_transaction: |
| 342 | db_col_transaction_abort(col); |
| 343 | return rc; |
| 344 | } |