From fdf1a80fdeb6bb030f7bcd2f1fd18d8b061509e0 Mon Sep 17 00:00:00 2001 From: Palash Oswal Date: Thu, 14 Aug 2025 11:37:55 -0400 Subject: [PATCH 1/2] Allow LINUX_IMMUTABLE capability at runtime --- cmgr/docker.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmgr/docker.go b/cmgr/docker.go index 9936e70..1350b40 100644 --- a/cmgr/docker.go +++ b/cmgr/docker.go @@ -447,6 +447,7 @@ func (m *Manager) executeBuild(cMeta *ChallengeMetadata, bMeta *BuildMetadata, b m.log.debug("inserting custom seccomp profile") hConfig.SecurityOpt = []string{"seccomp:" + seccompPolicy} } + hConfig.CapAdd = append(hConfig.CapAdd, "LINUX_IMMUTABLE") respCC, err := m.cli.ContainerCreate(m.ctx, &cConfig, &hConfig, &nConfig, nil, "") if err != nil { @@ -753,6 +754,7 @@ func (m *Manager) startContainers(build *BuildMetadata, instance *InstanceMetada m.log.debug("inserting custom seccomp profile") hConfig.SecurityOpt = append(hConfig.SecurityOpt, "seccomp:"+seccompPolicy) } + hConfig.CapAdd = append(hConfig.CapAdd, "LINUX_IMMUTABLE") nConfig := network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{ From fad685ac4f2bb6ae1b263a2b73ff5e82dd1d037a Mon Sep 17 00:00:00 2001 From: Palash Oswal Date: Thu, 14 Aug 2025 13:07:03 -0400 Subject: [PATCH 2/2] Updated capability to map directly to challenge --- cmgr/database.go | 4 ++++ cmgr/database_challenges.go | 15 ++++++++++----- cmgr/docker.go | 5 +++-- cmgr/types.go | 1 + 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/cmgr/database.go b/cmgr/database.go index c6af318..e98c844 100644 --- a/cmgr/database.go +++ b/cmgr/database.go @@ -166,6 +166,7 @@ const schemaQuery string = ` nonewprivileges INTEGER NOT NULL CHECK(nonewprivileges == 0 OR nonewprivileges == 1), diskquota TEXT NOT NULL, cgroupparent TEXT NOT NULL, + capimmutable INTEGER NOT NULL CHECK(capimmutable == 0 OR capimmutable == 1) DEFAULT 0, FOREIGN KEY (challenge) REFERENCES challenges (id) ON UPDATE CASCADE ON DELETE CASCADE );` @@ -193,6 +194,9 @@ func (m *Manager) initDatabase() error { m.log.errorf("could not set database schema: %s", err) return err } + // Best-effort migration for older DBs: add capimmutable if missing. + // If the column already exists, this will error and we ignore it. + _, _ = db.Exec("ALTER TABLE containerOptions ADD COLUMN capimmutable INTEGER NOT NULL DEFAULT 0;") var fkeysEnforced bool err = db.QueryRow("PRAGMA foreign_keys;").Scan(&fkeysEnforced) diff --git a/cmgr/database_challenges.go b/cmgr/database_challenges.go index 5c69deb..d90043b 100644 --- a/cmgr/database_challenges.go +++ b/cmgr/database_challenges.go @@ -94,7 +94,7 @@ func (m *Manager) lookupChallengeMetadata(challenge ChallengeId) (*ChallengeMeta containerOptions := new([]dbContainerOptions) if err == nil { - err = txn.Select(containerOptions, "SELECT host, init, cpus, memory, ulimits, pidslimit, readonlyrootfs, droppedcaps, nonewprivileges, diskquota, cgroupparent FROM containerOptions WHERE challenge=?", challenge) + err = txn.Select(containerOptions, "SELECT host, init, cpus, memory, ulimits, pidslimit, readonlyrootfs, droppedcaps, nonewprivileges, diskquota, cgroupparent, capimmutable FROM containerOptions WHERE challenge=?", challenge) } for _, dbOpts := range *containerOptions { cOpts, err := newFromDbContainerOptions(dbOpts) @@ -279,7 +279,7 @@ func (m *Manager) addChallenges(addedChallenges []*ChallengeMetadata) []error { break } m.log.debugf("%s%s: %v", metadata.Id, host_str, dbOpts) - _, err = txn.Exec("INSERT INTO containerOptions(challenge, host, init, cpus, memory, ulimits, pidslimit, readonlyrootfs, droppedcaps, nonewprivileges, diskquota, cgroupparent) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);", + _, err = txn.Exec("INSERT INTO containerOptions(challenge, host, init, cpus, memory, ulimits, pidslimit, readonlyrootfs, droppedcaps, nonewprivileges, diskquota, cgroupparent, capimmutable) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);", metadata.Id, host, dbOpts.Init, @@ -291,7 +291,8 @@ func (m *Manager) addChallenges(addedChallenges []*ChallengeMetadata) []error { dbOpts.DroppedCaps, dbOpts.NoNewPrivileges, dbOpts.DiskQuota, - dbOpts.CgroupParent) + dbOpts.CgroupParent, + dbOpts.CapImmutable) if err != nil { m.log.error(err) err = txn.Rollback() @@ -542,7 +543,7 @@ func (m *Manager) updateChallenges(updatedChallenges []*ChallengeMetadata, rebui } break } - _, err = txn.Exec("INSERT INTO containerOptions(challenge, host, init, cpus, memory, ulimits, pidslimit, readonlyrootfs, droppedcaps, nonewprivileges, diskquota, cgroupparent) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);", + _, err = txn.Exec("INSERT INTO containerOptions(challenge, host, init, cpus, memory, ulimits, pidslimit, readonlyrootfs, droppedcaps, nonewprivileges, diskquota, cgroupparent, capimmutable) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);", metadata.Id, host, dbOpts.Init, @@ -554,7 +555,8 @@ func (m *Manager) updateChallenges(updatedChallenges []*ChallengeMetadata, rebui dbOpts.DroppedCaps, dbOpts.NoNewPrivileges, dbOpts.DiskQuota, - dbOpts.CgroupParent) + dbOpts.CgroupParent, + dbOpts.CapImmutable) if err != nil { m.log.error(err) err = txn.Rollback() @@ -690,6 +692,7 @@ type dbContainerOptions struct { NoNewPrivileges bool DiskQuota string CgroupParent string + CapImmutable bool } func newFromDbContainerOptions(dbOpts dbContainerOptions) (ContainerOptions, error) { @@ -724,6 +727,7 @@ func newFromDbContainerOptions(dbOpts dbContainerOptions) (ContainerOptions, err cOpts.DiskQuota = dbOpts.DiskQuota cOpts.CgroupParent = dbOpts.CgroupParent + cOpts.CapImmutable = dbOpts.CapImmutable return cOpts, nil } @@ -760,6 +764,7 @@ func (cOpts ContainerOptions) toDbContainerOptions() (dbContainerOptions, error) dbOpts.DiskQuota = cOpts.DiskQuota dbOpts.CgroupParent = cOpts.CgroupParent + dbOpts.CapImmutable = cOpts.CapImmutable return dbOpts, nil } diff --git a/cmgr/docker.go b/cmgr/docker.go index 1350b40..aed1af6 100644 --- a/cmgr/docker.go +++ b/cmgr/docker.go @@ -447,7 +447,6 @@ func (m *Manager) executeBuild(cMeta *ChallengeMetadata, bMeta *BuildMetadata, b m.log.debug("inserting custom seccomp profile") hConfig.SecurityOpt = []string{"seccomp:" + seccompPolicy} } - hConfig.CapAdd = append(hConfig.CapAdd, "LINUX_IMMUTABLE") respCC, err := m.cli.ContainerCreate(m.ctx, &cConfig, &hConfig, &nConfig, nil, "") if err != nil { @@ -754,7 +753,9 @@ func (m *Manager) startContainers(build *BuildMetadata, instance *InstanceMetada m.log.debug("inserting custom seccomp profile") hConfig.SecurityOpt = append(hConfig.SecurityOpt, "seccomp:"+seccompPolicy) } - hConfig.CapAdd = append(hConfig.CapAdd, "LINUX_IMMUTABLE") + if cOpts.CapImmutable { + hConfig.CapAdd = append(hConfig.CapAdd, "LINUX_IMMUTABLE") + } nConfig := network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{ diff --git a/cmgr/types.go b/cmgr/types.go index 4dd07cb..dc91796 100644 --- a/cmgr/types.go +++ b/cmgr/types.go @@ -69,6 +69,7 @@ type ContainerOptions struct { NoNewPrivileges bool `json:"nonewprivileges,omitempty" yaml:"nonewprivileges"` DiskQuota string `json:"diskquota,omitempty" yaml:"diskquota"` CgroupParent string `json:"cgroupparent,omitempty" yaml:"cgroupparent"` + CapImmutable bool `json:"cap_immutable,omitempty" yaml:"cap_immutable"` } type ChallengeOptions struct {