From 71dc7cfbd6f4d927643fe31c89000e02efcb44b6 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Wed, 2 Sep 2026 17:37:47 -0700 Subject: [PATCH] errors: report build-facing paths on read failure Signed-off-by: Tonis Tiigi (cherry picked from commit 12bf293e5feee7393e971a1d22a191dff7f6e7f8) (cherry picked from commit 5a01794b79c7af91a961b1236b0e0661975161d7) --- cache/util/fsutil.go | 19 ++++++++++++------- executor/oci/user.go | 16 +++++++++++++--- exporter/attestation/make.go | 22 ++++++++++++++++++---- exporter/attestation/unbundle.go | 22 ++++++++++++++++++---- solver/llbsolver/ops/build.go | 11 ++++++++++- solver/llbsolver/ops/user_linux.go | 15 ++++++++++++--- 6 files changed, 83 insertions(+), 22 deletions(-) diff --git a/cache/util/fsutil.go b/cache/util/fsutil.go index 53808d8f9..92d8368d0 100644 --- a/cache/util/fsutil.go +++ b/cache/util/fsutil.go @@ -23,7 +23,15 @@ type FileRange struct { Length int } -func ReadFile(ctx context.Context, root string, req ReadRequest) ([]byte, error) { +func ReadFile(ctx context.Context, root string, req ReadRequest) (_ []byte, retErr error) { + // paths below are internal to the mount, report the one that was requested + defer func() { + var pathErr *os.PathError + if errors.As(retErr, &pathErr) { + pathErr.Path = req.Filename + } + }() + fp, err := fs.RootPath(root, req.Filename) if err != nil { return nil, errors.WithStack(err) @@ -31,18 +39,15 @@ func ReadFile(ctx context.Context, root string, req ReadRequest) ([]byte, error) f, err := openfile.Regular(fp) if err != nil { - // The filename here is internal to the mount, so we can restore - // the request base path for error reporting. - pe := &os.PathError{} - if errors.As(err, &pe) { - pe.Path = req.Filename - } return nil, err } defer f.Close() var rdr io.Reader = f if req.Range != nil { + if req.Range.Offset < 0 || req.Range.Length < 0 { + return nil, errors.Errorf("invalid range for %s", req.Filename) + } rdr = io.NewSectionReader(f, int64(req.Range.Offset), int64(req.Range.Length)) } dt, err := io.ReadAll(rdr) diff --git a/executor/oci/user.go b/executor/oci/user.go index 6691bf43a..a7bcae3e9 100644 --- a/executor/oci/user.go +++ b/executor/oci/user.go @@ -3,6 +3,7 @@ package oci import ( "context" "io" + "os" "slices" "strconv" "strings" @@ -71,8 +72,17 @@ func ParseUIDGID(str string) (uid uint32, gid uint32, err error) { return } -func openUserFile(root, p string) (io.ReadCloser, error) { - p, err := fs.RootPath(root, p) +func openUserFile(root, orig string) (_ io.ReadCloser, retErr error) { + // paths below are daemon-side locations, report the path as the build + // asked for it + defer func() { + var pathErr *os.PathError + if errors.As(retErr, &pathErr) { + pathErr.Path = orig + } + }() + + p, err := fs.RootPath(root, orig) if err != nil { return nil, errors.WithStack(err) } @@ -85,7 +95,7 @@ func openUserFile(root, p string) (io.ReadCloser, error) { return &limitedReadCloser{ ReadCloser: f, r: &io.LimitedReader{R: f, N: maxUserFileBytes + 1}, - name: p, + name: orig, }, nil } diff --git a/exporter/attestation/make.go b/exporter/attestation/make.go index f6e280304..50b7db03d 100644 --- a/exporter/attestation/make.go +++ b/exporter/attestation/make.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "io" + "os" "github.com/containerd/continuity/fs" intoto "github.com/in-toto/in-toto-golang/in_toto" @@ -20,7 +21,15 @@ import ( const maxAttestationBytes int64 = 80 << 20 // ReadAll reads the content of an attestation. -func ReadAll(ctx context.Context, s session.Group, att exporter.Attestation) ([]byte, error) { +func ReadAll(ctx context.Context, s session.Group, att exporter.Attestation) (_ []byte, retErr error) { + // paths below are daemon-side locations, report the attestation path + defer func() { + var pathErr *os.PathError + if errors.As(retErr, &pathErr) { + pathErr.Path = att.Path + } + }() + var content []byte if att.ContentFunc != nil { data, err := att.ContentFunc(ctx) @@ -44,7 +53,7 @@ func ReadAll(ctx context.Context, s session.Group, att exporter.Attestation) ([] if err != nil { return nil, err } - content, err = readRegularFile(p) + content, err = readRegularFile(p, att.Path) if err != nil { return nil, errors.Wrap(err, "cannot read in-toto attestation") } @@ -57,14 +66,19 @@ func ReadAll(ctx context.Context, s session.Group, att exporter.Attestation) ([] return content, nil } -func readRegularFile(p string) ([]byte, error) { +func readRegularFile(p, name string) ([]byte, error) { f, err := openfile.Regular(p) if err != nil { + // the resolved path is a daemon-side location + var pathErr *os.PathError + if errors.As(err, &pathErr) { + pathErr.Path = name + } return nil, err } defer f.Close() - dt, err := readAllLimited(f, p, maxAttestationBytes) + dt, err := readAllLimited(f, name, maxAttestationBytes) if err != nil { return nil, err } diff --git a/exporter/attestation/unbundle.go b/exporter/attestation/unbundle.go index 65fc418cd..465dca252 100644 --- a/exporter/attestation/unbundle.go +++ b/exporter/attestation/unbundle.go @@ -117,28 +117,42 @@ func sort(atts []exporter.Attestation) []exporter.Attestation { return result } -func unbundle(root string, bundle exporter.Attestation) ([]exporter.Attestation, error) { +func unbundle(root string, bundle exporter.Attestation) (_ []exporter.Attestation, retErr error) { + // paths below are daemon-side locations, report the bundle path + defer func() { + var pathErr *os.PathError + if errors.As(retErr, &pathErr) { + pathErr.Path = bundle.Path + } + }() + dir, err := fs.RootPath(root, bundle.Path) if err != nil { return nil, err } entries, err := os.ReadDir(dir) if err != nil { - return nil, err + return nil, errors.WithStack(err) } var unbundled []exporter.Attestation for _, entry := range entries { + name := path.Join(bundle.Path, entry.Name()) p, err := fs.RootPath(dir, entry.Name()) if err != nil { return nil, err } f, err := openfile.Regular(p) if err != nil { + // p is a daemon-side location, report the bundle entry + var pathErr *os.PathError + if errors.As(err, &pathErr) { + pathErr.Path = name + } return nil, err } var stmt intoto.Statement - stmt, err = decodeStatement(f, p) + stmt, err = decodeStatement(f, name) f.Close() if err != nil { return nil, err @@ -163,7 +177,7 @@ func unbundle(root string, bundle exporter.Attestation) ([]exporter.Attestation, unbundled = append(unbundled, exporter.Attestation{ Kind: gatewaypb.AttestationKind_InToto, Metadata: bundle.Metadata, - Path: path.Join(bundle.Path, entry.Name()), + Path: name, ContentFunc: func(context.Context) ([]byte, error) { return predicate, nil }, InToto: result.InTotoAttestation{ PredicateType: stmt.PredicateType, diff --git a/solver/llbsolver/ops/build.go b/solver/llbsolver/ops/build.go index 8bdcc8c71..76b5b7a1e 100644 --- a/solver/llbsolver/ops/build.go +++ b/solver/llbsolver/ops/build.go @@ -3,6 +3,7 @@ package ops import ( "context" "encoding/json" + "os" "github.com/containerd/continuity/fs" "github.com/moby/buildkit/client/llb" @@ -111,6 +112,14 @@ func (b *BuildOp) Exec(ctx context.Context, job solver.JobContext, inputs []solv fn = override } + // paths below are daemon-side locations, report the requested filename + defer func() { + var pathErr *os.PathError + if errors.As(retErr, &pathErr) { + pathErr.Path = fn + } + }() + newfn, err := fs.RootPath(root, fn) if err != nil { return nil, errors.Wrapf(err, "working dir %s points to invalid target", fn) @@ -118,7 +127,7 @@ func (b *BuildOp) Exec(ctx context.Context, job solver.JobContext, inputs []solv f, err := openfile.Regular(newfn) if err != nil { - return nil, errors.Wrapf(err, "failed to open %s", newfn) + return nil, err } def, err := llb.ReadFrom(f) diff --git a/solver/llbsolver/ops/user_linux.go b/solver/llbsolver/ops/user_linux.go index 6dd85c558..a119a6a4d 100644 --- a/solver/llbsolver/ops/user_linux.go +++ b/solver/llbsolver/ops/user_linux.go @@ -119,8 +119,17 @@ func readUser(chopt *pb.ChownOpt, mu, mg snapshot.Mountable) (*copy.User, error) return &us, nil } -func openUserFile(root, p string) (io.ReadCloser, error) { - p, err := fs.RootPath(root, p) +func openUserFile(root, orig string) (_ io.ReadCloser, retErr error) { + // paths below are daemon-side locations, report the path as the build + // asked for it + defer func() { + var pathErr *os.PathError + if errors.As(retErr, &pathErr) { + pathErr.Path = orig + } + }() + + p, err := fs.RootPath(root, orig) if err != nil { return nil, errors.WithStack(err) } @@ -133,7 +142,7 @@ func openUserFile(root, p string) (io.ReadCloser, error) { return &limitedReadCloser{ ReadCloser: f, r: &io.LimitedReader{R: f, N: maxUserFileBytes + 1}, - name: p, + name: orig, }, nil }