Skip to content

devices: drop cilium/ebpf{,link} deps - #64

Merged
thaJeztah merged 1 commit into
opencontainers:mainfrom
kolyshkin:drop-cilium-ebpf
Aug 18, 2026
Merged

devices: drop cilium/ebpf{,link} deps#64
thaJeztah merged 1 commit into
opencontainers:mainfrom
kolyshkin:drop-cilium-ebpf

Conversation

@kolyshkin

@kolyshkin kolyshkin commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

This currently contains #69; will rebase once that one is merged.

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, thus endian_{le,be}.go are introduced as a
    workaround.

This reduces the runc binary size by about ~1M.

NOTE that this is probably limited to runc, because for other users (k8s, cri-o) this was already solved by opencontainers/runc#4248.

Being tested in opencontainers/runc#5340.


For initial discussion about this, see opencontainers/runc#5218.

@kolyshkin
kolyshkin requested a review from a team as a code owner June 23, 2026 06:30
@kolyshkin
kolyshkin force-pushed the drop-cilium-ebpf branch 2 times, most recently from d5f40e9 to 733c596 Compare June 23, 2026 06:56
kolyshkin added a commit to kolyshkin/runc that referenced this pull request Jun 23, 2026
…ranch

Temporarily point the opencontainers/cgroups dependency at the
drop-cilium-ebpf branch (opencontainers/cgroups#64) via a go.mod replace,
and re-vendor, so CI can exercise the cilium/ebpf main+link package
removal end-to-end in runc.

This must not be merged: the replace directive points at a personal fork
branch. Once opencontainers/cgroups#64 lands and is tagged, this should be
replaced by a normal dependency bump.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
kolyshkin added a commit to kolyshkin/runc that referenced this pull request Jun 23, 2026
Let's drop the second biggest (by size) runc dependency and reduce the
binary size by another ~1MB.

Draft/DNM until opencontainers/cgroups#64 is merged/released.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the devices eBPF cgroup-device filter implementation to stop importing github.com/cilium/ebpf’s main and link packages, and instead perform the required operations via direct bpf(2) syscalls while continuing to use github.com/cilium/ebpf/asm for instruction assembly. This aligns with the stated goal of reducing consumer binary size by avoiding heavy transitive dependencies.

Changes:

  • Replaced cilium/ebpf program/link usage with thin wrappers around BPF_PROG_* commands and raw program fds.
  • Updated cgroup device filter attach/query logic to operate on fds (with explicit closes) instead of *ebpf.Program.
  • Added nativeEndian selection via build-tagged endian-specific files to satisfy asm.Instructions.Marshal requirements.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.

File Description
go.sum Removes now-unused transitive dependencies after dropping cilium/ebpf main/link usage.
devices/endian_le.go Provides nativeEndian = binary.LittleEndian under little-endian arch build tags.
devices/endian_be.go Provides nativeEndian = binary.BigEndian under big-endian arch build tags.
devices/ebpf_linux.go Replaces ebpf/link usage with direct bpf(2) syscall wrappers and fd-based program management.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread devices/ebpf_linux.go
Comment thread devices/ebpf_linux.go
Comment on lines +304 to 307
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)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems legit?

@kolyshkin kolyshkin Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ell, since this is the only non-test^ call to bpfProgAttach we can just omit the flags (and BPF_CGROUP_DEVICE, too -- it is kind of expected here).

^ by the test call I mean the one in haveBpfProgReplace

@kolyshkin kolyshkin changed the title devices: drop cilium/ebpf main and link package dependency devices: drop cilium/ebpf{,link} deps Jun 23, 2026
kolyshkin added a commit to kolyshkin/runc that referenced this pull request Jun 23, 2026
Let's drop the second biggest (by size) runc dependency and reduce the
binary size by another ~1MB.

Draft/DNM until opencontainers/cgroups#64 is merged/released.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
kolyshkin added a commit to kolyshkin/runc that referenced this pull request Jun 23, 2026
Let's drop the second biggest (by size) runc dependency and reduce the
binary size by another ~1MB.

Draft/DNM until opencontainers/cgroups#64 is merged/released.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
Comment thread devices/ebpf_linux.go
Comment thread devices/endian_be.go Outdated
@kolyshkin

