Files
syft/internal/task/executor.go
T
Alex Goodman 10af8342c0 fix: don't fail the whole SBOM when a cataloger panics on one file (#5342)
* fix(generic): recover from parser and processor panics

- a panic while parsing one file becomes an unknown for that location instead of failing the whole SBOM
- a panic in a package processor is logged as a warning and the unprocessed results are kept
- stacks go to the trace log, not into the unknown text

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(unknown): keep errors that unwrap to nil

`sync.PanicError` with a non-error panic value (e.g. `panic("boom")`) unwraps to nil, and was being dropped entirely from the error graph instead of being kept as a leaf.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(task): don't put the stack trace in task panic errors

Task panics are still fatal, but the error now reads `panic in task "<name>": <value>` and the stack goes to the debug log.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* test(generic): cover chained processors when a later one panics

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(file): recover from panics in the executable and file digest catalogers

A panic while reading one file is now recorded as an unknown for that file instead of failing the whole SBOM, the same as generic catalogers.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-29 09:21:03 -04:00

64 lines
1.8 KiB
Go

package task
import (
"context"
"fmt"
"runtime/debug"
"slices"
"time"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/sbomsync"
"github.com/anchore/syft/internal/unknown"
"github.com/anchore/syft/syft/event/monitor"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/sbom"
)
func RunTask(ctx context.Context, tsk Task, resolver file.Resolver, s sbomsync.Builder, prog *monitor.TaskProgress) error {
err := runTaskSafely(ctx, tsk, resolver, s)
unknowns, remainingErrors := unknown.ExtractCoordinateErrors(err)
if len(unknowns) > 0 {
appendUnknowns(s, tsk.Name(), unknowns)
}
if remainingErrors != nil {
prog.SetError(remainingErrors)
}
prog.Increment()
return remainingErrors
}
func appendUnknowns(builder sbomsync.Builder, taskName string, unknowns []unknown.CoordinateError) {
if accessor, ok := builder.(sbomsync.Accessor); ok {
accessor.WriteToSBOM(func(sb *sbom.SBOM) {
for _, u := range unknowns {
if sb.Artifacts.Unknowns == nil {
sb.Artifacts.Unknowns = map[file.Coordinates][]string{}
}
unknownText := formatUnknown(u.Reason.Error(), taskName)
existing := sb.Artifacts.Unknowns[u.Coordinates]
// don't include duplicate unknowns
if slices.Contains(existing, unknownText) {
continue
}
sb.Artifacts.Unknowns[u.Coordinates] = append(existing, unknownText)
}
})
}
}
func runTaskSafely(ctx context.Context, t Task, resolver file.Resolver, s sbomsync.Builder) (err error) {
// handle individual cataloger panics
defer func() {
if e := recover(); e != nil {
log.WithFields("task", t.Name()).Debugf("task panic stack:\n%s", debug.Stack())
err = fmt.Errorf("panic in task %q: %v", t.Name(), e)
}
}()
start := time.Now()
res := t.Execute(ctx, resolver, s)
log.WithFields("task", t.Name(), "elapsed", time.Since(start)).Info("task completed")
return res
}