Files
Jing Chen ff3425ac1f Fix crash and resource-leak bugs in the containerd shim
- runsc/debug.go: the SIGUSR2 stack dumper never exited its loop, so one
signal pinned a core for the life of the shim.
- proc/init_state.go: the start-failure path closed p.io unconditionally,
but Init.Create leaves it nil for terminal containers. The nil deref
killed the shim, and with it the pod.
- proc/utils.go: getLastRuntimeError leaked the log file it opened, on
every failed runsc command.
- utils/volumes.go: configureShm wrote into a nil annotation map, which
IsSandbox permits.
- runsc/epoll.go: the cgroups v1 OOM watcher panicked on the first
publish failure, which is normal under CRI-O. Reuse forward's leniency
check; the panic under containerd is kept.
- runsccmd/runsc.go: cmdOutput returned slices aliasing sync.Pool buffers
that putBuf immediately recycled.
2026-08-26 22:47:36 +00:00

146 lines
3.3 KiB
Go

// Copyright 2018 The containerd Authors.
// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
// +build linux
package runsc
import (
"context"
"fmt"
"sync"
cgroups "github.com/containerd/cgroups/v3/cgroup1"
"github.com/containerd/containerd/v2/core/events"
"github.com/containerd/containerd/v2/core/runtime"
"github.com/containerd/log"
"golang.org/x/sys/unix"
)
func newOOMEpoller(publisher events.Publisher) (*epoller, error) {
fd, err := unix.EpollCreate1(unix.EPOLL_CLOEXEC)
if err != nil {
return nil, err
}
return &epoller{
fd: fd,
publisher: publisher,
set: make(map[uintptr]*item),
}, nil
}
type epoller struct {
mu sync.Mutex
fd int
publisher events.Publisher
set map[uintptr]*item
}
type item struct {
id string
cg cgroups.Cgroup
}
func (e *epoller) Close() error {
return unix.Close(e.fd)
}
func (e *epoller) run(ctx context.Context) {
var events [128]unix.EpollEvent
for {
select {
case <-ctx.Done():
e.Close()
return
default:
n, err := unix.EpollWait(e.fd, events[:], -1)
if err != nil {
if err == unix.EINTR || err == unix.EAGAIN {
continue
}
// Should not happen.
panic(fmt.Errorf("cgroups: epoll wait: %w", err))
}
for i := 0; i < n; i++ {
e.process(ctx, uintptr(events[i].Fd))
}
}
}
}
func (e *epoller) add(id string, cgx any) error {
e.mu.Lock()
defer e.mu.Unlock()
cg, ok := cgx.(cgroups.Cgroup)
if !ok {
return fmt.Errorf("expected cgroups.Cgroup, got: %T", cgx)
}
fd, err := cg.OOMEventFD()
if err != nil {
return err
}
e.set[fd] = &item{
id: id,
cg: cg,
}
event := unix.EpollEvent{
Fd: int32(fd),
Events: unix.EPOLLHUP | unix.EPOLLIN | unix.EPOLLERR,
}
return unix.EpollCtl(e.fd, unix.EPOLL_CTL_ADD, int(fd), &event)
}
// isOOM is not implemented for cgroups v1. The epoll-based eventfd mechanism
// used in v1 provides direct kernel notification and is not subject to the
// same race condition as v2's inotify-based EventChan.
func (e *epoller) isOOM(id string) bool {
return false
}
func (e *epoller) process(ctx context.Context, fd uintptr) {
flush(fd)
e.mu.Lock()
i, ok := e.set[fd]
if !ok {
e.mu.Unlock()
return
}
e.mu.Unlock()
if i.cg.State() == cgroups.Deleted {
e.mu.Lock()
delete(e.set, fd)
e.mu.Unlock()
unix.Close(int(fd))
return
}
if err := e.publisher.Publish(ctx, runtime.TaskOOMEventTopic, &TaskOOM{
ContainerID: i.id,
}); err != nil {
if publishFailureIsFatal() {
// Should not happen when an event sink is configured.
panic(fmt.Errorf("publish OOM event: %w", err))
}
log.L.Warningf("Failed to publish OOM event (no containerd event sink): %v", err)
}
}
func flush(fd uintptr) error {
var buf [8]byte
_, err := unix.Read(int(fd), buf[:])
return err
}