Copy link
Copy Markdown
Contributor Author

@cyphar @rata PTAL 🙏🏻

@rata rata left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kolyshkin nice, thanks! Did you c&p the definitions? In that case, can you link them so it's simpler to review?

Also, don't we have overlapping functionality in the cgroups package?

Also, I guess no go.mod changes because it is used for the asm part? Nice that the size is decreased anyways :)

Comment thread devices/ebpf_linux.go
@kolyshkin

Copy link
Copy Markdown
Contributor Author

Also, don't we have overlapping functionality in the cgroups package?

Again I'm afraid I fail to understand what you mean here @rata, can you please elaborate or point to whatever you have in mind?

@kolyshkin

Copy link
Copy Markdown
Contributor Author

Did you c&p the definitions? In that case, can you link them so it's simpler to review?

I did not, I just recreated a bare minimum (poor boy) version of functionality that we used from cilium/ebpf and cilium/ebpf/link, basically wiring BPF_PROG_LOAD, BPF_PROG_GET_FD_BY_ID, BPF_PROG_ATTACH, BPF_PROG_DETACH and adding related data structures (also as bare minimum).

I have added a separate second commit, linking to the original cilium/ebpf functions. Let me know if you want it or not so I will squash or remove it.

@kolyshkin

Copy link
Copy Markdown
Contributor Author

@AkihiroSuda @thaJeztah PTAL (I think I've addressed all of your comments).

The second commit is optional and can either be squashed or removed.

@kolyshkin kolyshkin left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking into kernel's tools/lib/bpf/bpf.c, I see that BPF_PROG_LOAD is retried up to 5 times when EAGAIN is received. Also, cilium/ebpf does that (indefinitely).

Implemented the same.

kolyshkin added a commit to kolyshkin/runc that referenced this pull request Aug 1, 2026
Let's drop the second biggest (by size) runc dependency and reduce the
binary size by another ~1MB.

Draft/DNM until opencontainers/cgroups#64 is merged/released.

NB: to update this:

	go mod edit -replace github.com/opencontainers/cgroups=github.com/kolyshkin/oc-cgroups@drop-cilium-ebpf
	make vendor
	git add vendor
	git commit --amend -a

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
@kolyshkin kolyshkin added this to the 0.1.0 milestone Aug 1, 2026
@kolyshkin

Copy link
Copy Markdown
Contributor Author

@rata @cyphar PTAL

kolyshkin added a commit to kolyshkin/runc that referenced this pull request Aug 5, 2026
Let's drop the second biggest (by size) runc dependency and reduce the
binary size by another ~1MB.

Draft/DNM until opencontainers/cgroups#64 is merged/released.

NB: to update this:

	go mod edit -replace github.com/opencontainers/cgroups=github.com/kolyshkin/oc-cgroups@drop-cilium-ebpf
	make vendor
	git add vendor
	git commit --amend -a

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
@kolyshkin

Copy link
Copy Markdown
Contributor Author

Rebased on top of merged #69; updated opencontainers/runc#5340.

@thaJeztah thaJeztah left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly nits and suggestions; ebpf itself is not my forte, but overall looks good.

Comment thread devices/ebpf_linux.go
Comment on lines +24 to +25
maskProfilerSignal()
defer unmaskProfilerSignal()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps consider making maskProfilerSignal() return the unmask func. This forces anyone using it to consider how to handle it, and makes it more organic to call the unmask after wards (similar to context.WithCancel and similar);

unmask := maskProfilerSignal()
defer unmask()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is taken from cilium, there are benefits on keeping it as it is there so we can sync. The func is not exporter either.

But no strong opinion here,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahm right; yeah, that makes sense. It could be something we could try if it's accepted in upstream.

I generally don't like functions that come with a "don't forget to close the door using <some other function> when you leave"; it's easy to not read the docs, and especially in cases where the consequences may not always be immediately apparent.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fwiw I agree with @thaJeztah but am similarly not so strict about it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we can still do these kind of changes in a follow-up; it's all non-exported, so no breaking changes involved.

Comment thread devices/ebpf_linux.go
Comment thread devices/ebpf_linux.go
// 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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated; we could consider using more structured logs for these (probably also remove "by runc");

Suggested change
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.WithFields(logrus.Fields{
"error": err,
"progID": progID,
}).Debug("ignoring existing CGROUP_DEVICE program which cannot be accessed -- likely due to LSM policy")

Comment thread devices/ebpf_linux.go
}, "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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here;

