fix: don't set xattrs while decompressing extensions

When decompressing extensions, we might not be able to set xattrs (e.g.
running rootless), so instead of setting xattrs, save them in memory and
push to mksquashfs as pseudo definitions.

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
(cherry picked from commit d697f5538a7a624a1ac7bafdfebc67dd9418c434)
This commit is contained in:
Andrey Smirnov 2026-04-04 16:40:08 +04:00
parent 9cc735588b
commit 9be7bc0250
No known key found for this signature in database
GPG Key ID: 322C6F63F594CE7C
10 changed files with 92 additions and 40 deletions

View File

@ -12,6 +12,8 @@ import (
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
"github.com/siderolabs/talos/pkg/machinery/imager/quirks"
)
@ -47,7 +49,7 @@ func initramfsPaths(quirks quirks.Quirks) []string {
//
// Components which should be placed to the initramfs are moved to the initramfsPath.
// Ucode components are moved into a separate designated location.
func (ext *Extension) Compress(ctx context.Context, squashPath, initramfsPath string, quirks quirks.Quirks) (string, error) {
func (ext *Extension) Compress(ctx context.Context, squashPath, initramfsPath string, quirks quirks.Quirks, xattrsMap map[string]string) (string, error) {
if err := ext.handleUcode(initramfsPath, quirks); err != nil {
return "", err
}
@ -70,12 +72,62 @@ func (ext *Extension) Compress(ctx context.Context, squashPath, initramfsPath st
compressArgs = []string{"-comp", "xz", "-Xdict-size", "100%"}
}
cmd := exec.CommandContext(ctx, "mksquashfs", append([]string{ext.RootfsPath(), squashPath, "-all-root", "-noappend", "-no-progress"}, compressArgs...)...)
pseudoFlags, err := ext.xattrPseudoFlags(xattrsMap)
if err != nil {
return "", err
}
cmd := exec.CommandContext(ctx, "mksquashfs",
slices.Concat(
[]string{
ext.RootfsPath(),
squashPath,
"-all-root",
"-noappend",
"-no-progress",
},
compressArgs,
pseudoFlags,
)...)
cmd.Stderr = os.Stderr
return squashPath, cmd.Run()
}
// xattrPseudoFlags returns a list of pseudo-flag strings for the xattrs of the extension.
//
// These pseudo-flags are used to indicate the presence of specific SELinux xattrs on files within the extension.
// The mksquashfs tool will use that to mark files with xattrs instead of reading it from the filesystem.
func (ext *Extension) xattrPseudoFlags(xattrsMap map[string]string) ([]string, error) {
if xattrsMap == nil {
return nil, nil
}
flags := []string{"-xattrs-exclude", ".*"} // exclude all xattrs by default
for path, xattrValue := range xattrsMap {
if strings.HasPrefix(path, ext.RootfsPath()) {
// check if the file exists still (it might have been moved to the initramfs)
if _, err := os.Lstat(path); os.IsNotExist(err) {
continue
}
relativePath, err := filepath.Rel(ext.RootfsPath(), path)
if err != nil {
return nil, err
}
if relativePath == "." {
relativePath = "/"
}
flags = append(flags, "-p", fmt.Sprintf("%s x security.selinux=%s", relativePath, xattrValue))
}
}
return flags, nil
}
func appendBlob(dst io.Writer, srcPath string) error {
src, err := os.Open(srcPath)
if err != nil {

View File

@ -30,7 +30,7 @@ func TestCompress(t *testing.T) {
ext := exts[0]
squashDest, initramfsDest := t.TempDir(), t.TempDir()
squashFile, err := ext.Compress(t.Context(), squashDest, initramfsDest, quirks.New(""))
squashFile, err := ext.Compress(t.Context(), squashDest, initramfsDest, quirks.New(""), nil)
assert.NoError(t, err)
assert.FileExists(t, squashFile)

View File

@ -29,21 +29,3 @@ func TarGz(ctx context.Context, rootPath string, output io.Writer, walkerOptions
return zw.Close()
}
// UntarGz extracts .tar.gz archive to the rootPath.
func UntarGz(ctx context.Context, input io.Reader, rootPath string) error {
zr, err := gzip.NewReader(input)
if err != nil {
return err
}
//nolint:errcheck
defer zr.Close()
err = Untar(ctx, zr, rootPath)
if err != nil {
return err
}
return zr.Close()
}

View File

@ -13,15 +13,19 @@ import (
"os"
"path/filepath"
"github.com/pkg/xattr"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/safepath"
)
// Untar extracts .tar archive from r into filesystem under rootPath.
//
//nolint:gocyclo
func Untar(ctx context.Context, r io.Reader, rootPath string) error {
// If xattrsMap is not nil, it will be filled with paths and their corresponding
// SELinux xattr values instead of setting the xattrs on the filesystem.
//
// If xattrsMap is nil, the function will ignore SELinux xattr values.
//
//nolint:gocyclo,cyclop
func Untar(ctx context.Context, r io.Reader, rootPath string, xattrsMap map[string]string) error {
tr := tar.NewReader(r)
for {
@ -87,10 +91,8 @@ func Untar(ctx context.Context, r io.Reader, rootPath string) error {
}
}
if hdr.PAXRecords["SCHILY.xattr.security.selinux"] != "" {
if err = xattr.LSet(path, "security.selinux", []byte(hdr.PAXRecords["SCHILY.xattr.security.selinux"])); err != nil {
return fmt.Errorf("error setting selinux xattr for %q: %w", path, err)
}
if hdr.PAXRecords[constants.TarPaxHeaderSELinux] != "" && xattrsMap != nil {
xattrsMap[path] = hdr.PAXRecords[constants.TarPaxHeaderSELinux]
}
}

View File

@ -112,10 +112,10 @@ func BuildEmbeddedConfigExtension(machineConfig []byte) (io.Reader, error) {
if err = tw.WriteHeader(&tar.Header{
Name: filepath.Join("rootfs", constants.EmbeddedConfigDirectory, constants.ConfigFilename),
Typeflag: tar.TypeReg,
Mode: 0o000,
Mode: 0o400,
Size: int64(len(machineConfig)),
PAXRecords: map[string]string{
"SCHILY.xattr.security.selinux": constants.StateSelinuxLabel,
constants.TarPaxHeaderSELinux: constants.StateSelinuxLabel,
},
}); err != nil {
return nil, fmt.Errorf("failed to write embedded header: %w", err)

View File

@ -31,6 +31,8 @@ type Builder struct {
Printf func(format string, v ...any)
// Quirks for the Talos version being used.
Quirks quirks.Quirks
// XAttrsMap is used to store paths and their corresponding SELinux xattr values during extraction of extensions.
XAttrsMap map[string]string
}
// Build rebuilds the initramfs.xz with extensions.
@ -122,7 +124,7 @@ func (builder *Builder) compressExtensions(ctx context.Context, extensions []*ex
builder.Printf("compressing system extensions")
for _, ext := range extensions {
path, err := ext.Compress(ctx, tempDir, tempDir, builder.Quirks)
path, err := ext.Compress(ctx, tempDir, tempDir, builder.Quirks, builder.XAttrsMap)
if err != nil {
return nil, fmt.Errorf("error compressing extension %q: %w", ext.Manifest.Metadata.Name, err)
}

View File

@ -47,12 +47,16 @@ type Imager struct {
sdBootPath string
ukiPath string
// xattrsMap is used to store paths and their corresponding SELinux xattr values during extraction of extensions.
xattrsMap map[string]string
}
// New creates a new Imager.
func New(prof profile.Profile) (*Imager, error) {
return &Imager{
prof: prof,
prof: prof,
xattrsMap: map[string]string{},
}, nil
}
@ -198,7 +202,7 @@ func (i *Imager) handleOverlay(ctx context.Context, report *reporter.Reporter) e
return fmt.Errorf("failed to create overlay directory: %w", err)
}
if err := i.prof.Overlay.Image.Extract(ctx, tempOverlayPath, runtime.GOARCH, progressPrintf(report, reporter.Update{Message: "pulling overlay...", Status: reporter.StatusRunning})); err != nil {
if err := i.prof.Overlay.Image.Extract(ctx, tempOverlayPath, runtime.GOARCH, progressPrintf(report, reporter.Update{Message: "pulling overlay...", Status: reporter.StatusRunning}), nil); err != nil {
return err
}
@ -316,7 +320,7 @@ func (i *Imager) buildInitramfs(ctx context.Context, report *reporter.Reporter)
return fmt.Errorf("failed to create extension directory: %w", err)
}
if err := ext.Extract(ctx, extensionDir, i.prof.Arch, printf); err != nil {
if err := ext.Extract(ctx, extensionDir, i.prof.Arch, printf, i.xattrsMap); err != nil {
return err
}
}
@ -328,6 +332,7 @@ func (i *Imager) buildInitramfs(ctx context.Context, report *reporter.Reporter)
ExtensionTreePath: extensionsCheckoutDir,
Printf: printf,
Quirks: quirks.New(i.prof.Version),
XAttrsMap: i.xattrsMap,
}
if err := builder.Build(ctx); err != nil {

View File

@ -97,7 +97,7 @@ func (i *Imager) outISO(ctx context.Context, path string, report *reporter.Repor
return err
}
if err := i.prof.Input.ImageCache.Extract(ctx, filepath.Join(scratchSpace, "imagecache"), i.prof.Arch, printf); err != nil {
if err := i.prof.Input.ImageCache.Extract(ctx, filepath.Join(scratchSpace, "imagecache"), i.prof.Arch, printf, nil); err != nil {
return err
}
}
@ -358,7 +358,7 @@ func (i *Imager) buildImage(ctx context.Context, path string, printf func(string
return err
}
if err := i.prof.Input.ImageCache.Extract(ctx, imageCacheDir, i.prof.Arch, printf); err != nil {
if err := i.prof.Input.ImageCache.Extract(ctx, imageCacheDir, i.prof.Arch, printf, nil); err != nil {
return err
}
@ -540,6 +540,7 @@ func (i *Imager) outInstaller(ctx context.Context, path string, report *reporter
tempOverlayPath,
i.prof.Arch,
progressPrintf(report, reporter.Update{Message: "pulling overlay for installer...", Status: reporter.StatusRunning}),
nil,
); err != nil {
return err
}

View File

@ -320,7 +320,12 @@ func (c *ContainerAsset) pullFromOCI(arch string) (v1.Image, error) {
}
// Extract the container asset to the path.
func (c *ContainerAsset) Extract(ctx context.Context, destination, arch string, printf func(string, ...any)) error {
func (c *ContainerAsset) Extract(
ctx context.Context,
destination, arch string,
printf func(string, ...any),
xattrsMap map[string]string,
) error {
if c.TarballPath != "" {
in, err := os.Open(c.TarballPath)
if err != nil {
@ -331,7 +336,7 @@ func (c *ContainerAsset) Extract(ctx context.Context, destination, arch string,
printf("extracting %s...", c.TarballPath)
return archiver.Untar(ctx, in, destination)
return archiver.Untar(ctx, in, destination, xattrsMap)
}
img, err := c.Pull(ctx, arch, printf)
@ -356,7 +361,7 @@ func (c *ContainerAsset) Extract(ctx context.Context, destination, arch string,
})
eg.Go(func() error {
if untarErr := archiver.Untar(ctx, r, destination); untarErr != nil {
if untarErr := archiver.Untar(ctx, r, destination, xattrsMap); untarErr != nil {
r.CloseWithError(untarErr)
return untarErr

View File

@ -1329,6 +1329,9 @@ const (
// ImageLabelVerified is the label key for the verified image label.
ImageLabelVerified = "talos.dev/verified"
// TarPaxHeaderSELinux is the name of the PAX header for storing SELinux labels.
TarPaxHeaderSELinux = "SCHILY.xattr.security.selinux"
)
// names of variable that can be substituted in the talos.config kernel parameter.