runcexecutor: share keyring in NoProcessSandbox

In NoProcessSandbox mode the build container runs in the PID namespace
of buildkitd, so a process that a step leaves running in the background
is not killed when the step ends. runc still creates a new session
keyring for every container, and each leftover process keeps that
keyring alive. Each of them takes one key from the per-user quota of the
daemon user (kernel.keys.maxkeys, 200 by default for non-root). Once the
quota is used up, every new container fails with:

    unable to join session keyring: unable to create session key: disk
    quota exceeded

Restarting buildkitd ends the leftover processes and frees the keys, so
long-running rootless daemons hit this again after some days.

Run runc with --no-new-keyring in this mode, so the build containers
use the session keyring of buildkitd. They already share the PID
namespace and procfs with the daemon, and runc treats the per-container
keyring as best-effort isolation, so this removes nothing the mode
provides. ProcessSandbox mode is unchanged.

Fixes #7310
Refs #6247

Signed-off-by: Joao Otavio Manieri <69759037+JoaoManierii@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Joao Otavio ManieriandClaude Opus 5.5 committed 2026-10-21 19:54:31 +00:00
1 parent ce2b38c3a2
commit eac3279c43
2 files changed
+38 -4

No files matched your search

+9 -4
View File
@@ -13,6 +13,7 @@ import (
"github.com/containerd/console"
runc "github.com/containerd/go-runc"
"github.com/moby/buildkit/executor"
"github.com/moby/buildkit/executor/oci"
gatewayapi "github.com/moby/buildkit/frontend/gateway/pb"
"github.com/moby/buildkit/util/bklog"
"github.com/moby/sys/signal"
@@ -34,10 +35,14 @@ func (w *runcExecutor) run(ctx context.Context, id, bundle string, process execu
extraArgs = append(extraArgs, "--keep")
}
_, err := w.runc.Run(ctx, id, bundle, &runc.CreateOpts{
NoPivot: w.noPivot,
Started: started,
IO: io,
ExtraArgs: extraArgs,
NoPivot: w.noPivot,
// Without a PID namespace, processes left behind by a step outlive the
// container and keep its session keyring, until the per-user key quota
// is exhausted and every new container fails to start.
NoNewKeyring: w.processMode == oci.NoProcessSandbox,
Started: started,
IO: io,
ExtraArgs: extraArgs,
})
return err
})
+29
View File
@@ -18,6 +18,7 @@ import (
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/executor"
"github.com/moby/buildkit/executor/oci"
"github.com/moby/buildkit/identity"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/snapshot"
"github.com/moby/buildkit/util/iohelper"
@@ -211,6 +212,34 @@ func TestRuncWorkerNoProcessSandbox(t *testing.T) {
require.Equal(t, string(selfCmdline), stdout.String())
}
func TestRuncWorkerNoProcessSandboxSessionKeyring(t *testing.T) {
t.Parallel()
checkRequirement(t)
workerOpt := newWorkerOpt(t, oci.NoProcessSandbox)
w, err := base.NewWorker(t.Context(), workerOpt)
require.NoError(t, err)
ctx := tests.NewCtx("buildkit-test")
sm, err := session.NewManager()
require.NoError(t, err)
snap := tests.NewBusyboxSourceSnapshot(ctx, t, w, sm)
root, err := w.CacheMgr.New(ctx, snap, nil)
require.NoError(t, err)
id := identity.NewID()
meta := executor.Meta{
Args: []string{"/bin/cat", "/proc/keys"},
Cwd: "/",
}
stdout := bytes.NewBuffer(nil)
stderr := bytes.NewBuffer(nil)
_, err = w.WorkerOpt.Executor.Run(ctx, id, execMount(root, false), nil, executor.ProcessInfo{Meta: meta, Stdout: &iohelper.NopWriteCloser{Writer: stdout}, Stderr: &iohelper.NopWriteCloser{Writer: stderr}}, nil)
require.NoError(t, err, "stdout=%q, stderr=%q", stdout.String(), stderr.String())
// runc names the session keyring it creates for a container "_ses.<id>".
require.NotContains(t, stdout.String(), "_ses."+id)
}
func TestRuncWorkerExec(t *testing.T) {
t.Parallel()
checkRequirement(t)