mirror of
https://github.com/anchore/syft.git
synced 2026-11-04 08:26:38 +01:00
* chore: centralize temp files and prefer streaming IO Catalogers that create temp files ad-hoc can easily forget cleanup, leaking files on disk. Similarly, io.ReadAll is convenient but risks OOM on large or malicious inputs. Introduce internal/tmpdir to manage all cataloger temp storage under a single root directory with automatic cleanup. Prefer streaming parsers (bufio.Scanner, json/yaml.NewDecoder, io.LimitReader) over buffering entire inputs into memory. Add ruleguard rules to enforce both practices going forward. Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com> * chore: go back to old release parsing Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com> * simplify to limit reader in version check Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com> * chore: regex change postponed Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com> * simplify supplement release to limitreader Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com> --------- Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>
261 lines
6.6 KiB
Go
261 lines
6.6 KiB
Go
package nix
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
"github.com/anchore/syft/internal"
|
|
"github.com/anchore/syft/internal/log"
|
|
"github.com/anchore/syft/internal/tmpdir"
|
|
"github.com/anchore/syft/syft/artifact"
|
|
"github.com/anchore/syft/syft/file"
|
|
"github.com/anchore/syft/syft/pkg"
|
|
)
|
|
|
|
var _ dbProcessor = processV10DB
|
|
|
|
func processV10DB(ctx context.Context, config Config, dbLocation file.Location, resolver file.Resolver, catalogerName string) ([]pkg.Package, []artifact.Relationship, error) {
|
|
dbContents, err := resolver.FileContentsByLocation(dbLocation)
|
|
defer internal.CloseAndLogError(dbContents, dbLocation.RealPath)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("unable to read Nix database: %w", err)
|
|
}
|
|
|
|
td := tmpdir.FromContext(ctx)
|
|
if td == nil {
|
|
return nil, nil, fmt.Errorf("no temp dir factory in context")
|
|
}
|
|
tempDB, cleanupDB, err := createTempDB(td, dbContents)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to create temporary database: %w", err)
|
|
}
|
|
// defer order is LIFO: cleanupDB (remove file) must run after db.Close and tempDB.Close
|
|
defer cleanupDB()
|
|
|
|
// close order is LIFO: db.Close() (SQLite conn) → tempDB.Close() (file handle) → cleanupDB (remove file)
|
|
defer tempDB.Close()
|
|
|
|
db, err := sql.Open("sqlite", tempDB.Name())
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to open database: %w", err)
|
|
}
|
|
defer db.Close()
|
|
db.SetConnMaxLifetime(0)
|
|
|
|
packageEntries, err := extractV10DBPackages(config, db, dbLocation, resolver)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
pkgs, relationships, err := finalizeV10DBResults(db, packageEntries, catalogerName)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return pkgs, relationships, nil
|
|
}
|
|
|
|
func extractV10DBPackages(config Config, db *sql.DB, dbLocation file.Location, resolver file.Resolver) (map[int]*dbPackageEntry, error) {
|
|
pkgs, err := extractV10DBValidPaths(config, db, dbLocation, resolver)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = extractV10DBDerivationOutputs(db, pkgs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return pkgs, nil
|
|
}
|
|
|
|
func extractV10DBValidPaths(config Config, db *sql.DB, dbLocation file.Location, resolver file.Resolver) (map[int]*dbPackageEntry, error) {
|
|
packages := make(map[int]*dbPackageEntry)
|
|
|
|
rows, err := db.Query("SELECT id, path, hash, deriver FROM ValidPaths")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query ValidPaths: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
var id int
|
|
var path, hash, deriver sql.NullString
|
|
|
|
if err := rows.Scan(&id, &path, &hash, &deriver); err != nil {
|
|
return nil, fmt.Errorf("failed to scan ValidPaths row: %w", err)
|
|
}
|
|
|
|
if !path.Valid {
|
|
continue
|
|
}
|
|
|
|
nsp := parseNixStorePath(path.String)
|
|
if nsp == nil {
|
|
nsp = &nixStorePath{}
|
|
}
|
|
// always trust the DB values over string parsing
|
|
nsp.OutputHash = hash.String
|
|
nsp.StorePath = path.String
|
|
|
|
var files []string
|
|
if config.CaptureOwnedFiles {
|
|
files = listOutputPaths(path.String, resolver)
|
|
}
|
|
|
|
df, err := newDerivationFromPath(deriver.String, resolver)
|
|
if err != nil {
|
|
log.WithFields("path", deriver.String, "error", err).Trace("unable to find derivation")
|
|
df = nil
|
|
}
|
|
|
|
packages[id] = &dbPackageEntry{
|
|
ID: id,
|
|
nixStorePath: *nsp,
|
|
derivationFile: df,
|
|
DeriverPath: deriver.String,
|
|
Location: &dbLocation,
|
|
Files: files,
|
|
}
|
|
}
|
|
|
|
return packages, nil
|
|
}
|
|
|
|
func listOutputPaths(storePath string, resolver file.Resolver) []string {
|
|
if storePath == "" {
|
|
return nil
|
|
}
|
|
searchGlob := storePath + "/**/*"
|
|
locations, err := resolver.FilesByGlob(searchGlob)
|
|
if err != nil {
|
|
log.WithFields("path", storePath, "error", err).Trace("unable to find output paths")
|
|
return nil
|
|
}
|
|
|
|
return filePaths(locations)
|
|
}
|
|
|
|
func extractV10DBDerivationOutputs(db *sql.DB, packages map[int]*dbPackageEntry) error {
|
|
outputRows, err := db.Query("SELECT drv, id, path FROM DerivationOutputs")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to query DerivationOutputs: %w", err)
|
|
}
|
|
defer outputRows.Close()
|
|
|
|
pkgsByPath := make(map[string]*dbPackageEntry)
|
|
for _, p := range packages {
|
|
pkgsByPath[p.StorePath] = p
|
|
}
|
|
|
|
for outputRows.Next() {
|
|
var drvID int
|
|
var outputID, outputPath string
|
|
|
|
if err := outputRows.Scan(&drvID, &outputID, &outputPath); err != nil {
|
|
return fmt.Errorf("failed to scan DerivationOutputs row: %w", err)
|
|
}
|
|
|
|
if _, ok := pkgsByPath[outputPath]; !ok {
|
|
continue
|
|
}
|
|
pkgsByPath[outputPath].Output = outputID
|
|
pkgsByPath[outputPath].DrvID = drvID
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func finalizeV10DBResults(db *sql.DB, packageEntries map[int]*dbPackageEntry, catalogerName string) ([]pkg.Package, []artifact.Relationship, error) {
|
|
// make Syft packages for each package entry
|
|
syftPackages := make(map[int]pkg.Package)
|
|
for id, entry := range packageEntries {
|
|
syftPackages[id] = newDBPackage(entry, catalogerName)
|
|
}
|
|
|
|
var relationships []artifact.Relationship
|
|
|
|
query := `
|
|
SELECT r.referrer, r.reference
|
|
FROM Refs r
|
|
JOIN ValidPaths v1 ON r.referrer = v1.id
|
|
JOIN ValidPaths v2 ON r.reference = v2.id
|
|
`
|
|
|
|
refRows, err := db.Query(query)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to query Refs with ValidPaths JOIN: %w", err)
|
|
}
|
|
defer refRows.Close()
|
|
|
|
relExists := make(map[int]map[int]bool)
|
|
|
|
for refRows.Next() {
|
|
var referrerID, referenceID int
|
|
|
|
if err := refRows.Scan(&referrerID, &referenceID); err != nil {
|
|
return nil, nil, fmt.Errorf("failed to scan Refs row: %w", err)
|
|
}
|
|
|
|
if referrerID == referenceID {
|
|
// skip self-references
|
|
continue
|
|
}
|
|
|
|
referrer, refExists := syftPackages[referrerID]
|
|
reference, refeeExists := syftPackages[referenceID]
|
|
|
|
if !refExists || !refeeExists {
|
|
// only include relationships for packages we have discovered
|
|
continue
|
|
}
|
|
|
|
if _, ok := relExists[referrerID]; !ok {
|
|
relExists[referrerID] = make(map[int]bool)
|
|
}
|
|
|
|
if relExists[referrerID][referenceID] {
|
|
// deduplicate existing relationships
|
|
continue
|
|
}
|
|
|
|
relExists[referrerID][referenceID] = true
|
|
|
|
rel := artifact.Relationship{
|
|
From: reference,
|
|
To: referrer,
|
|
Type: artifact.DependencyOfRelationship,
|
|
}
|
|
|
|
relationships = append(relationships, rel)
|
|
}
|
|
|
|
var pkgs []pkg.Package
|
|
for _, p := range syftPackages {
|
|
pkgs = append(pkgs, p)
|
|
}
|
|
|
|
return pkgs, relationships, nil
|
|
}
|
|
|
|
func createTempDB(td *tmpdir.TempDir, content io.ReadCloser) (*os.File, func(), error) {
|
|
noop := func() {}
|
|
|
|
tempFile, cleanup, err := td.NewFile("nix-db-*.sqlite") //nolint:gocritic // cleanup is returned to caller, not deferred here
|
|
if err != nil {
|
|
return nil, noop, err
|
|
}
|
|
|
|
_, err = io.Copy(tempFile, content)
|
|
if err != nil {
|
|
tempFile.Close()
|
|
cleanup()
|
|
return nil, noop, err
|
|
}
|
|
|
|
return tempFile, cleanup, nil
|
|
}
|