diff --git a/internal/chroot.go b/internal/chroot.go new file mode 100644 index 00000000..20d7422d --- /dev/null +++ b/internal/chroot.go @@ -0,0 +1,64 @@ +// Copyright 2022 buildkit-syft-scanner 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 +// +// 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 internal + +import ( + "syscall" +) + +// withChroot executes a target function inside a chroot environment. +// +// After the function is executed, the chroot is exited and the working +// directory is restored. +func withChroot(dir string, f func() error) error { + // save previous state + oldfd, err := syscall.Open("/", syscall.O_RDONLY, 0) + if err != nil { + return err + } + oldwd, err := syscall.Getwd() + if err != nil { + return err + } + + // set new state + if err := syscall.Chroot(dir); err != nil { + return err + } + if err := syscall.Chdir("/"); err != nil { + return err + } + + // execute target function + err = f() + + // restore previous state + if err2 := syscall.Fchdir(oldfd); err2 != nil && err == nil { + return err2 + } + if err2 := syscall.Chroot("."); err2 != nil && err == nil { + return err2 + } + if err2 := syscall.Chdir(oldwd); err2 != nil && err == nil { + return err2 + } + + // cleanup + if err2 := syscall.Close(oldfd); err2 != nil && err == nil { + return err2 + } + + return err +} diff --git a/internal/target.go b/internal/target.go index 3634a055..a3b1af43 100644 --- a/internal/target.go +++ b/internal/target.go @@ -34,37 +34,52 @@ func (t Target) Name() string { } func (t Target) Scan() (sbom.SBOM, error) { - inputSrc := "dir:" + t.Path - input, err := source.ParseInput(inputSrc, "", false) - if err != nil { - return sbom.SBOM{}, fmt.Errorf("failed to parse user input %q: %w", inputSrc, err) - } + // HACK: execute the scan inside a chroot, to ensure that symlinks are + // correctly resolved internally to the mounted image (instead of + // redirecting to the host). + // + // To avoid this, syft needs to support a mode of execution that scans + // unpacked container filesystems, see https://github.com/anchore/syft/issues/1359. - src, cleanup, err := source.New(*input, nil, nil) - if err != nil { - return sbom.SBOM{}, fmt.Errorf("failed to construct source from user input %q: %w", inputSrc, err) - } - src.Metadata.Name = t.Name() - if cleanup != nil { - defer cleanup() - } + var result sbom.SBOM + err := withChroot(t.Path, func() error { + inputSrc := "dir:/" + input, err := source.ParseInput(inputSrc, "", false) + if err != nil { + return fmt.Errorf("failed to parse user input %q: %w", inputSrc, err) + } - result := sbom.SBOM{ - Source: src.Metadata, - Descriptor: sbom.Descriptor{ - Name: "syft", - Version: version.SyftVersion, - }, - } + src, cleanup, err := source.New(*input, nil, nil) + if err != nil { + return fmt.Errorf("failed to construct source from user input %q: %w", inputSrc, err) + } + src.Metadata.Name = t.Name() + if cleanup != nil { + defer cleanup() + } + + result = sbom.SBOM{ + Source: src.Metadata, + Descriptor: sbom.Descriptor{ + Name: "syft", + Version: version.SyftVersion, + }, + } + + packageCatalog, relationships, theDistro, err := syft.CatalogPackages(src, cataloger.DefaultConfig()) + if err != nil { + return err + } - packageCatalog, relationships, theDistro, err := syft.CatalogPackages(src, cataloger.DefaultConfig()) + result.Artifacts.PackageCatalog = packageCatalog + result.Artifacts.LinuxDistribution = theDistro + result.Relationships = relationships + + return nil + }) if err != nil { return sbom.SBOM{}, err } - result.Artifacts.PackageCatalog = packageCatalog - result.Artifacts.LinuxDistribution = theDistro - result.Relationships = relationships - return result, nil }