github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snap-seccomp/main.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017-2019 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package main 21 22 //#cgo CFLAGS: -D_FILE_OFFSET_BITS=64 23 //#cgo pkg-config: libseccomp 24 //#cgo LDFLAGS: 25 // 26 //#include <asm/ioctls.h> 27 //#include <ctype.h> 28 //#include <errno.h> 29 //#include <linux/can.h> 30 //#include <linux/netlink.h> 31 //#include <sched.h> 32 //#include <search.h> 33 //#include <stdbool.h> 34 //#include <stdio.h> 35 //#include <stdlib.h> 36 //#include <string.h> 37 //#include <sys/prctl.h> 38 //#include <sys/quota.h> 39 //#include <sys/resource.h> 40 //#include <sys/socket.h> 41 //#include <sys/stat.h> 42 //#include <sys/types.h> 43 //#include <sys/utsname.h> 44 //#include <sys/ptrace.h> 45 //#include <termios.h> 46 //#include <unistd.h> 47 // //The XFS interface requires a 64 bit file system interface 48 // //but we don't want to leak this anywhere else if not globally 49 // //defined. 50 //#ifndef _FILE_OFFSET_BITS 51 //#define _FILE_OFFSET_BITS 64 52 //#include <xfs/xqm.h> 53 //#undef _FILE_OFFSET_BITS 54 //#else 55 //#include <xfs/xqm.h> 56 //#endif 57 //#include <seccomp.h> 58 //#include <linux/sched.h> 59 //#include <linux/seccomp.h> 60 //#include <arpa/inet.h> 61 // 62 //#ifndef AF_IB 63 //#define AF_IB 27 64 //#define PF_IB AF_IB 65 //#endif // AF_IB 66 // 67 //#ifndef AF_MPLS 68 //#define AF_MPLS 28 69 //#define PF_MPLS AF_MPLS 70 //#endif // AF_MPLS 71 // 72 // // https://github.com/sctplab/usrsctp/blob/master/usrsctplib/usrsctp.h 73 //#ifndef AF_CONN 74 //#define AF_CONN 123 75 //#define PF_CONN AF_CONN 76 //#endif // AF_CONN 77 // 78 //#ifndef PR_CAP_AMBIENT 79 //#define PR_CAP_AMBIENT 47 80 //#define PR_CAP_AMBIENT_IS_SET 1 81 //#define PR_CAP_AMBIENT_RAISE 2 82 //#define PR_CAP_AMBIENT_LOWER 3 83 //#define PR_CAP_AMBIENT_CLEAR_ALL 4 84 //#endif // PR_CAP_AMBIENT 85 // 86 //#ifndef PR_SET_THP_DISABLE 87 //#define PR_SET_THP_DISABLE 41 88 //#endif // PR_SET_THP_DISABLE 89 //#ifndef PR_GET_THP_DISABLE 90 //#define PR_GET_THP_DISABLE 42 91 //#endif // PR_GET_THP_DISABLE 92 // 93 //#ifndef PR_MPX_ENABLE_MANAGEMENT 94 //#define PR_MPX_ENABLE_MANAGEMENT 43 95 //#endif 96 // 97 //#ifndef PR_MPX_DISABLE_MANAGEMENT 98 //#define PR_MPX_DISABLE_MANAGEMENT 44 99 //#endif 100 // 101 // //FIXME: ARCH_BAD is defined as ~0 in libseccomp internally, however 102 // // this leads to a build failure on 14.04. the important part 103 // // is that its an invalid id for libseccomp. 104 // 105 //#define ARCH_BAD 0x7FFFFFFF 106 //#ifndef SCMP_ARCH_AARCH64 107 //#define SCMP_ARCH_AARCH64 ARCH_BAD 108 //#endif 109 // 110 //#ifndef SCMP_ARCH_PPC 111 //#define SCMP_ARCH_PPC ARCH_BAD 112 //#endif 113 // 114 //#ifndef SCMP_ARCH_PPC64LE 115 //#define SCMP_ARCH_PPC64LE ARCH_BAD 116 //#endif 117 // 118 //#ifndef SCMP_ARCH_PPC64 119 //#define SCMP_ARCH_PPC64 ARCH_BAD 120 //#endif 121 // 122 //#ifndef SCMP_ARCH_S390X 123 //#define SCMP_ARCH_S390X ARCH_BAD 124 //#endif 125 // 126 //#ifndef SECCOMP_RET_LOG 127 //#define SECCOMP_RET_LOG 0x7ffc0000U 128 //#endif 129 // 130 //typedef struct seccomp_data kernel_seccomp_data; 131 // 132 //__u32 htot32(__u32 arch, __u32 val) 133 //{ 134 // if (arch & __AUDIT_ARCH_LE) 135 // return htole32(val); 136 // else 137 // return htobe32(val); 138 //} 139 // 140 //__u64 htot64(__u32 arch, __u64 val) 141 //{ 142 // if (arch & __AUDIT_ARCH_LE) 143 // return htole64(val); 144 // else 145 // return htobe64(val); 146 //} 147 // 148 // /* Define missing ptrace constants. They are available on some architectures 149 // only but the missing values are not reused on architectures that lack them. 150 // As such we can simply define the missing pair and have a simpler cross-arch 151 // code to support. */ 152 // 153 // #ifndef PTRACE_GETREGS 154 // #define PTRACE_GETREGS 12 155 // #endif 156 // #ifndef PTRACE_SETREGS 157 // #define PTRACE_SETREGS 13 158 // #endif 159 // #ifndef PTRACE_GETFPREGS 160 // #define PTRACE_GETFPREGS 14 161 // #endif 162 // #ifndef PTRACE_SETFPREGS 163 // #define PTRACE_SETFPREGS 15 164 // #endif 165 // #ifndef PTRACE_GETFPXREGS 166 // #define PTRACE_GETFPXREGS 18 167 // #endif 168 // #ifndef PTRACE_SETFPXREGS 169 // #define PTRACE_SETFPXREGS 19 170 // #endif 171 import "C" 172 173 import ( 174 "bufio" 175 "bytes" 176 "fmt" 177 "io/ioutil" 178 "os" 179 "strconv" 180 "strings" 181 "syscall" 182 183 // FIXME: we want github.com/seccomp/libseccomp-golang but that will not work with trusty because libseccomp-golang checks for the seccomp version and errors if it find one < 2.2.0 184 "github.com/mvo5/libseccomp-golang" 185 186 "github.com/snapcore/snapd/arch" 187 "github.com/snapcore/snapd/osutil" 188 ) 189 190 // libseccomp maximum per ARG_COUNT_MAX in src/arch.h 191 const ScArgsMaxlength = 6 192 193 var seccompResolver = map[string]uint64{ 194 // man 2 socket - domain and man 5 apparmor.d. AF_ and PF_ are 195 // synonymous in the kernel and can be used interchangeably in 196 // policy (ie, if use AF_UNIX, don't need a corresponding PF_UNIX 197 // rule). See include/linux/socket.h 198 "AF_UNIX": syscall.AF_UNIX, 199 "PF_UNIX": C.PF_UNIX, 200 "AF_LOCAL": syscall.AF_LOCAL, 201 "PF_LOCAL": C.PF_LOCAL, 202 "AF_INET": syscall.AF_INET, 203 "PF_INET": C.PF_INET, 204 "AF_INET6": syscall.AF_INET6, 205 "PF_INET6": C.PF_INET6, 206 "AF_IPX": syscall.AF_IPX, 207 "PF_IPX": C.PF_IPX, 208 "AF_NETLINK": syscall.AF_NETLINK, 209 "PF_NETLINK": C.PF_NETLINK, 210 "AF_X25": syscall.AF_X25, 211 "PF_X25": C.PF_X25, 212 "AF_AX25": syscall.AF_AX25, 213 "PF_AX25": C.PF_AX25, 214 "AF_ATMPVC": syscall.AF_ATMPVC, 215 "PF_ATMPVC": C.PF_ATMPVC, 216 "AF_APPLETALK": syscall.AF_APPLETALK, 217 "PF_APPLETALK": C.PF_APPLETALK, 218 "AF_PACKET": syscall.AF_PACKET, 219 "PF_PACKET": C.PF_PACKET, 220 "AF_ALG": syscall.AF_ALG, 221 "PF_ALG": C.PF_ALG, 222 "AF_BRIDGE": syscall.AF_BRIDGE, 223 "PF_BRIDGE": C.PF_BRIDGE, 224 "AF_NETROM": syscall.AF_NETROM, 225 "PF_NETROM": C.PF_NETROM, 226 "AF_ROSE": syscall.AF_ROSE, 227 "PF_ROSE": C.PF_ROSE, 228 "AF_NETBEUI": syscall.AF_NETBEUI, 229 "PF_NETBEUI": C.PF_NETBEUI, 230 "AF_SECURITY": syscall.AF_SECURITY, 231 "PF_SECURITY": C.PF_SECURITY, 232 "AF_KEY": syscall.AF_KEY, 233 "PF_KEY": C.PF_KEY, 234 "AF_ASH": syscall.AF_ASH, 235 "PF_ASH": C.PF_ASH, 236 "AF_ECONET": syscall.AF_ECONET, 237 "PF_ECONET": C.PF_ECONET, 238 "AF_SNA": syscall.AF_SNA, 239 "PF_SNA": C.PF_SNA, 240 "AF_IRDA": syscall.AF_IRDA, 241 "PF_IRDA": C.PF_IRDA, 242 "AF_PPPOX": syscall.AF_PPPOX, 243 "PF_PPPOX": C.PF_PPPOX, 244 "AF_WANPIPE": syscall.AF_WANPIPE, 245 "PF_WANPIPE": C.PF_WANPIPE, 246 "AF_BLUETOOTH": syscall.AF_BLUETOOTH, 247 "PF_BLUETOOTH": C.PF_BLUETOOTH, 248 "AF_RDS": syscall.AF_RDS, 249 "PF_RDS": C.PF_RDS, 250 "AF_LLC": syscall.AF_LLC, 251 "PF_LLC": C.PF_LLC, 252 "AF_TIPC": syscall.AF_TIPC, 253 "PF_TIPC": C.PF_TIPC, 254 "AF_IUCV": syscall.AF_IUCV, 255 "PF_IUCV": C.PF_IUCV, 256 "AF_RXRPC": syscall.AF_RXRPC, 257 "PF_RXRPC": C.PF_RXRPC, 258 "AF_ISDN": syscall.AF_ISDN, 259 "PF_ISDN": C.PF_ISDN, 260 "AF_PHONET": syscall.AF_PHONET, 261 "PF_PHONET": C.PF_PHONET, 262 "AF_IEEE802154": syscall.AF_IEEE802154, 263 "PF_IEEE802154": C.PF_IEEE802154, 264 "AF_CAIF": syscall.AF_CAIF, 265 "PF_CAIF": C.AF_CAIF, 266 "AF_NFC": C.AF_NFC, 267 "PF_NFC": C.PF_NFC, 268 "AF_VSOCK": C.AF_VSOCK, 269 "PF_VSOCK": C.PF_VSOCK, 270 // may not be defined in socket.h yet 271 "AF_IB": C.AF_IB, // 27 272 "PF_IB": C.PF_IB, 273 "AF_MPLS": C.AF_MPLS, // 28 274 "PF_MPLS": C.PF_MPLS, 275 "AF_CAN": syscall.AF_CAN, 276 "PF_CAN": C.PF_CAN, 277 "AF_CONN": C.AF_CONN, // 123 278 "PF_CONN": C.PF_CONN, 279 280 // man 2 socket - type 281 "SOCK_STREAM": syscall.SOCK_STREAM, 282 "SOCK_DGRAM": syscall.SOCK_DGRAM, 283 "SOCK_SEQPACKET": syscall.SOCK_SEQPACKET, 284 "SOCK_RAW": syscall.SOCK_RAW, 285 "SOCK_RDM": syscall.SOCK_RDM, 286 "SOCK_PACKET": syscall.SOCK_PACKET, 287 288 // man 2 prctl 289 "PR_CAP_AMBIENT": C.PR_CAP_AMBIENT, 290 "PR_CAP_AMBIENT_RAISE": C.PR_CAP_AMBIENT_RAISE, 291 "PR_CAP_AMBIENT_LOWER": C.PR_CAP_AMBIENT_LOWER, 292 "PR_CAP_AMBIENT_IS_SET": C.PR_CAP_AMBIENT_IS_SET, 293 "PR_CAP_AMBIENT_CLEAR_ALL": C.PR_CAP_AMBIENT_CLEAR_ALL, 294 "PR_CAPBSET_READ": C.PR_CAPBSET_READ, 295 "PR_CAPBSET_DROP": C.PR_CAPBSET_DROP, 296 "PR_SET_CHILD_SUBREAPER": C.PR_SET_CHILD_SUBREAPER, 297 "PR_GET_CHILD_SUBREAPER": C.PR_GET_CHILD_SUBREAPER, 298 "PR_SET_DUMPABLE": C.PR_SET_DUMPABLE, 299 "PR_GET_DUMPABLE": C.PR_GET_DUMPABLE, 300 "PR_SET_ENDIAN": C.PR_SET_ENDIAN, 301 "PR_GET_ENDIAN": C.PR_GET_ENDIAN, 302 "PR_SET_FPEMU": C.PR_SET_FPEMU, 303 "PR_GET_FPEMU": C.PR_GET_FPEMU, 304 "PR_SET_FPEXC": C.PR_SET_FPEXC, 305 "PR_GET_FPEXC": C.PR_GET_FPEXC, 306 "PR_SET_KEEPCAPS": C.PR_SET_KEEPCAPS, 307 "PR_GET_KEEPCAPS": C.PR_GET_KEEPCAPS, 308 "PR_MCE_KILL": C.PR_MCE_KILL, 309 "PR_MCE_KILL_GET": C.PR_MCE_KILL_GET, 310 "PR_SET_MM": C.PR_SET_MM, 311 "PR_SET_MM_START_CODE": C.PR_SET_MM_START_CODE, 312 "PR_SET_MM_END_CODE": C.PR_SET_MM_END_CODE, 313 "PR_SET_MM_START_DATA": C.PR_SET_MM_START_DATA, 314 "PR_SET_MM_END_DATA": C.PR_SET_MM_END_DATA, 315 "PR_SET_MM_START_STACK": C.PR_SET_MM_START_STACK, 316 "PR_SET_MM_START_BRK": C.PR_SET_MM_START_BRK, 317 "PR_SET_MM_BRK": C.PR_SET_MM_BRK, 318 "PR_SET_MM_ARG_START": C.PR_SET_MM_ARG_START, 319 "PR_SET_MM_ARG_END": C.PR_SET_MM_ARG_END, 320 "PR_SET_MM_ENV_START": C.PR_SET_MM_ENV_START, 321 "PR_SET_MM_ENV_END": C.PR_SET_MM_ENV_END, 322 "PR_SET_MM_AUXV": C.PR_SET_MM_AUXV, 323 "PR_SET_MM_EXE_FILE": C.PR_SET_MM_EXE_FILE, 324 "PR_MPX_ENABLE_MANAGEMENT": C.PR_MPX_ENABLE_MANAGEMENT, 325 "PR_MPX_DISABLE_MANAGEMENT": C.PR_MPX_DISABLE_MANAGEMENT, 326 "PR_SET_NAME": C.PR_SET_NAME, 327 "PR_GET_NAME": C.PR_GET_NAME, 328 "PR_SET_NO_NEW_PRIVS": C.PR_SET_NO_NEW_PRIVS, 329 "PR_GET_NO_NEW_PRIVS": C.PR_GET_NO_NEW_PRIVS, 330 "PR_SET_PDEATHSIG": C.PR_SET_PDEATHSIG, 331 "PR_GET_PDEATHSIG": C.PR_GET_PDEATHSIG, 332 "PR_SET_PTRACER": C.PR_SET_PTRACER, 333 "PR_SET_SECCOMP": C.PR_SET_SECCOMP, 334 "PR_GET_SECCOMP": C.PR_GET_SECCOMP, 335 "PR_SET_SECUREBITS": C.PR_SET_SECUREBITS, 336 "PR_GET_SECUREBITS": C.PR_GET_SECUREBITS, 337 "PR_SET_THP_DISABLE": C.PR_SET_THP_DISABLE, 338 "PR_TASK_PERF_EVENTS_DISABLE": C.PR_TASK_PERF_EVENTS_DISABLE, 339 "PR_TASK_PERF_EVENTS_ENABLE": C.PR_TASK_PERF_EVENTS_ENABLE, 340 "PR_GET_THP_DISABLE": C.PR_GET_THP_DISABLE, 341 "PR_GET_TID_ADDRESS": C.PR_GET_TID_ADDRESS, 342 "PR_SET_TIMERSLACK": C.PR_SET_TIMERSLACK, 343 "PR_GET_TIMERSLACK": C.PR_GET_TIMERSLACK, 344 "PR_SET_TIMING": C.PR_SET_TIMING, 345 "PR_GET_TIMING": C.PR_GET_TIMING, 346 "PR_SET_TSC": C.PR_SET_TSC, 347 "PR_GET_TSC": C.PR_GET_TSC, 348 "PR_SET_UNALIGN": C.PR_SET_UNALIGN, 349 "PR_GET_UNALIGN": C.PR_GET_UNALIGN, 350 351 // man 2 getpriority 352 "PRIO_PROCESS": syscall.PRIO_PROCESS, 353 "PRIO_PGRP": syscall.PRIO_PGRP, 354 "PRIO_USER": syscall.PRIO_USER, 355 356 // man 2 setns 357 "CLONE_NEWIPC": syscall.CLONE_NEWIPC, 358 "CLONE_NEWNET": syscall.CLONE_NEWNET, 359 "CLONE_NEWNS": syscall.CLONE_NEWNS, 360 "CLONE_NEWPID": syscall.CLONE_NEWPID, 361 "CLONE_NEWUSER": syscall.CLONE_NEWUSER, 362 "CLONE_NEWUTS": syscall.CLONE_NEWUTS, 363 364 // man 4 tty_ioctl 365 "TIOCSTI": syscall.TIOCSTI, 366 367 // man 2 quotactl (with what Linux supports) 368 "Q_SYNC": C.Q_SYNC, 369 "Q_QUOTAON": C.Q_QUOTAON, 370 "Q_QUOTAOFF": C.Q_QUOTAOFF, 371 "Q_GETFMT": C.Q_GETFMT, 372 "Q_GETINFO": C.Q_GETINFO, 373 "Q_SETINFO": C.Q_SETINFO, 374 "Q_GETQUOTA": C.Q_GETQUOTA, 375 "Q_SETQUOTA": C.Q_SETQUOTA, 376 "Q_XQUOTAON": C.Q_XQUOTAON, 377 "Q_XQUOTAOFF": C.Q_XQUOTAOFF, 378 "Q_XGETQUOTA": C.Q_XGETQUOTA, 379 "Q_XSETQLIM": C.Q_XSETQLIM, 380 "Q_XGETQSTAT": C.Q_XGETQSTAT, 381 "Q_XQUOTARM": C.Q_XQUOTARM, 382 383 // man 2 mknod 384 "S_IFREG": syscall.S_IFREG, 385 "S_IFCHR": syscall.S_IFCHR, 386 "S_IFBLK": syscall.S_IFBLK, 387 "S_IFIFO": syscall.S_IFIFO, 388 "S_IFSOCK": syscall.S_IFSOCK, 389 390 // man 7 netlink (uapi/linux/netlink.h) 391 "NETLINK_ROUTE": syscall.NETLINK_ROUTE, 392 "NETLINK_USERSOCK": syscall.NETLINK_USERSOCK, 393 "NETLINK_FIREWALL": syscall.NETLINK_FIREWALL, 394 "NETLINK_SOCK_DIAG": C.NETLINK_SOCK_DIAG, 395 "NETLINK_NFLOG": syscall.NETLINK_NFLOG, 396 "NETLINK_XFRM": syscall.NETLINK_XFRM, 397 "NETLINK_SELINUX": syscall.NETLINK_SELINUX, 398 "NETLINK_ISCSI": syscall.NETLINK_ISCSI, 399 "NETLINK_AUDIT": syscall.NETLINK_AUDIT, 400 "NETLINK_FIB_LOOKUP": syscall.NETLINK_FIB_LOOKUP, 401 "NETLINK_CONNECTOR": syscall.NETLINK_CONNECTOR, 402 "NETLINK_NETFILTER": syscall.NETLINK_NETFILTER, 403 "NETLINK_IP6_FW": syscall.NETLINK_IP6_FW, 404 "NETLINK_DNRTMSG": syscall.NETLINK_DNRTMSG, 405 "NETLINK_KOBJECT_UEVENT": syscall.NETLINK_KOBJECT_UEVENT, 406 "NETLINK_GENERIC": syscall.NETLINK_GENERIC, 407 "NETLINK_SCSITRANSPORT": syscall.NETLINK_SCSITRANSPORT, 408 "NETLINK_ECRYPTFS": syscall.NETLINK_ECRYPTFS, 409 "NETLINK_RDMA": C.NETLINK_RDMA, 410 "NETLINK_CRYPTO": C.NETLINK_CRYPTO, 411 "NETLINK_INET_DIAG": C.NETLINK_INET_DIAG, // synonymous with NETLINK_SOCK_DIAG 412 413 // man 2 ptrace 414 "PTRACE_ATTACH": C.PTRACE_ATTACH, 415 "PTRACE_DETACH": C.PTRACE_DETACH, 416 "PTRACE_GETREGS": C.PTRACE_GETREGS, 417 "PTRACE_GETFPREGS": C.PTRACE_GETFPREGS, 418 "PTRACE_GETFPXREGS": C.PTRACE_GETFPXREGS, 419 "PTRACE_GETREGSET": C.PTRACE_GETREGSET, 420 "PTRACE_PEEKDATA": C.PTRACE_PEEKDATA, 421 // <linux/ptrace.h> and <sys/ptrace.h> have different spellings for PEEKUS{,E}R 422 "PTRACE_PEEKUSR": C.PTRACE_PEEKUSER, 423 "PTRACE_PEEKUSER": C.PTRACE_PEEKUSER, 424 "PTRACE_CONT": C.PTRACE_CONT, 425 } 426 427 // DpkgArchToScmpArch takes a dpkg architecture and converts it to 428 // the seccomp.ScmpArch as used in the libseccomp-golang library 429 func DpkgArchToScmpArch(dpkgArch string) seccomp.ScmpArch { 430 switch dpkgArch { 431 case "amd64": 432 return seccomp.ArchAMD64 433 case "arm64": 434 return seccomp.ArchARM64 435 case "armhf": 436 return seccomp.ArchARM 437 case "i386": 438 return seccomp.ArchX86 439 case "powerpc": 440 return seccomp.ArchPPC 441 case "ppc64": 442 return seccomp.ArchPPC64 443 case "ppc64el": 444 return seccomp.ArchPPC64LE 445 case "s390x": 446 return seccomp.ArchS390X 447 } 448 panic(fmt.Sprintf("cannot map dpkg arch %q to a seccomp arch", dpkgArch)) 449 } 450 451 // important for unit testing 452 type SeccompData C.kernel_seccomp_data 453 454 func (sc *SeccompData) SetNr(nr seccomp.ScmpSyscall) { 455 sc.nr = C.int(C.htot32(C.__u32(sc.arch), C.__u32(nr))) 456 } 457 func (sc *SeccompData) SetArch(arch uint32) { 458 sc.arch = C.htot32(C.__u32(arch), C.__u32(arch)) 459 } 460 func (sc *SeccompData) SetArgs(args [6]uint64) { 461 for i := range args { 462 sc.args[i] = C.htot64(sc.arch, C.__u64(args[i])) 463 } 464 } 465 466 // Only support negative args for syscalls where we understand the glibc/kernel 467 // prototypes and behavior. This lists all the syscalls that support negative 468 // arguments where we want to ignore the high 32 bits (ie, we'll mask it since 469 // the arg is known to be 32 bit (uid_t/gid_t) and the kernel accepts one 470 // or both of uint32(-1) and uint64(-1) and does its own masking). 471 var syscallsWithNegArgsMaskHi32 = map[string]bool{ 472 "chown": true, 473 "chown32": true, 474 "fchown": true, 475 "fchown32": true, 476 "fchownat": true, 477 "lchown": true, 478 "lchown32": true, 479 "setgid": true, 480 "setgid32": true, 481 "setregid": true, 482 "setregid32": true, 483 "setresgid": true, 484 "setresgid32": true, 485 "setreuid": true, 486 "setreuid32": true, 487 "setresuid": true, 488 "setresuid32": true, 489 "setuid": true, 490 "setuid32": true, 491 } 492 493 // The kernel uses uint32 for all syscall arguments, but seccomp takes a 494 // uint64. For unsigned ints in our policy, just read straight into uint32 495 // since we don't need to worry about sign extending. 496 // 497 // For negative signed ints in our policy, we first read in as int32, convert 498 // to uint32 and then again uint64 to avoid sign extension woes (see 499 // https://github.com/seccomp/libseccomp/issues/69). For syscalls that take 500 // a 64bit arg that we want to express in our policy, we can add an exception 501 // for reading into a uint64. For now there are no exceptions, so don't need to 502 // do anything extra. 503 func readNumber(token string, syscallName string) (uint64, error) { 504 if value, ok := seccompResolver[token]; ok { 505 return value, nil 506 } 507 508 if value, err := strconv.ParseUint(token, 10, 32); err == nil { 509 return value, nil 510 } 511 512 // Not a positive integer, see if negative is allowed for this syscall 513 if !syscallsWithNegArgsMaskHi32[syscallName] { 514 return 0, fmt.Errorf(`negative argument not supported with "%s"`, syscallName) 515 } 516 517 // It is, so try to parse as an int32 518 value, err := strconv.ParseInt(token, 10, 32) 519 if err != nil { 520 return 0, err 521 } 522 523 // convert the int32 to uint32 then to uint64 (see above) 524 return uint64(uint32(value)), nil 525 } 526 527 func parseLine(line string, secFilter *seccomp.ScmpFilter) error { 528 // ignore comments and empty lines 529 if strings.HasPrefix(line, "#") || line == "" { 530 return nil 531 } 532 533 // regular line 534 tokens := strings.Fields(line) 535 if len(tokens[1:]) > ScArgsMaxlength { 536 return fmt.Errorf("too many arguments specified for syscall '%s' in line %q", tokens[0], line) 537 } 538 539 // fish out syscall 540 syscallName := tokens[0] 541 secSyscall, err := seccomp.GetSyscallFromName(syscallName) 542 if err != nil { 543 // FIXME: use structed error in libseccomp-golang when 544 // https://github.com/seccomp/libseccomp-golang/pull/26 545 // gets merged. For now, ignore 546 // unknown syscalls 547 return nil 548 } 549 550 var conds []seccomp.ScmpCondition 551 for pos, arg := range tokens[1:] { 552 var cmpOp seccomp.ScmpCompareOp 553 var value uint64 554 var err error 555 556 if arg == "-" { // skip arg 557 continue 558 } 559 560 if strings.HasPrefix(arg, ">=") { 561 cmpOp = seccomp.CompareGreaterEqual 562 value, err = readNumber(arg[2:], syscallName) 563 } else if strings.HasPrefix(arg, "<=") { 564 cmpOp = seccomp.CompareLessOrEqual 565 value, err = readNumber(arg[2:], syscallName) 566 } else if strings.HasPrefix(arg, "!") { 567 cmpOp = seccomp.CompareNotEqual 568 value, err = readNumber(arg[1:], syscallName) 569 } else if strings.HasPrefix(arg, "<") { 570 cmpOp = seccomp.CompareLess 571 value, err = readNumber(arg[1:], syscallName) 572 } else if strings.HasPrefix(arg, ">") { 573 cmpOp = seccomp.CompareGreater 574 value, err = readNumber(arg[1:], syscallName) 575 } else if strings.HasPrefix(arg, "|") { 576 cmpOp = seccomp.CompareMaskedEqual 577 value, err = readNumber(arg[1:], syscallName) 578 } else if strings.HasPrefix(arg, "u:") { 579 cmpOp = seccomp.CompareEqual 580 value, err = findUid(arg[2:]) 581 if err != nil { 582 return fmt.Errorf("cannot parse token %q (line %q): %v", arg, line, err) 583 } 584 } else if strings.HasPrefix(arg, "g:") { 585 cmpOp = seccomp.CompareEqual 586 value, err = findGid(arg[2:]) 587 if err != nil { 588 return fmt.Errorf("cannot parse token %q (line %q): %v", arg, line, err) 589 } 590 } else { 591 cmpOp = seccomp.CompareEqual 592 value, err = readNumber(arg, syscallName) 593 } 594 if err != nil { 595 return fmt.Errorf("cannot parse token %q (line %q)", arg, line) 596 } 597 598 // For now only support EQ with negative args. If changing 599 // this, be sure to adjust readNumber accordingly and use 600 // libseccomp carefully. 601 if syscallsWithNegArgsMaskHi32[syscallName] { 602 if cmpOp != seccomp.CompareEqual { 603 return fmt.Errorf("cannot parse token %q (line %q): unsupported comparison", arg, line) 604 } 605 } 606 607 var scmpCond seccomp.ScmpCondition 608 if cmpOp == seccomp.CompareMaskedEqual { 609 scmpCond, err = seccomp.MakeCondition(uint(pos), cmpOp, value, value) 610 } else if syscallsWithNegArgsMaskHi32[syscallName] { 611 scmpCond, err = seccomp.MakeCondition(uint(pos), seccomp.CompareMaskedEqual, 0xFFFFFFFF, value) 612 } else { 613 scmpCond, err = seccomp.MakeCondition(uint(pos), cmpOp, value) 614 } 615 if err != nil { 616 return fmt.Errorf("cannot parse line %q: %s", line, err) 617 } 618 conds = append(conds, scmpCond) 619 } 620 621 // Default to adding a precise match if possible. Otherwise 622 // let seccomp figure out the architecture specifics. 623 if err = secFilter.AddRuleConditionalExact(secSyscall, seccomp.ActAllow, conds); err != nil { 624 err = secFilter.AddRuleConditional(secSyscall, seccomp.ActAllow, conds) 625 } 626 627 return err 628 } 629 630 // used to mock in tests 631 var ( 632 archDpkgArchitecture = arch.DpkgArchitecture 633 archDpkgKernelArchitecture = arch.DpkgKernelArchitecture 634 ) 635 636 var ( 637 dpkgArchitecture = archDpkgArchitecture() 638 dpkgKernelArchitecture = archDpkgKernelArchitecture() 639 ) 640 641 // For architectures that support a compat architecture, when the 642 // kernel and userspace match, add the compat arch, otherwise add 643 // the kernel arch to support the kernel's arch (eg, 64bit kernels with 644 // 32bit userspace). 645 func addSecondaryArches(secFilter *seccomp.ScmpFilter) error { 646 // note that all architecture strings are in the dpkg 647 // architecture notation 648 var compatArch seccomp.ScmpArch 649 650 // common case: kernel and userspace have the same arch. We 651 // add a compat architecture for some architectures that 652 // support it, e.g. on amd64 kernel and userland, we add 653 // compat i386 syscalls. 654 if dpkgArchitecture == dpkgKernelArchitecture { 655 switch archDpkgArchitecture() { 656 case "amd64": 657 compatArch = seccomp.ArchX86 658 case "arm64": 659 compatArch = seccomp.ArchARM 660 case "ppc64": 661 compatArch = seccomp.ArchPPC 662 } 663 } else { 664 // less common case: kernel and userspace have different archs 665 // so add a compat architecture that matches the kernel. E.g. 666 // an amd64 kernel with i386 userland needs the amd64 secondary 667 // arch added to support specialized snaps that might 668 // conditionally call 64bit code when the kernel supports it. 669 // Note that in this case snapd requests i386 (or arch 'all') 670 // snaps. While unusual from a traditional Linux distribution 671 // perspective, certain classes of embedded devices are known 672 // to use this configuration. 673 compatArch = DpkgArchToScmpArch(archDpkgKernelArchitecture()) 674 } 675 676 if compatArch != seccomp.ArchInvalid { 677 return secFilter.AddArch(compatArch) 678 } 679 680 return nil 681 } 682 683 var errnoOnDenial int16 = C.EPERM 684 685 func preprocess(content []byte) (unrestricted, complain bool) { 686 scanner := bufio.NewScanner(bytes.NewBuffer(content)) 687 for scanner.Scan() { 688 line := strings.TrimSpace(scanner.Text()) 689 switch line { 690 case "@unrestricted": 691 unrestricted = true 692 case "@complain": 693 complain = true 694 } 695 } 696 return unrestricted, complain 697 } 698 699 // With golang-seccomp <= 0.9.0, seccomp.ActLog is not available so guess 700 // at the ActLog value by adding one to ActAllow and then verify that the 701 // string representation is what we expect for ActLog. The value and string is 702 // defined in https://github.com/seccomp/libseccomp-golang/pull/29. 703 // 704 // Ultimately, the fix for this workaround is to be able to use the GetApi() 705 // function created in the PR above. It'll tell us if the kernel, libseccomp, 706 // and libseccomp-golang all support ActLog, but GetApi() is also not available 707 // in golang-seccomp <= 0.9.0. 708 const actLog seccomp.ScmpAction = seccomp.ActAllow + 1 709 710 func actLogSupported() bool { 711 return actLog.String() == "Action: Log system call" 712 } 713 714 func complainAction() seccomp.ScmpAction { 715 // XXX: Work around some distributions not having a new enough 716 // libseccomp-golang that declares ActLog. 717 if actLogSupported() { 718 return actLog 719 } 720 721 // Because ActLog is functionally ActAllow with logging, if we don't 722 // support ActLog, fallback to ActLog. 723 return seccomp.ActAllow 724 } 725 726 func compile(content []byte, out string) error { 727 var err error 728 var secFilter *seccomp.ScmpFilter 729 730 unrestricted, complain := preprocess(content) 731 switch { 732 case unrestricted: 733 return osutil.AtomicWrite(out, bytes.NewBufferString("@unrestricted\n"), 0644, 0) 734 case complain: 735 var complainAct seccomp.ScmpAction = complainAction() 736 737 secFilter, err = seccomp.NewFilter(complainAct) 738 if err != nil { 739 if complainAct != seccomp.ActAllow { 740 // ActLog is only supported in newer versions 741 // of the kernel, libseccomp, and 742 // libseccomp-golang. Attempt to fall back to 743 // ActAllow before erroring out. 744 complainAct = seccomp.ActAllow 745 secFilter, err = seccomp.NewFilter(complainAct) 746 } 747 } 748 749 // Set unrestricted to 'true' to fallback to the pre-ActLog 750 // behavior of simply setting the allow filter without adding 751 // any rules. 752 if complainAct == seccomp.ActAllow { 753 unrestricted = true 754 } 755 default: 756 secFilter, err = seccomp.NewFilter(seccomp.ActErrno.SetReturnCode(errnoOnDenial)) 757 } 758 if err != nil { 759 return fmt.Errorf("cannot create seccomp filter: %s", err) 760 } 761 if err := addSecondaryArches(secFilter); err != nil { 762 return err 763 } 764 765 if !unrestricted { 766 scanner := bufio.NewScanner(bytes.NewBuffer(content)) 767 for scanner.Scan() { 768 if err := parseLine(scanner.Text(), secFilter); err != nil { 769 return fmt.Errorf("cannot parse line: %s", err) 770 } 771 } 772 if scanner.Err(); err != nil { 773 return err 774 } 775 } 776 777 if osutil.GetenvBool("SNAP_SECCOMP_DEBUG") { 778 secFilter.ExportPFC(os.Stdout) 779 } 780 781 // write atomically 782 fout, err := osutil.NewAtomicFile(out, 0644, 0, osutil.NoChown, osutil.NoChown) 783 if err != nil { 784 return err 785 } 786 // Cancel once Committed is a NOP 787 defer fout.Cancel() 788 789 if err := secFilter.ExportBPF(fout.File); err != nil { 790 return err 791 } 792 return fout.Commit() 793 } 794 795 // caches for uid and gid lookups 796 var uidCache = make(map[string]uint64) 797 var gidCache = make(map[string]uint64) 798 799 // findUid returns the identifier of the given UNIX user name. 800 func findUid(username string) (uint64, error) { 801 if uid, ok := uidCache[username]; ok { 802 return uid, nil 803 } 804 if !osutil.IsValidUsername(username) { 805 return 0, fmt.Errorf("%q must be a valid username", username) 806 } 807 uid, err := osutil.FindUid(username) 808 if err == nil { 809 uidCache[username] = uid 810 } 811 return uid, err 812 } 813 814 // findGid returns the identifier of the given UNIX group name. 815 func findGid(group string) (uint64, error) { 816 if gid, ok := gidCache[group]; ok { 817 return gid, nil 818 } 819 if !osutil.IsValidUsername(group) { 820 return 0, fmt.Errorf("%q must be a valid group name", group) 821 } 822 gid, err := osutil.FindGid(group) 823 if err == nil { 824 gidCache[group] = gid 825 } 826 return gid, err 827 } 828 829 func showSeccompLibraryVersion() error { 830 major, minor, micro := seccomp.GetLibraryVersion() 831 fmt.Fprintf(os.Stdout, "%d.%d.%d\n", major, minor, micro) 832 return nil 833 } 834 835 func main() { 836 var err error 837 var content []byte 838 839 if len(os.Args) < 2 { 840 fmt.Printf("%s: need a command\n", os.Args[0]) 841 os.Exit(1) 842 } 843 844 cmd := os.Args[1] 845 switch cmd { 846 case "compile": 847 if len(os.Args) < 4 { 848 fmt.Println("compile needs an input and output file") 849 os.Exit(1) 850 } 851 content, err = ioutil.ReadFile(os.Args[2]) 852 if err != nil { 853 break 854 } 855 err = compile(content, os.Args[3]) 856 case "library-version": 857 err = showSeccompLibraryVersion() 858 case "version-info": 859 err = showVersionInfo() 860 default: 861 err = fmt.Errorf("unsupported argument %q", cmd) 862 } 863 864 if err != nil { 865 fmt.Fprintf(os.Stderr, "error: %v\n", err) 866 os.Exit(1) 867 } 868 }