| 1 | package instancectl |
| 2 | |
| 3 | import ( |
| 4 | "os" |
| 5 | "path/filepath" |
| 6 | "runtime" |
| 7 | "strings" |
| 8 | "unsafe" |
| 9 | |
| 10 | "golang.org/x/sys/unix" |
| 11 | ) |
| 12 | |
| 13 | type claimBPFInsn struct { |
| 14 | Code, Registers uint8 |
| 15 | Offset int16 |
| 16 | Immediate int32 |
| 17 | } |
| 18 | |
| 19 | func claimConnectFilter(ipv6 bool) []claimBPFInsn { |
| 20 | // bpf_sock_addr has network-order user_ip4 at 4 and user_ip6 at 8. |
| 21 | load := func(offset int16) []claimBPFInsn { return []claimBPFInsn{{0x61, 0x10, offset, 0}, {0xdc, 0, 0, 32}} } |
| 22 | var p []claimBPFInsn |
| 23 | if !ipv6 { |
| 24 | p = append(p, load(4)...) |
| 25 | p = append(p, claimBPFInsn{0x54, 0, 0, -16777216}, claimBPFInsn{0x15, 0, 2, 0x7f000000}) |
| 26 | } else { |
| 27 | p = append(p, load(8)...) |
| 28 | p = append(p, claimBPFInsn{0x55, 0, 14, 0}) |
| 29 | p = append(p, load(12)...) |
| 30 | p = append(p, claimBPFInsn{0x55, 0, 11, 0}) |
| 31 | p = append(p, load(16)...) |
| 32 | p = append(p, claimBPFInsn{0x15, 0, 4, 0xffff}, claimBPFInsn{0x55, 0, 7, 0}) |
| 33 | p = append(p, load(20)...) |
| 34 | p = append(p, claimBPFInsn{0x15, 0, 6, 1}) |
| 35 | p = append(p, load(20)...) |
| 36 | p = append(p, claimBPFInsn{0x54, 0, 0, -16777216}, claimBPFInsn{0x15, 0, 2, 0x7f000000}) |
| 37 | } |
| 38 | return append(p, claimBPFInsn{0xb7, 0, 0, 1}, claimBPFInsn{0x95, 0, 0, 0}, claimBPFInsn{0xb7, 0, 0, 0}, claimBPFInsn{0x95, 0, 0, 0}) |
| 39 | } |
| 40 | |
| 41 | func claimAttachConnectFilter(group int, kind uint32, ipv6 bool) error { |
| 42 | p := claimConnectFilter(ipv6) |
| 43 | license := []byte("GPL\x00") |
| 44 | attr := struct { |
| 45 | Type, Count uint32 |
| 46 | Instructions, License uint64 |
| 47 | LogLevel, LogSize uint32 |
| 48 | LogBuffer uint64 |
| 49 | Version, Flags uint32 |
| 50 | Name [16]byte |
| 51 | IfIndex, Attach uint32 |
| 52 | }{Type: unix.BPF_PROG_TYPE_CGROUP_SOCK_ADDR, Count: uint32(len(p)), Instructions: uint64(uintptr(unsafe.Pointer(&p[0]))), License: uint64(uintptr(unsafe.Pointer(&license[0]))), Attach: kind} |
| 53 | fd, _, errno := unix.Syscall(unix.SYS_BPF, unix.BPF_PROG_LOAD, uintptr(unsafe.Pointer(&attr)), unsafe.Sizeof(attr)) |
| 54 | runtime.KeepAlive(p) |
| 55 | runtime.KeepAlive(license) |
| 56 | if errno != 0 { |
| 57 | return errno |
| 58 | } |
| 59 | defer unix.Close(int(fd)) |
| 60 | attach := struct{ Target, Program, Kind, Flags uint32 }{uint32(group), uint32(fd), kind, unix.BPF_F_ALLOW_MULTI} |
| 61 | _, _, errno = unix.Syscall(unix.SYS_BPF, unix.BPF_PROG_ATTACH, uintptr(unsafe.Pointer(&attach)), unsafe.Sizeof(attach)) |
| 62 | if errno != 0 { |
| 63 | return errno |
| 64 | } |
| 65 | return nil |
| 66 | } |
| 67 | |
| 68 | func (u ClaimUnits) restrictBoundNetwork(s ClaimUnitStarted) error { |
| 69 | _, group, err := u.Placement(u.BoundHost) |
| 70 | if err != nil || s.ControlGroup != group || s.Unit != u.BoundUnit || u.CgroupRoot != "/sys/fs/cgroup" { |
| 71 | return os.ErrPermission |
| 72 | } |
| 73 | // Pin each directory and check every ancestor before attaching. No program |
| 74 | // is pinned on disk; the kernel retains it until this cgroup is destroyed. |
| 75 | fd, err := unix.Open("/", unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) |
| 76 | if err != nil { |
| 77 | return err |
| 78 | } |
| 79 | defer func() { unix.Close(fd) }() |
| 80 | for _, part := range strings.Split(strings.TrimPrefix(filepath.Join(u.CgroupRoot, group), "/"), "/") { |
| 81 | next, e := unix.Openat(fd, part, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) |
| 82 | if e != nil { |
| 83 | return e |
| 84 | } |
| 85 | unix.Close(fd) |
| 86 | fd = next |
| 87 | var st unix.Stat_t |
| 88 | if e = unix.Fstat(fd, &st); e != nil { |
| 89 | return e |
| 90 | } |
| 91 | if st.Uid != 0 || st.Mode&0022 != 0 { |
| 92 | return os.ErrPermission |
| 93 | } |
| 94 | } |
| 95 | var fs unix.Statfs_t |
| 96 | if err = unix.Fstatfs(fd, &fs); err != nil { |
| 97 | return err |
| 98 | } |
| 99 | if fs.Type != unix.CGROUP2_SUPER_MAGIC { |
| 100 | return os.ErrPermission |
| 101 | } |
| 102 | for _, hook := range []struct { |
| 103 | kind uint32 |
| 104 | ipv6 bool |
| 105 | }{ |
| 106 | {unix.BPF_CGROUP_INET4_CONNECT, false}, {unix.BPF_CGROUP_INET6_CONNECT, true}, |
| 107 | {unix.BPF_CGROUP_UDP4_SENDMSG, false}, {unix.BPF_CGROUP_UDP6_SENDMSG, true}, |
| 108 | } { |
| 109 | if err = claimAttachConnectFilter(fd, hook.kind, hook.ipv6); err != nil { |
| 110 | return err |
| 111 | } |
| 112 | } |
| 113 | return nil |
| 114 | } |
| 115 | |