Suggested change
logrus.Warnf("checking for BPF_F_REPLACE support: bpfProgLoad failed: %v", err)
logrus.WithError(err).Warn("checking for BPF_F_REPLACE support: bpfProgLoad failed")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather address this separately as this is already a big change.

Comment thread devices/ebpf_linux.go
Comment on lines +290 to +293
progFd, err := bpfProgLoad(asm.Instructions{
asm.Mov.Imm(asm.R0, 0),
asm.Return(),
}, "MIT")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could make license the first argument to make formatting slightly neater, or remove it altogether as argument (if it's only internal and always using MIT);

Suggested change
progFd, err := bpfProgLoad(asm.Instructions{
asm.Mov.Imm(asm.R0, 0),
asm.Return(),
}, "MIT")
progFd, err := bpfProgLoad("MIT", asm.Instructions{
asm.Mov.Imm(asm.R0, 0),
asm.Return(),
})

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather keep this as is because the signature matches whatever the deviceFilter returns. If we are to drop this from deviceFilter, the change is too much (and this PR is already non-trivial).

Here's the draft diff BTW:

diff --git a/devices/devicefilter.go b/devices/devicefilter.go
index aafa0d0..7b505fe 100644
--- a/devices/devicefilter.go
+++ b/devices/devicefilter.go
@@ -17,13 +17,8 @@ import (
 	"golang.org/x/sys/unix"
 )
 
-const (
-	// license string format is same as kernel MODULE_LICENSE macro
-	license = "Apache"
-)
-
-// deviceFilter returns eBPF device filter program and its license string.
-func deviceFilter(rules []*devices.Rule) (asm.Instructions, string, error) {
+// deviceFilter returns eBPF device filter program.
+func deviceFilter(rules []*devices.Rule) (asm.Instructions, error) {
 	// Generate the minimum ruleset for the device rules we are given. While we
 	// don't care about minimum transitions in cgroupv2, using the emulator
 	// gives us a guarantee that the behaviour of devices filtering is the same
@@ -32,12 +27,12 @@ func deviceFilter(rules []*devices.Rule) (asm.Instructions, string, error) {
 	emu := new(emulator)
 	for _, rule := range rules {
 		if err := emu.Apply(*rule); err != nil {
-			return nil, "", err
+			return nil, err
 		}
 	}
 	cleanRules, err := emu.Rules()
 	if err != nil {
-		return nil, "", err
+		return nil, err
 	}
 
 	p := &program{
@@ -51,20 +46,20 @@ func deviceFilter(rules []*devices.Rule) (asm.Instructions, string, error) {
 			// only be one (at most) at the very start to instruct cgroupv1 to
 			// go into allow-list mode. However we do double-check this here.
 			if idx != 0 || rule.Allow != emu.IsBlacklist() {
-				return nil, "", fmt.Errorf("[internal error] emulated cgroupv2 devices ruleset had bad wildcard at idx %v (%s)", idx, rule.CgroupString())
+				return nil, fmt.Errorf("[internal error] emulated cgroupv2 devices ruleset had bad wildcard at idx %v (%s)", idx, rule.CgroupString())
 			}
 			continue
 		}
 		if rule.Allow == p.defaultAllow {
 			// There should be no rules which have an action equal to the
 			// default action, the emulator removes those.
-			return nil, "", fmt.Errorf("[internal error] emulated cgroupv2 devices ruleset had no-op rule at idx %v (%s)", idx, rule.CgroupString())
+			return nil, fmt.Errorf("[internal error] emulated cgroupv2 devices ruleset had no-op rule at idx %v (%s)", idx, rule.CgroupString())
 		}
 		if err := p.appendRule(rule); err != nil {
-			return nil, "", err
+			return nil, err
 		}
 	}
-	return p.finalize(), license, nil
+	return p.finalize(), nil
 }
 
 type program struct {
diff --git a/devices/devicefilter_test.go b/devices/devicefilter_test.go
index 6df6af2..6a5f93d 100644
--- a/devices/devicefilter_test.go
+++ b/devices/devicefilter_test.go
@@ -20,7 +20,7 @@ func hash(s, comm string) string {
 }
 
 func testDeviceFilter(t testing.TB, devices []*devices.Rule, expectedStr string) {
-	insts, _, err := deviceFilter(devices)
+	insts, err := deviceFilter(devices)
 	if err != nil {
 		t.Fatalf("%s: %v (devices: %+v)", t.Name(), err, devices)
 	}
diff --git a/devices/ebpf_linux.go b/devices/ebpf_linux.go
index 96e1379..3e312d7 100644
--- a/devices/ebpf_linux.go
+++ b/devices/ebpf_linux.go
@@ -65,17 +65,18 @@ func bpfFD(cmd uintptr, attr unsafe.Pointer, size uintptr) (int, error) {
 //
 // 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) {
+func bpfProgLoad(insns asm.Instructions) (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
-	}
+	// License string, in the same format as the kernel MODULE_LICENSE macro.
+	const license = "MIT\000"
 
 	// 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
@@ -92,14 +93,14 @@ func bpfProgLoad(insns asm.Instructions, license string) (int, error) {
 		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))),
+		license:  uint64(uintptr(unsafe.Pointer(unsafe.StringData(license)))),
 	}
 
 	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.
+	// referenced objects alive until the syscall returns. The license string is
+	// a constant, so it needs no such treatment.
 	runtime.KeepAlive(insnsBytes)
-	runtime.KeepAlive(licensePtr)
 	if err == nil {
 		return fd, nil
 	}
@@ -120,7 +121,6 @@ func bpfProgLoad(insns asm.Instructions, license string) (int, error) {
 
 		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)
@@ -290,7 +290,7 @@ func haveBpfProgReplace() bool {
 		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: bpfProgLoad failed: %v", err)
 			return
@@ -334,7 +334,7 @@ func haveBpfProgReplace() bool {
 // Requires the system to be running in cgroup2 unified-mode with kernel >= 4.15 .
 //
 // https://github.com/torvalds/linux/commit/ebc614f687369f9df99828572b1d85a7c2de3d92
-func loadAttachCgroupDeviceFilter(insts asm.Instructions, license string, dirFd int) error {
+func loadAttachCgroupDeviceFilter(insts asm.Instructions, dirFd int) error {
 	// Increase `ulimit -l` limit to avoid BPF_PROG_LOAD error (#2167).
 	// This limit is not inherited into the container.
 	memlockLimit := &unix.Rlimit{
@@ -357,7 +357,7 @@ func loadAttachCgroupDeviceFilter(insts asm.Instructions, license string, dirFd
 	useReplaceProg := haveBpfProgReplace() && len(oldFds) == 1
 
 	// Generate new program.
-	progFd, err := bpfProgLoad(insts, license)
+	progFd, err := bpfProgLoad(insts)
 	if err != nil {
 		return fmt.Errorf("failed to call BPF_PROG_LOAD: %w", err)
 	}
diff --git a/devices/v2.go b/devices/v2.go
index 508f3dd..6da257a 100644
--- a/devices/v2.go
+++ b/devices/v2.go
@@ -55,7 +55,7 @@ func setV2(dirPath string, r *cgroups.Resources) error {
 	if r.SkipDevices {
 		return nil
 	}
-	insts, license, err := deviceFilter(r.Devices)
+	insts, err := deviceFilter(r.Devices)
 	if err != nil {
 		return err
 	}
@@ -64,7 +64,7 @@ func setV2(dirPath string, r *cgroups.Resources) error {
 		return fmt.Errorf("cannot get dir FD for %s", dirPath)
 	}
 	defer unix.Close(dirFD)
-	if err := loadAttachCgroupDeviceFilter(insts, license, dirFD); err != nil {
+	if err := loadAttachCgroupDeviceFilter(insts, dirFD); err != nil {
 		if !canSkipEBPFError(r) {
 			return err
 		}

Comment thread devices/ebpf_linux.go
Comment on lines +393 to 394
err = bpfProgDetach(dirFd, oldFd)
if err != nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this was done to please the "shadow" linter, but it's good to clearly scope the var (to avoid ambiguity)

Suggested change
err = bpfProgDetach(dirFd, oldFd)
if err != nil {
if err := bpfProgDetach(dirFd, oldFd); err != nil {

Also wondering if for these we should always fail early, or use a multi error;

var errs []error
...
errs = append(errs, err)

if err := errors.Join(errs...); err != nil {
    return fmt.Errorf("failed to call BPF_PROG_DETACH (BPF_CGROUP_DEVICE) on old filter program: %w", err)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failing early is better I guess -- the current state is broken in any way, and trying to remove other old progs makes no sense.

@rata rata left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm no maintainer here, but LGTM! :)

The comments @thaJeztah seem nice, but this LGTM already and with those changes it still LGTM :)

@kolyshkin

Copy link
Copy Markdown
Contributor Author

@opencontainers/cgroups-maintainers @cyphar PTAL

@kolyshkin

Copy link
Copy Markdown
Contributor Author

Would really love to have this released in time for runc v1.6.0-rc.1 (ETA 31 August); PTAL @opencontainers/cgroups-maintainers

@DrDaveD

DrDaveD commented Aug 17, 2026

Copy link
Copy Markdown

FYI packages that depend on this one (e.g. https://github.com/apptainer/apptainer) today got a low severity dependabot alert asking to upgrade to github.com/cilium/ebpf version 0.22.0, but that's failing because type *ebpf.ProgramInfo has has removed RunCount. This PR removes the usage of that, but I suggest at the same time upgrading to github.com/cilium/ebpf version 0.22.0 to resolve that alert.

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 <kolyshkin@gmail.com>
@kolyshkin

Copy link
Copy Markdown
Contributor Author

Ran claude's ultrareview on this, found one nit:

devices/ebpf_linux.go:215-223 — Pinner accumulates pins across ENOSPC retries. The runtime.Pinner is function-scoped with defer pinner.Unpin(), but pinner.Pin(&progIds[0]) runs inside the retry loop (up to 10 iterations). Since Pin has no per-object release, each retry's stale progIds buffer stays pinned until the function returns.

Impact is tiny and bounded — at most ~10 buffers of ≤256 bytes each, held for the duration of one query — so it's cleanliness, not a leak. Fix is a one-liner: pinner.Unpin() at the top of each iteration (a no-op on an empty pinner).

I had this deliberately written this way (pinned object is small, retries are rare, and there should not be more than one, so one defer for simplicity), but decided to implement review's comment.

Rebased, pushed, PTAL @opencontaienrs/runc-maintainers (note we have one LGTM from @rata (who is currently not a maintainer).

@kolyshkin

Copy link
Copy Markdown
Contributor Author

@opencontainers/cgroups-maintainers as a side effect, this PR also solves a compatibility issue with cilium/ebpf@v0.22 (by removing its use).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated no new comments.

@thaJeztah thaJeztah left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Sorry thought I already did 😂

@thaJeztah

thaJeztah commented Aug 18, 2026

Copy link
Copy Markdown
Member

(note we have one LGTM from @rata (who is currently not a maintainer).

Can we fix that? I think the intent of this repository was mostly "mechanical" (extract it from runc), and still maintained by the runc maintainers;

Idealy maintainers here would be "runc maintainers" + any "cgroup-only" maintainers.

So I'm tempted to make @opencontainers/cgroups-maintainers "extend" @opencontainers/runc-maintainers I think that can be done by making cgroups-maintainers a "Parent" of runc-maintainers.

@kolyshkin

Copy link
Copy Markdown
Contributor Author

@cyphar @AkihiroSuda @haircommander PTAL

@thaJeztah

Copy link
Copy Markdown
Member

3 reviews; one "non-binding", but works for me; let's bring this one in.

@kolyshkin did you want to tag a new version with this already, or do some of the follow-ups first? (either way works for me; incremental releases are fine)

@thaJeztah
thaJeztah merged commit 30a2293 into opencontainers:main Aug 18, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants