|
| 1 | +// SPDX-FileCopyrightText: Copyright 2025 Stacklok, Inc. |
| 2 | +// SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +//go:build linux |
| 5 | + |
| 6 | +package xattr |
| 7 | + |
| 8 | +import ( |
| 9 | + "fmt" |
| 10 | + "log/slog" |
| 11 | + "os" |
| 12 | + |
| 13 | + "golang.org/x/sys/unix" |
| 14 | +) |
| 15 | + |
| 16 | +// SetOverrideStat sets the user.containers.override_stat xattr on path |
| 17 | +// so that libkrun's virtiofs server reports the given uid, gid, |
| 18 | +// and mode to the guest instead of the real host values. |
| 19 | +// Errors are logged at debug level and silently ignored. |
| 20 | +// |
| 21 | +// On Linux the kernel restricts user.* xattrs to regular files and |
| 22 | +// directories (fs/xattr.c:xattr_permission). Symlinks, named pipes, |
| 23 | +// sockets, and device nodes are silently skipped. |
| 24 | +func SetOverrideStat(path string, uid, gid int, mode os.FileMode) { |
| 25 | + // The Linux kernel refuses user.* xattrs on anything other than |
| 26 | + // regular files and directories. Skip early to avoid EPERM. |
| 27 | + if mode&(os.ModeSymlink|os.ModeNamedPipe|os.ModeSocket|os.ModeDevice) != 0 { |
| 28 | + return |
| 29 | + } |
| 30 | + |
| 31 | + unixMode := goFileModeToPosix(mode) |
| 32 | + val := fmt.Sprintf("%d:%d:0%o", uid, gid, unixMode) |
| 33 | + if err := unix.Lsetxattr(path, overrideKey, []byte(val), 0); err != nil { |
| 34 | + slog.Debug("setxattr override_stat failed", "path", path, "err", err) |
| 35 | + } |
| 36 | +} |
| 37 | + |
| 38 | +// SetOverrideStatFromPath sets the override_stat xattr by reading the |
| 39 | +// file's current mode via Lstat. Useful when you know the intended |
| 40 | +// uid/gid but the mode comes from the existing file on disk. |
| 41 | +func SetOverrideStatFromPath(path string, uid, gid int) { |
| 42 | + info, err := os.Lstat(path) |
| 43 | + if err != nil { |
| 44 | + slog.Debug("lstat for override_stat failed", "path", path, "err", err) |
| 45 | + return |
| 46 | + } |
| 47 | + SetOverrideStat(path, uid, gid, info.Mode()) |
| 48 | +} |
| 49 | + |
| 50 | +// CopyOverrideStat copies the user.containers.override_stat xattr from |
| 51 | +// src to dst. No-op if src has no such xattr. Errors are silently ignored. |
| 52 | +func CopyOverrideStat(src, dst string) { |
| 53 | + buf := make([]byte, 256) |
| 54 | + n, err := unix.Lgetxattr(src, overrideKey, buf) |
| 55 | + if err != nil || n == 0 { |
| 56 | + return |
| 57 | + } |
| 58 | + if err := unix.Lsetxattr(dst, overrideKey, buf[:n], 0); err != nil { |
| 59 | + slog.Debug("copy override_stat xattr failed", "dst", dst, "err", err) |
| 60 | + } |
| 61 | +} |
0 commit comments