Merge pull request #5116 from lifubang/backport-1.4-5091

[1.4] Fix runc exec vs go1.26 + older kernel
This commit is contained in:
Kir Kolyshkin
2026-02-12 11:58:04 -08:00
committed by GitHub
4 changed files with 53 additions and 3 deletions
+6 -2
View File
@@ -25,7 +25,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ubuntu-24.04, ubuntu-24.04-arm] os: [ubuntu-24.04, ubuntu-24.04-arm]
go-version: [1.24.x, 1.25.x] go-version: [1.24.x, 1.25.x, 1.26.x]
rootless: ["rootless", ""] rootless: ["rootless", ""]
race: ["-race", ""] race: ["-race", ""]
criu: ["", "criu-dev"] criu: ["", "criu-dev"]
@@ -34,11 +34,15 @@ jobs:
# (need to compile criu) and don't add much value/coverage. # (need to compile criu) and don't add much value/coverage.
- criu: criu-dev - criu: criu-dev
go-version: 1.24.x go-version: 1.24.x
- criu: criu-dev
go-version: 1.25.x
- criu: criu-dev - criu: criu-dev
rootless: rootless rootless: rootless
# Do race detection only on latest Go. # Do race detection only with latest stable Go version.
- race: -race - race: -race
go-version: 1.24.x go-version: 1.24.x
- race: -race
go-version: 1.25.x
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
+37
View File
@@ -0,0 +1,37 @@
package libcontainer
import "os/exec"
// cloneCmd creates a copy of exec.Cmd. It is needed because cmd.Start
// must only be used once, and go1.26 actually enforces that (see
// https://go-review.googlesource.com/c/go/+/728642). The implementation
// is similar to
//
// cmd = *c
// return &cmd
//
// except it does not copy private fields, or fields populated
// after the call to cmd.Start.
//
// NOTE if Go will add exec.Cmd.Clone, we should switch to it.
func cloneCmd(c *exec.Cmd) *exec.Cmd {
cmd := &exec.Cmd{
Path: c.Path,
Args: c.Args,
Env: c.Env,
Dir: c.Dir,
Stdin: c.Stdin,
Stdout: c.Stdout,
Stderr: c.Stderr,
ExtraFiles: c.ExtraFiles,
SysProcAttr: c.SysProcAttr,
// Don't copy Process, ProcessState, Err since
// these fields are populated after the start.
// Technically, we do not use Cancel or WaitDelay,
// but they are here for the sake of completeness.
Cancel: c.Cancel,
WaitDelay: c.WaitDelay,
}
return cmd
}
+6
View File
@@ -528,6 +528,12 @@ func (c *Container) newParentProcess(p *Process) (parentProcess, error) {
} }
cmd := exec.Command(exePath, "init") cmd := exec.Command(exePath, "init")
// Theoretically, exec.Command can set cmd.Err. Practically, this
// should never happen (Linux, Go <= 1.26, exePath is absolute),
// but in the unlikely case it just did, let's fail early.
if cmd.Err != nil {
return nil, fmt.Errorf("exec.Command: %w", cmd.Err)
}
cmd.Args[0] = os.Args[0] cmd.Args[0] = os.Args[0]
cmd.Stdin = p.Stdin cmd.Stdin = p.Stdin
cmd.Stdout = p.Stdout cmd.Stdout = p.Stdout
+4 -1
View File
@@ -425,11 +425,14 @@ func (p *setnsProcess) startWithCgroupFD() error {
defer fd.Close() defer fd.Close()
} }
cmdCopy := cloneCmd(p.cmd)
err = p.startWithCPUAffinity() err = p.startWithCPUAffinity()
if err != nil && p.cmd.SysProcAttr.UseCgroupFD { if err != nil && p.cmd.SysProcAttr.UseCgroupFD {
logrus.Debugf("exec with CLONE_INTO_CGROUP failed: %v; retrying without", err) logrus.Debugf("exec with CLONE_INTO_CGROUP failed: %v; retrying without", err)
// SysProcAttr.CgroupFD is never used when UseCgroupFD is unset. // SysProcAttr.CgroupFD is never used when UseCgroupFD is unset.
p.cmd.SysProcAttr.UseCgroupFD = false cmdCopy.SysProcAttr.UseCgroupFD = false
// Must not reuse exec.Cmd.
p.cmd = cmdCopy
err = p.startWithCPUAffinity() err = p.startWithCPUAffinity()
} }