mirror of
https://github.com/containerd/containerd.git
synced 2026-08-07 16:41:22 +00:00
fix(oci): handle absolute symlinks in rootfs user lookup
Go 1.24 introduced stricter checks for os.DirFS (via os.Root), which causes failures when /etc/passwd or /etc/group are absolute symlinks pointing outside the mount root (common in NixOS). This patch introduces a helper that detects absolute symlinks and resolves them relative to the rootfs before opening, preventing the 'path escapes from parent' error. Fixes #12683 Signed-off-by: Paulo Oliveira <paulo.hco47@gmail.com>
This commit is contained in:
@@ -1152,7 +1152,7 @@ func UserFromPath(root string, filter func(user.User) bool) (user.User, error) {
|
||||
// UserFromFS inspects the user object using /etc/passwd in the specified fs.FS.
|
||||
// filter can be nil.
|
||||
func UserFromFS(root fs.FS, filter func(user.User) bool) (user.User, error) {
|
||||
f, err := root.Open("etc/passwd")
|
||||
f, err := openUserFile(root, "etc/passwd")
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
@@ -1184,7 +1184,7 @@ func GIDFromPath(root string, filter func(user.Group) bool) (gid uint32, err err
|
||||
// GIDFromFS inspects the GID using /etc/group in the specified fs.FS.
|
||||
// filter can be nil.
|
||||
func GIDFromFS(root fs.FS, filter func(user.Group) bool) (gid uint32, err error) {
|
||||
f, err := root.Open("etc/group")
|
||||
f, err := openUserFile(root, "etc/group")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -1816,3 +1816,43 @@ func WithWindowsNetworkNamespace(ns string) SpecOpts {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// readLinker defines the ReadLink method locally.
|
||||
// We keep this shim to ensure compatibility with build environments where
|
||||
// the standard library's fs.ReadLinkFS interface is not yet available or recognized.
|
||||
type readLinker interface {
|
||||
ReadLink(name string) (string, error)
|
||||
}
|
||||
|
||||
// openUserFile attempts to open a file within the root fs.
|
||||
// It handles cases where the file is an absolute symlink (e.g., NixOS /etc/passwd -> /nix/store/...),
|
||||
// which triggers "path escapes from parent" errors in Go 1.24+ due to stricter os.DirFS validation.
|
||||
func openUserFile(root fs.FS, name string) (fs.File, error) {
|
||||
f, err := root.Open(name)
|
||||
if err == nil {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Check if the FS implements our local ReadLink interface.
|
||||
// We use a local interface instead of fs.ReadLinkFS to avoid strict dependency
|
||||
// issues in some build environments.
|
||||
if lfs, ok := root.(readLinker); ok {
|
||||
if target, lerr := lfs.ReadLink(name); lerr == nil {
|
||||
// Use filepath.IsAbs to handle platform-agnostic absolute path checks
|
||||
if filepath.IsAbs(target) {
|
||||
// Re-anchor the absolute path to the root.
|
||||
// e.g. /nix/store/... becomes nix/store/... (relative to root fs)
|
||||
// We use filepath.Rel to safely strip the leading separator.
|
||||
rel, rerr := filepath.Rel(string(filepath.Separator), target)
|
||||
if rerr == nil {
|
||||
// filepath.Rel might return OS-specific separators (backslashes on Windows).
|
||||
// fs.Open strictly expects forward slashes, so we convert it.
|
||||
return root.Open(filepath.ToSlash(rel))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the original error if we couldn't resolve it
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ package oci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
@@ -325,3 +328,36 @@ func TestWithPrivileged(t *testing.T) {
|
||||
t.Error("Did not find mount for cgroupfs")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenUserFile_AbsoluteSymlink(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
targetName := "passwd"
|
||||
targetPath := filepath.Join(tmpDir, targetName)
|
||||
expectedContent := []byte("root:x:0:0:root:/root:/bin/bash")
|
||||
if err := os.WriteFile(targetPath, expectedContent, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
linkName := "abs_link"
|
||||
linkPath := filepath.Join(tmpDir, linkName)
|
||||
if err := os.Symlink(targetPath, linkPath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rootFS := os.DirFS(tmpDir)
|
||||
|
||||
f, err := openUserFile(rootFS, linkName)
|
||||
if err != nil {
|
||||
t.Fatalf("openUserFile failed on absolute symlink: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
content, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(content) != string(expectedContent) {
|
||||
t.Errorf("expected content %q, got %q", string(expectedContent), string(content))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user