Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/handlers/ar.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (h *arHandler) processARFiles(ctx logContext.Context, reader *deb.Ar, dataO

if err := h.handleNonArchiveContent(fileCtx, rdr, dataOrErrChan); err != nil {
dataOrErrChan <- DataOrErr{
Err: fmt.Errorf("%w: error handling archive content in AR: %v", ErrProcessingWarning, err),
Err: fmt.Errorf("%w: error handling archive content in AR: %w", ErrProcessingWarning, err),
}
h.metrics.incErrors()
continue
Expand Down
73 changes: 70 additions & 3 deletions pkg/handlers/ar_test.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
package handlers

import (
"context"
"io"
"os"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/trufflesecurity/trufflehog/v3/pkg/context"
trContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
)

func TestHandleARFile(t *testing.T) {
file, err := os.Open("testdata/test.deb")
assert.Nil(t, err)
defer file.Close()

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
ctx, cancel := trContext.WithTimeout(trContext.Background(), 3*time.Second)
defer cancel()

rdr, err := newFileReader(ctx, file)
assert.NoError(t, err)
defer rdr.Close()

handler := newARHandler()
dataOrErrChan := handler.HandleFile(context.AddLogger(ctx), rdr)
dataOrErrChan := handler.HandleFile(trContext.AddLogger(ctx), rdr)
assert.NoError(t, err)

wantChunkCount := 102
Expand All @@ -34,3 +38,66 @@ func TestHandleARFile(t *testing.T) {

assert.Equal(t, wantChunkCount, count)
}

// TestARHandler_NonArchiveErrPreservesIdentity is a regression test for the
// %v wrap on the ErrProcessingWarning send to dataOrErrChan in
// processARFiles' handleNonArchiveContent error path. Before the fix, wrapping
// handleNonArchiveContent's
// return value with %v dropped the inner error's identity from the errors.Is
// chain, causing isFatal in handleChunksWithError to misclassify cancellation
// (and other fatal causes) as a non-fatal warning. The test injects a
// cancelling chunk reader so handleNonArchiveContent's CancellableWrite
// returns context.Canceled, which is then wrapped by processARFiles. It
// asserts both that the outer ErrProcessingWarning wrap is preserved and
// that the inner cancellation cause remains inspectable.
func TestARHandler_NonArchiveErrPreservesIdentity(t *testing.T) {
file, err := os.Open("testdata/test.deb")
require.NoError(t, err)
defer file.Close()

ctx, cancel := trContext.WithCancel(trContext.Background())
defer cancel()

rdr, err := newFileReader(ctx, file)
require.NoError(t, err)
defer rdr.Close()

// Cancel the parent context the moment handleNonArchiveContent asks for
// chunks, then deliver a single non-error chunk. CancellableWrite of that
// chunk's data sees the cancelled context and returns context.Canceled,
// which handleNonArchiveContent returns and processARFiles wraps. This
// is the only path that exercises the processARFiles dataOrErrChan
// ErrProcessingWarning wrap.
cancellingChunkReader := sources.ChunkReader(func(_ trContext.Context, _ io.Reader) <-chan sources.ChunkResult {
ch := make(chan sources.ChunkResult, 1)
cancel()
ch <- sources.NewChunkResult([]byte("data"), 4)
close(ch)
return ch
})

handler := &arHandler{
defaultHandler: newDefaultHandler(arHandlerType, withChunkReader(cancellingChunkReader)),
}

var got []DataOrErr
for d := range handler.HandleFile(trContext.AddLogger(ctx), rdr) {
got = append(got, d)
}

var warnErr error
for _, d := range got {
if d.Err != nil {
warnErr = d.Err
break
}
}
require.Error(t, warnErr, "expected wrapped warning from ar.go non-archive error path")

assert.ErrorIs(t, warnErr, ErrProcessingWarning,
"outer ErrProcessingWarning wrap should be preserved")
assert.ErrorIs(t, warnErr, context.Canceled,
"inner cancellation cause should be inspectable via errors.Is")
assert.True(t, isFatal(warnErr),
"isFatal should classify the wrapped error based on the inner cause")
}
2 changes: 1 addition & 1 deletion pkg/handlers/rpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func (h *rpmHandler) processRPMFiles(

if err := h.handleNonArchiveContent(fileCtx, rdr, dataOrErrChan); err != nil {
dataOrErrChan <- DataOrErr{
Err: fmt.Errorf("%w: error processing RPM archive: %v", ErrProcessingWarning, err),
Err: fmt.Errorf("%w: error processing RPM archive: %w", ErrProcessingWarning, err),
}
h.metrics.incErrors()
}
Expand Down
64 changes: 61 additions & 3 deletions pkg/handlers/rpm_test.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
package handlers

import (
"context"
"io"
"os"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/trufflesecurity/trufflehog/v3/pkg/context"
trContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
)

func TestHandleRPMFile(t *testing.T) {
file, err := os.Open("testdata/test.rpm")
assert.Nil(t, err)
defer file.Close()

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
ctx, cancel := trContext.WithTimeout(trContext.Background(), 3*time.Second)
defer cancel()

rdr, err := newFileReader(ctx, file)
assert.NoError(t, err)
defer rdr.Close()

handler := newRPMHandler()
dataOrErrChan := handler.HandleFile(context.AddLogger(ctx), rdr)
dataOrErrChan := handler.HandleFile(trContext.AddLogger(ctx), rdr)
assert.NoError(t, err)

wantChunkCount := 179
Expand All @@ -34,3 +38,57 @@ func TestHandleRPMFile(t *testing.T) {

assert.Equal(t, wantChunkCount, count)
}

// TestRPMHandler_NonArchiveErrPreservesIdentity is a regression test for the
// %v wrap on the ErrProcessingWarning send to dataOrErrChan in
// processRPMFiles' handleNonArchiveContent error path. Same shape as
// TestARHandler_NonArchiveErrPreservesIdentity:
// inject a cancelling chunk reader so handleNonArchiveContent's CancellableWrite
// returns context.Canceled, which processRPMFiles wraps with ErrProcessingWarning.
// Asserts the outer warning wrap and inner cancellation cause are both
// observable via errors.Is so isFatal can classify correctly.
func TestRPMHandler_NonArchiveErrPreservesIdentity(t *testing.T) {
file, err := os.Open("testdata/test.rpm")
require.NoError(t, err)
defer file.Close()

ctx, cancel := trContext.WithCancel(trContext.Background())
defer cancel()

rdr, err := newFileReader(ctx, file)
require.NoError(t, err)
defer rdr.Close()

cancellingChunkReader := sources.ChunkReader(func(_ trContext.Context, _ io.Reader) <-chan sources.ChunkResult {
ch := make(chan sources.ChunkResult, 1)
cancel()
ch <- sources.NewChunkResult([]byte("data"), 4)
close(ch)
return ch
})

handler := &rpmHandler{
defaultHandler: newDefaultHandler(rpmHandlerType, withChunkReader(cancellingChunkReader)),
}

var got []DataOrErr
for d := range handler.HandleFile(trContext.AddLogger(ctx), rdr) {
got = append(got, d)
}

var warnErr error
for _, d := range got {
if d.Err != nil {
warnErr = d.Err
break
}
}
require.Error(t, warnErr, "expected wrapped warning from rpm.go non-archive error path")

assert.ErrorIs(t, warnErr, ErrProcessingWarning,
"outer ErrProcessingWarning wrap should be preserved")
assert.ErrorIs(t, warnErr, context.Canceled,
"inner cancellation cause should be inspectable via errors.Is")
assert.True(t, isFatal(warnErr),
"isFatal should classify the wrapped error based on the inner cause")
}
Loading