Commit 7acfcebdf5

7acfcebdf5445e4c73e674ee6be54f499f243c98

parent: 2258972d20

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-29 03:45 UTC

runner: refuse to start without build cgroups when taking untrusted builds or polling over loopback

Ref #260

Layout: unified · split

cmd/gitbay-runner/cgroup.go +19
@@ -31,6 +31,25 @@ func buildCgroupDir(builds string, id int64, trusted bool) string {
3131 return filepath.Join(builds, class, fmt.Sprintf("build-%d", id))
3232}
3333
34// buildCgroupsRequired names what makes build cgroups mandatory, or ""
35// when a podman runner may run its builds in its own service cgroup.
36// Limits that cannot be applied are refused, not dropped: a runner that
37// accepted -memory and ran uncapped is what #188 was. A runner taking
38// untrusted builds, or polling over loopback on the daemon's host, is
39// the shape the builds nftables table guards, and that table matches
40// builds by these cgroups; without them it matches nothing (#260).
41func buildCgroupsRequired(memory, cpus string, untrusted, loopback bool) string {
42 switch {
43 case memory != "" || cpus != "":
44 return "-memory/-cpus"
45 case untrusted:
46 return "-untrusted"
47 case loopback:
48 return "a loopback -remote"
49 }
50 return ""
51}
52
3453// memoryBytes parses podman's memory units — a whole number with an
3554// optional b, k, m or g suffix — into bytes.
3655func memoryBytes(s string) (int64, error) {
cmd/gitbay-runner/cgroup_test.go +23
@@ -134,3 +134,26 @@ func TestBuildsTableNamesTheClassCgroups(t *testing.T) {
134134 t.Error("the drop-in does not load the builds table")
135135 }
136136}
137
138// Without build cgroups a podman runner may carry on only where nothing
139// depends on them: no limits, no untrusted builds, and not on the
140// daemon's host, where the builds table matches by cgroup (#260).
141func TestBuildCgroupsRequired(t *testing.T) {
142 cases := []struct {
143 memory, cpus string
144 untrusted, loopback bool
145 want string
146 }{
147 {"", "", false, false, ""},
148 {"6g", "", false, false, "-memory/-cpus"},
149 {"", "3", false, false, "-memory/-cpus"},
150 {"", "", true, false, "-untrusted"},
151 {"", "", false, true, "a loopback -remote"},
152 {"", "", true, true, "-untrusted"},
153 }
154 for _, c := range cases {
155 if got := buildCgroupsRequired(c.memory, c.cpus, c.untrusted, c.loopback); got != c.want {
156 t.Errorf("buildCgroupsRequired(%q, %q, %v, %v) = %q, want %q", c.memory, c.cpus, c.untrusted, c.loopback, got, c.want)
157 }
158 }
159}
cmd/gitbay-runner/main.go +3 −6
@@ -132,14 +132,11 @@ func main() {
132132 // cgroup of the first invocation, and that must be the runner's
133133 // leaf, not a build's.
134134 cg, err := prepareBuildCgroups()
135 switch {
135 switch why := buildCgroupsRequired(r.memory, r.cpus, *untrusted, r.loopbackRemote()); {
136136 case err == nil:
137137 r.cgroups = cg
138 case r.memory != "" || r.cpus != "":
139 // Limits that cannot be applied are refused, not dropped:
140 // a runner that accepted -memory and ran uncapped is what
141 // #188 was.
142 log.Fatalf("-memory/-cpus: build cgroups unavailable: %v", err)
138 case why != "":
139 log.Fatalf("%s: build cgroups unavailable: %v", why, err)
143140 default:
144141 log.Printf("build cgroups unavailable (%v); builds run unconfined in the service cgroup", err)
145142 }