From 714796a1cd15b0ff658ecef89cf34ad35dfc3130 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Sat, 1 Aug 2026 14:59:14 -0700 Subject: [PATCH 1/2] devices: fix out-of-bounds write in BPF_PROG_QUERY bpfAttrQuery only described the query member of union bpf_attr up to prog_cnt, and that size (32 bytes) is what is passed to bpf(2). Since Linux 6.16 (kernel commit 120933984460, "bpf: Implement mprog API on top of existing cgroup progs"), __cgroup_bpf_query copies query.revision back to userspace unconditionally, without looking at the size we passed. As revision sits at offset 56, the kernel writes 8 bytes past the end of the struct on every BPF_PROG_QUERY. Depending on how the compiler lays out the frame, those 8 bytes either land on unused stack space, and nothing happens, or on live data. In runc this showed up as corrupted defer records, crashing "runc restore" in runtime.copystack, and as garbled strings. Kernel commit 21c4b99b27f3 ("bpf: fix BPF_PROG_QUERY OOB write and cgroup backward compat") added the missing size check, but we have to keep working on kernels that lack it, so describe the query struct in full. Co-Authored-By: Claude Opus 5 Signed-off-by: Kir Kolyshkin --- devices/ebpf_linux.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/devices/ebpf_linux.go b/devices/ebpf_linux.go index cfc36f7..f7def12 100644 --- a/devices/ebpf_linux.go +++ b/devices/ebpf_linux.go @@ -23,6 +23,14 @@ func findAttachedCgroupDeviceFilters(dirFd int) (_ []*ebpf.Program, retErr error AttachFlags uint32 ProgIds uint64 // __aligned_u64 ProgCnt uint32 + // Kernels from v6.16 to v7.1 have a bug: they write the Revision field + // (at offset 56) no matter what size is provided to bpf(2). The fields + // below are unused here but has to be kept here as a workaround. + _ uint32 // padding + ProgAttachFlags uint64 // __aligned_u64 + LinkIDs uint64 // __aligned_u64 + LinkAttachFlags uint64 // __aligned_u64 + Revision uint64 } // Currently you can only have 64 eBPF programs attached to a cgroup. From b364e3b9588ec3ad42dd21bf52b20021553a30f5 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Mon, 22 Jun 2026 23:56:17 -0700 Subject: [PATCH 2/2] devices: drop cilium/ebpf{,link} deps Replace the use of the cilium/ebpf and cilium/ebpf/link with direct bpf(2) syscalls. Keep cilium/ebpf/asm for instruction assembly. Notes: - the eBPF device-filter programs are now tracked by raw file descriptors instead of *ebpf.Program handles; - asm.Instructions.Marshal requires a concrete binary.LittleEndian or binary.BigEndian, so endian.go detects the native byte order at runtime as a workaround. This could be done during compile time but requires maintaining a list of all GOARCHes; - the "removing old filter %d from cgroup" log messages are removed: this always happens when using systemd and the messages are not useful, plus obtaining the details would add more code; This reduces the runc binary size by about ~1M. Signed-off-by: Kir Kolyshkin --- devices/ebpf_linux.go | 332 +++++++++++++++++++++++++++------------ devices/endian.go | 20 +++ devices/signals_linux.go | 55 +++++++ go.sum | 12 -- 4 files changed, 309 insertions(+), 110 deletions(-) create mode 100644 devices/endian.go create mode 100644 devices/signals_linux.go diff --git a/devices/ebpf_linux.go b/devices/ebpf_linux.go index f7def12..116f20c 100644 --- a/devices/ebpf_linux.go +++ b/devices/ebpf_linux.go @@ -1,6 +1,7 @@ package devices import ( + "bytes" "errors" "fmt" "os" @@ -8,14 +9,185 @@ import ( "sync" "unsafe" - "github.com/cilium/ebpf" "github.com/cilium/ebpf/asm" - "github.com/cilium/ebpf/link" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) -func findAttachedCgroupDeviceFilters(dirFd int) (_ []*ebpf.Program, retErr error) { +// bpfMaxRetries is the maximum number of times a BPF_PROG_LOAD is retried +// after being interrupted by a signal (EAGAIN). +const bpfMaxRetries = 30 + +func bpf(cmd uintptr, attr unsafe.Pointer, size uintptr) (uintptr, error) { + // Prevent the Go profiler from repeatedly interrupting the verifier. + if cmd == unix.BPF_PROG_LOAD || cmd == unix.BPF_PROG_RUN { + maskProfilerSignal() + defer unmaskProfilerSignal() + } + for retries := 0; ; retries++ { + r1, _, errno := unix.Syscall(unix.SYS_BPF, cmd, uintptr(attr), size) + runtime.KeepAlive(attr) + if errno == 0 { + return r1, nil + } + + // As of ~4.20 the verifier can be interrupted by a signal, + // and returns EAGAIN in that case. + if errno == unix.EAGAIN && cmd == unix.BPF_PROG_LOAD { + if retries < bpfMaxRetries { + continue + } + return r1, fmt.Errorf("bpf: prog load keeps being interrupted by EAGAIN after %d retries: %w", bpfMaxRetries, errno) + } + + return r1, os.NewSyscallError("bpf", errno) + } +} + +func bpfFD(cmd uintptr, attr unsafe.Pointer, size uintptr) (int, error) { + fd, err := bpf(cmd, attr, size) + if err != nil { + return -1, err + } + if fd != 0 { + return int(fd), nil + } + // bpf(2) treats fd 0 as unset, so we need to dup it. + newFD, err := unix.FcntlInt(fd, unix.F_DUPFD_CLOEXEC, 1) + unix.Close(int(fd)) + if err != nil { + return -1, os.NewSyscallError("dup", err) + } + return newFD, nil +} + +// bpfProgLoad loads a BPF_PROG_TYPE_CGROUP_DEVICE program and returns its fd. +// +// It is roughly equivalent to [github.com/cilium/ebpf/internal/sys.ProgLoad], +// and the "retry with verifier log" is taken from [github.com/cilium/ebpf.NewProgram]. +func bpfProgLoad(insns asm.Instructions, license string) (int, error) { + buf := bytes.NewBuffer(make([]byte, 0, insns.Size())) + if err := insns.Marshal(buf, nativeEndian); err != nil { + return -1, err + } + insnsBytes := buf.Bytes() + + licensePtr, err := unix.BytePtrFromString(license) + if err != nil { + return -1, err + } + + // Subset of struct bpf_attr for BPF_PROG_LOAD. Fields past the ones we set + // are left zero; the kernel zero-fills any part of bpf_attr beyond the size + // we pass. + attr := struct { + progType uint32 + insnCnt uint32 + insns uint64 // pointer + license uint64 // pointer + logLevel uint32 + logSize uint32 + logBuf uint64 // pointer + }{ + progType: unix.BPF_PROG_TYPE_CGROUP_DEVICE, + insnCnt: uint32(len(insnsBytes) / asm.InstructionSize), + insns: uint64(uintptr(unsafe.Pointer(&insnsBytes[0]))), + license: uint64(uintptr(unsafe.Pointer(licensePtr))), + } + + fd, err := bpfFD(unix.BPF_PROG_LOAD, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) + // attr holds the pointers as integers, so the GC can't see them; keep the + // referenced objects alive until the syscall returns. + runtime.KeepAlive(insnsBytes) + runtime.KeepAlive(licensePtr) + if err == nil { + return fd, nil + } + origErr := err + + // The load failed. Retry with the verifier log enabled so we can include + // it in the error (the first attempt skips it, as it is the fast path). + const minLogSize = 64 * 1024 + // If the log does not fit into the buffer, the kernel returns ENOSPC, + // in which case retry with a bigger buffer, up to maxLogSize. + // Note kernels < v5.2 rejected a log_size larger than UINT_MAX >> 8. + const maxLogSize = 16*1024*1024 - 1 + attr.logLevel = 1 + for logSize := minLogSize; ; logSize = min(logSize*8, maxLogSize) { + log := make([]byte, logSize) + attr.logSize = uint32(len(log)) + attr.logBuf = uint64(uintptr(unsafe.Pointer(unsafe.SliceData(log)))) + + fd, err = bpfFD(unix.BPF_PROG_LOAD, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) + runtime.KeepAlive(insnsBytes) + runtime.KeepAlive(licensePtr) + runtime.KeepAlive(log) + if err == nil { // Totally unexpected. + logrus.Warnf("BPF_PROG_LOAD retry unexpectedly succeeded after failing with %v earlier", origErr) + return fd, nil + } + if errors.Is(err, unix.ENOSPC) && logSize < maxLogSize { + continue + } + if n := bytes.IndexByte(log, 0); n > 0 { + return -1, fmt.Errorf("%w: %s", err, bytes.TrimRight(log[:n], "\n")) + } + return -1, err + } +} + +// bpfProgGetFdByID returns the fd for the BPF program with the given ID. +// +// It is roughly equivalent to [github.com/cilium/ebpf/internal/sys.ProgGetFdById]. +func bpfProgGetFdByID(id uint32) (int, error) { + // The kernel zero-fills the rest of bpf_attr beyond the size we pass. + attr := struct{ id uint32 }{id} + return bpfFD(unix.BPF_PROG_GET_FD_BY_ID, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) +} + +// bpfProgAttach attaches progFd to cgroupFd with the given flags. If replaceFd +// is > 0, its fd is set in replaceBpfFd (for BPF_F_REPLACE semantics). +// +// It is roughly equivalent to [github.com/cilium/ebpf/link.RawAttachProgram]. +func bpfProgAttach(cgroupFd, progFd int, attachFlags uint32, replaceFd int) error { + attr := struct { + targetFd uint32 + attachBpfFd uint32 + attachType uint32 + attachFlags uint32 + replaceBpfFd uint32 + }{ + targetFd: uint32(cgroupFd), + attachBpfFd: uint32(progFd), + attachType: uint32(unix.BPF_CGROUP_DEVICE), + attachFlags: attachFlags, + } + if replaceFd > 0 { + attr.replaceBpfFd = uint32(replaceFd) + } + _, err := bpf(unix.BPF_PROG_ATTACH, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) + return err +} + +// bpfProgDetach detaches progFd from cgroupFd. +// +// It is roughly equivalent to [github.com/cilium/ebpf/link.RawDetachProgram]. +func bpfProgDetach(cgroupFd, progFd int) error { + // The kernel zero-fills the rest of bpf_attr beyond the size we pass. + attr := struct { + targetFd uint32 + attachBpfFd uint32 + attachType uint32 + }{ + targetFd: uint32(cgroupFd), + attachBpfFd: uint32(progFd), + attachType: uint32(unix.BPF_CGROUP_DEVICE), + } + _, err := bpf(unix.BPF_PROG_DETACH, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) + return err +} + +func findAttachedCgroupDeviceFilters(dirFd int) (_ []int, retErr error) { type bpfAttrQuery struct { TargetFd uint32 AttachType uint32 @@ -33,11 +205,21 @@ func findAttachedCgroupDeviceFilters(dirFd int) (_ []*ebpf.Program, retErr error Revision uint64 } + // query.ProgIds below holds the address of the progIds buffer as an + // integer, so it is invisible to the runtime: the GC does not see the + // buffer as reachable, and the stack copier would not rewrite the address + // if the buffer were stack-allocated. Pinning it takes care of both -- + // pinner.Pin forces the buffer onto the heap (its argument escapes) and + // keeps it in place and alive until Unpin. + var pinner runtime.Pinner + defer pinner.Unpin() + // Currently you can only have 64 eBPF programs attached to a cgroup. size := 64 retries := 0 for retries < 10 { progIds := make([]uint32, size) + pinner.Pin(&progIds[0]) query := bpfAttrQuery{ TargetFd: uint32(dirFd), AttachType: uint32(unix.BPF_CGROUP_DEVICE), @@ -46,35 +228,31 @@ func findAttachedCgroupDeviceFilters(dirFd int) (_ []*ebpf.Program, retErr error } // Fetch the list of program ids. - _, _, errno := unix.Syscall(unix.SYS_BPF, - uintptr(unix.BPF_PROG_QUERY), - uintptr(unsafe.Pointer(&query)), - unsafe.Sizeof(query)) + _, err := bpf(unix.BPF_PROG_QUERY, unsafe.Pointer(&query), unsafe.Sizeof(query)) size = int(query.ProgCnt) - runtime.KeepAlive(query) - if errno != 0 { + if err != nil { // On ENOSPC we get the correct number of programs. - if errno == unix.ENOSPC { + if errors.Is(err, unix.ENOSPC) { retries++ continue } - return nil, fmt.Errorf("bpf_prog_query(BPF_CGROUP_DEVICE) failed: %w", errno) + return nil, fmt.Errorf("bpf_prog_query(BPF_CGROUP_DEVICE) failed: %w", err) } - // Convert the ids to program handles. - // On error we don't return the programs slice, so close the fds stored there. + // Convert the ids to program fds. + // On error we don't return the fds slice, so close the fds stored there. progIds = progIds[:size] - programs := make([]*ebpf.Program, 0, len(progIds)) + fds := make([]int, 0, len(progIds)) defer func() { if retErr != nil { - for _, p := range programs { - p.Close() + for _, fd := range fds { + unix.Close(fd) } } }() - for _, progId := range progIds { - program, err := ebpf.NewProgramFromID(ebpf.ProgramID(progId)) + for _, progID := range progIds { + fd, err := bpfProgGetFdByID(progID) if err != nil { // We skip over programs that give us -EACCES or -EPERM. This // is necessary because there may be BPF programs that have @@ -86,15 +264,14 @@ func findAttachedCgroupDeviceFilters(dirFd int) (_ []*ebpf.Program, retErr error // programs (and stops runc from breaking on distributions with // very strict SELinux policies). if errors.Is(err, os.ErrPermission) { - logrus.Debugf("ignoring existing CGROUP_DEVICE program (prog_id=%v) which cannot be accessed by runc -- likely due to LSM policy: %v", progId, err) + logrus.Debugf("ignoring existing CGROUP_DEVICE program (prog_id=%v) which cannot be accessed by runc -- likely due to LSM policy: %v", progID, err) continue } return nil, fmt.Errorf("cannot fetch program from id: %w", err) } - programs = append(programs, program) + fds = append(fds, fd) } - runtime.KeepAlive(progIds) - return programs, nil + return fds, nil } return nil, errors.New("could not get complete list of CGROUP_DEVICE programs") @@ -107,23 +284,17 @@ var ( // Loosely based on the BPF_F_REPLACE support check in // https://github.com/cilium/ebpf/blob/v0.6.0/link/syscalls.go. -// -// TODO: move this logic to cilium/ebpf func haveBpfProgReplace() bool { haveBpfProgReplaceOnce.Do(func() { - prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ - Type: ebpf.CGroupDevice, - License: "MIT", - Instructions: asm.Instructions{ - asm.Mov.Imm(asm.R0, 0), - asm.Return(), - }, - }) + progFd, err := bpfProgLoad(asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.Return(), + }, "MIT") if err != nil { - logrus.Warnf("checking for BPF_F_REPLACE support: ebpf.NewProgram failed: %v", err) + logrus.Warnf("checking for BPF_F_REPLACE support: bpfProgLoad failed: %v", err) return } - defer prog.Close() + defer unix.Close(progFd) devnull, err := os.Open("/dev/null") if err != nil { @@ -135,24 +306,19 @@ func haveBpfProgReplace() bool { // We know that we have BPF_PROG_ATTACH since we can load // BPF_CGROUP_DEVICE programs. If passing BPF_F_REPLACE gives us EINVAL // we know that the feature isn't present. - err = link.RawAttachProgram(link.RawAttachProgramOptions{ - // We rely on this fd being checked after attachFlags in the kernel. - Target: int(devnull.Fd()), - // Attempt to "replace" our BPF program with itself. This will - // always fail, but we should get -EINVAL if BPF_F_REPLACE is not - // supported. - Anchor: link.ReplaceProgram(prog), - Program: prog, - Attach: ebpf.AttachCGroupDevice, - Flags: unix.BPF_F_ALLOW_MULTI, - }) - if errors.Is(err, ebpf.ErrNotSupported) || errors.Is(err, unix.EINVAL) { + // + // We rely on the target fd being checked after attachFlags in the + // kernel. Attempting to "replace" our BPF program with itself always + // fails, but we should get -EINVAL if BPF_F_REPLACE is not supported, + // and -EBADF (from the dummy target fd) if it is. + err = bpfProgAttach(int(devnull.Fd()), progFd, unix.BPF_F_ALLOW_MULTI|unix.BPF_F_REPLACE, progFd) + if errors.Is(err, unix.EINVAL) { // not supported return } if !errors.Is(err, unix.EBADF) { // If we see any new errors here, it's possible that there is a - // regression due to a cilium/ebpf update and the above EINVAL + // regression due to a kernel update and the above EINVAL // checks are not working. So, be loud about it so someone notices // and we can get the issue fixed quicker. logrus.Warnf("checking for BPF_F_REPLACE: got unexpected (not EBADF or EINVAL) error: %v", err) @@ -177,83 +343,53 @@ func loadAttachCgroupDeviceFilter(insts asm.Instructions, license string, dirFd _ = unix.Setrlimit(unix.RLIMIT_MEMLOCK, memlockLimit) // Get the list of existing programs. - oldProgs, err := findAttachedCgroupDeviceFilters(dirFd) + oldFds, err := findAttachedCgroupDeviceFilters(dirFd) if err != nil { return err } defer func() { - for _, p := range oldProgs { - p.Close() + for _, fd := range oldFds { + unix.Close(fd) } }() - useReplaceProg := haveBpfProgReplace() && len(oldProgs) == 1 + useReplaceProg := haveBpfProgReplace() && len(oldFds) == 1 // Generate new program. - spec := &ebpf.ProgramSpec{ - Type: ebpf.CGroupDevice, - Instructions: insts, - License: license, - } - prog, err := ebpf.NewProgram(spec) + progFd, err := bpfProgLoad(insts, license) if err != nil { - return err + return fmt.Errorf("failed to call BPF_PROG_LOAD: %w", err) } - defer prog.Close() + // Once the program is attached, the kernel keeps it alive via the cgroup + // attachment, so we no longer need our own fd; we also don't need it if the + // attach below fails. Either way, close it on return. + defer unix.Close(progFd) // If there is only one old program, we can just replace it directly. - - attachProgramOptions := link.RawAttachProgramOptions{ - Target: dirFd, - Program: prog, - Attach: ebpf.AttachCGroupDevice, - Flags: unix.BPF_F_ALLOW_MULTI, - } - + replaceFd := -1 + attachFlags := uint32(unix.BPF_F_ALLOW_MULTI) if useReplaceProg { - attachProgramOptions.Anchor = link.ReplaceProgram(oldProgs[0]) + replaceFd = oldFds[0] + attachFlags |= unix.BPF_F_REPLACE } - err = link.RawAttachProgram(attachProgramOptions) + err = bpfProgAttach(dirFd, progFd, attachFlags, replaceFd) if err != nil { - return fmt.Errorf("failed to call BPF_PROG_ATTACH (BPF_CGROUP_DEVICE, BPF_F_ALLOW_MULTI): %w", err) + return fmt.Errorf("failed to call BPF_PROG_ATTACH: %w", err) } + if !useReplaceProg { - logLevel := logrus.DebugLevel // If there was more than one old program, give a warning (since this // really shouldn't happen with runc-managed cgroups) and then detach // all the old programs. - if len(oldProgs) > 1 { + if len(oldFds) > 1 { // NOTE: Ideally this should be a warning but it turns out that // systemd-managed cgroups trigger this warning (apparently // systemd doesn't delete old non-systemd programs when // setting properties). - logrus.Infof("found more than one filter (%d) attached to a cgroup -- removing extra filters!", len(oldProgs)) - logLevel = logrus.InfoLevel + logrus.Infof("found more than one filter (%d) attached to a cgroup -- removing extra filters!", len(oldFds)) } - for idx, oldProg := range oldProgs { - // Output some extra debug info. - if info, err := oldProg.Info(); err == nil { - fields := logrus.Fields{ - "type": info.Type.String(), - "tag": info.Tag, - "name": info.Name, - } - if id, ok := info.ID(); ok { - fields["id"] = id - } - if runCount, ok := info.RunCount(); ok { - fields["run_count"] = runCount - } - if runtime, ok := info.Runtime(); ok { - fields["runtime"] = runtime.String() - } - logrus.WithFields(fields).Logf(logLevel, "removing old filter %d from cgroup", idx) - } - err = link.RawDetachProgram(link.RawDetachProgramOptions{ - Target: dirFd, - Program: oldProg, - Attach: ebpf.AttachCGroupDevice, - }) + for _, oldFd := range oldFds { + err = bpfProgDetach(dirFd, oldFd) if err != nil { return fmt.Errorf("failed to call BPF_PROG_DETACH (BPF_CGROUP_DEVICE) on old filter program: %w", err) } diff --git a/devices/endian.go b/devices/endian.go new file mode 100644 index 0000000..23c9cb1 --- /dev/null +++ b/devices/endian.go @@ -0,0 +1,20 @@ +package devices + +import ( + "encoding/binary" +) + +// nativeEndian is used as a workaround for cilium/ebpf/asm, +// which does not accept binary.NativeEndian. +var nativeEndian binary.ByteOrder = func() binary.ByteOrder { + buf := [2]byte{} + binary.NativeEndian.PutUint16(buf[:], 0xABCD) + switch buf { + case [2]byte{0xCD, 0xAB}: + return binary.LittleEndian + case [2]byte{0xAB, 0xCD}: + return binary.BigEndian + default: + panic("could not determine native endianness") + } +}() diff --git a/devices/signals_linux.go b/devices/signals_linux.go new file mode 100644 index 0000000..7d713b1 --- /dev/null +++ b/devices/signals_linux.go @@ -0,0 +1,55 @@ +package devices + +// The code below is copied from +// github.com/cilium/ebpf/internal/sys/signals.go@v0.22.0. + +import ( + "fmt" + "runtime" + "unsafe" + + "golang.org/x/sys/unix" +) + +// A sigset containing only SIGPROF. +var profSet unix.Sigset_t + +func init() { + // See github.com/cilium/ebpf/internal/sys.sigsetAdd for implementation + // details. Open coded here so that the compiler will check the + // constant calculations for us. + profSet.Val[sigprofBit/wordBits] |= 1 << (sigprofBit % wordBits) +} + +// maskProfilerSignal locks the calling goroutine to its underlying OS thread +// and adds SIGPROF to the thread's signal mask. This prevents pprof from +// interrupting expensive syscalls like e.g. BPF_PROG_LOAD. +// +// The caller must defer unmaskProfilerSignal() to reverse the operation. +func maskProfilerSignal() { + runtime.LockOSThread() + + if err := unix.PthreadSigmask(unix.SIG_BLOCK, &profSet, nil); err != nil { + runtime.UnlockOSThread() + panic(fmt.Errorf("masking profiler signal: %w", err)) + } +} + +// unmaskProfilerSignal removes SIGPROF from the underlying thread's signal +// mask, allowing it to be interrupted for profiling once again. +// +// It also unlocks the current goroutine from its underlying OS thread. +func unmaskProfilerSignal() { + defer runtime.UnlockOSThread() + + if err := unix.PthreadSigmask(unix.SIG_UNBLOCK, &profSet, nil); err != nil { + panic(fmt.Errorf("unmasking profiler signal: %w", err)) + } +} + +const ( + // Signal is the nth bit in the bitfield. + sigprofBit = int(unix.SIGPROF - 1) + // The number of bits in one Sigset_t word. + wordBits = int(unsafe.Sizeof(unix.Sigset_t{}.Val[0])) * 8 +) diff --git a/go.sum b/go.sum index db73c8e..136c60f 100644 --- a/go.sum +++ b/go.sum @@ -13,18 +13,10 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= -github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM= -github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= -github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= -github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= -github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= @@ -39,10 +31,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=