Files
runc/internal/pathrs/root_pathrslite.go
T
Aleksa Sarai a41366e740 openat2: improve resilience on busy systems
Previously, we would see a ~3% failure rate when starting containers
with mounts that contain ".." (which can trigger -EAGAIN). To counteract
this, filepath-securejoin v0.5.1 includes a bump of the internal retry
limit from 32 to 128, which lowers the failure rate to 0.12%.

However, there is still a risk of spurious failure on regular systems.
In order to try to provide more resilience (while avoiding DoS attacks),
this patch also includes an additional retry loop that terminates based
on a deadline rather than retry count. The deadline is 2ms, as my
testing found that ~800us for a single pathrs operation was the longest
latency due to -EAGAIN retries, and that was an outlier compared to the
more common ~400us latencies -- so 2ms should be more than enough for
any real system.

The failure rates above were based on more 50k runs of runc with an
attack script (from libpathrs) running a rename attack on all cores of a
16-core system, which is arguably a worst-case but heavily utilised
servers could likely approach similar results.

Tested-by: Phil Estes <estesp@gmail.com>
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
2025-11-05 18:57:51 +11:00

75 lines
2.4 KiB
Go

// SPDX-License-Identifier: Apache-2.0
/*
* Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
* Copyright (C) 2024-2025 SUSE LLC
*
* 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
*
* http://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.
*/
package pathrs
import (
"fmt"
"os"
"path/filepath"
"github.com/cyphar/filepath-securejoin/pathrs-lite"
"golang.org/x/sys/unix"
"github.com/opencontainers/runc/internal/linux"
)
// OpenInRoot opens the given path inside the root with the provided flags. It
// is effectively shorthand for [securejoin.OpenInRoot] followed by
// [securejoin.Reopen].
func OpenInRoot(root, subpath string, flags int) (*os.File, error) {
handle, err := retryEAGAIN(func() (*os.File, error) {
return pathrs.OpenInRoot(root, subpath)
})
if err != nil {
return nil, err
}
defer handle.Close()
return Reopen(handle, flags)
}
// CreateInRoot creates a new file inside a root (as well as any missing parent
// directories) and returns a handle to said file. This effectively has
// open(O_CREAT|O_NOFOLLOW) semantics. If you want the creation to use O_EXCL,
// include it in the passed flags. The fileMode argument uses unix.* mode bits,
// *not* os.FileMode.
func CreateInRoot(root, subpath string, flags int, fileMode uint32) (*os.File, error) {
dir, filename := filepath.Split(subpath)
if filepath.Join("/", filename) == "/" {
return nil, fmt.Errorf("create in root subpath %q has bad trailing component %q", subpath, filename)
}
dirFd, err := MkdirAllInRootOpen(root, dir, 0o755)
if err != nil {
return nil, err
}
defer dirFd.Close()
// We know that the filename does not have any "/" components, and that
// dirFd is inside the root. O_NOFOLLOW will stop us from following
// trailing symlinks, so this is safe to do. libpathrs's Root::create_file
// works the same way.
flags |= unix.O_CREAT | unix.O_NOFOLLOW
fd, err := linux.Openat(int(dirFd.Fd()), filename, flags, fileMode)
if err != nil {
return nil, err
}
return os.NewFile(uintptr(fd), root+"/"+subpath), nil
}