diff --git a/cache/util/fsutil.go b/cache/util/fsutil.go index 5bf04ef1fb03..1309fc257915 100644 --- a/cache/util/fsutil.go +++ b/cache/util/fsutil.go @@ -41,6 +41,14 @@ func ReadFile(ctx context.Context, root string, req ReadRequest) ([]byte, error) } defer f.Close() + info, err := f.Stat() + if err != nil { + return nil, errors.WithStack(err) + } + if !info.Mode().IsRegular() { + return nil, errors.Errorf("%s is not a regular file", req.Filename) + } + var rdr io.Reader = f if req.Range != nil { rdr = io.NewSectionReader(f, int64(req.Range.Offset), int64(req.Range.Length)) diff --git a/executor/proxyca_linux.go b/executor/proxyca_linux.go index a596634a4b02..159e1c30f570 100644 --- a/executor/proxyca_linux.go +++ b/executor/proxyca_linux.go @@ -7,6 +7,7 @@ import ( "crypto/sha256" "crypto/x509" "encoding/pem" + "io" "os" "path/filepath" "syscall" @@ -29,6 +30,8 @@ var ( proxyCAEnd = []byte("# buildkit proxy CA end\n") ) +const maxCertBundleBytes = 10 << 20 + // InjectProxyCA appends caPEM to the rootfs trust bundle used by common Linux // TLS stacks and returns a cleanup that removes only the injected CA. func InjectProxyCA(rootfsPath string, caPEM []byte) (func() error, error) { @@ -47,7 +50,7 @@ func InjectProxyCA(rootfsPath string, caPEM []byte) (func() error, error) { if err != nil { return nil, errors.Wrapf(err, "failed to resolve certificate bundle %s", name) } - if st, err := os.Stat(p); err == nil && !st.IsDir() { + if st, err := os.Stat(p); err == nil && st.Mode().IsRegular() { bundle = p break } @@ -56,17 +59,13 @@ func InjectProxyCA(rootfsPath string, caPEM []byte) (func() error, error) { return func() error { return nil }, nil } - original, err := os.ReadFile(bundle) + original, st, err := readCertBundle(bundle) if err != nil { - return nil, errors.WithStack(err) + return nil, err } if containsCertificate(original, certSum) { return func() error { return nil }, nil } - st, err := os.Stat(bundle) - if err != nil { - return nil, errors.WithStack(err) - } next := append([]byte{}, original...) if len(next) > 0 && next[len(next)-1] != '\n' { next = append(next, '\n') @@ -82,25 +81,47 @@ func InjectProxyCA(rootfsPath string, caPEM []byte) (func() error, error) { } return func() error { - current, err := os.ReadFile(bundle) + current, st, err := readCertBundle(bundle) if err != nil { if errors.Is(err, os.ErrNotExist) { return nil } - return errors.WithStack(err) + return err } cleaned := removeInjectedCA(current, certSum) if bytes.Equal(current, cleaned) { return nil } - st, err := os.Stat(bundle) - if err != nil { - return errors.WithStack(err) - } return writeCertBundle(bundle, cleaned, st) }, nil } +func readCertBundle(path string) ([]byte, os.FileInfo, error) { + f, err := os.Open(path) + if err != nil { + return nil, nil, errors.WithStack(err) + } + defer f.Close() + + st, err := f.Stat() + if err != nil { + return nil, nil, errors.WithStack(err) + } + if !st.Mode().IsRegular() { + return nil, nil, errors.Errorf("%s is not a regular file", path) + } + + limited := &io.LimitedReader{R: f, N: maxCertBundleBytes + 1} + dt, err := io.ReadAll(limited) + if err != nil { + return nil, nil, errors.WithStack(err) + } + if limited.N == 0 { + return nil, nil, errors.Errorf("%s exceeds %d bytes", path, maxCertBundleBytes) + } + return dt, st, nil +} + func firstCertificate(dt []byte) (*x509.Certificate, error) { for { block, rest := pem.Decode(dt) diff --git a/exporter/attestation/make.go b/exporter/attestation/make.go index 50ef7cf4e668..030040213baf 100644 --- a/exporter/attestation/make.go +++ b/exporter/attestation/make.go @@ -3,6 +3,7 @@ package attestation import ( "context" "encoding/json" + "io" "os" "github.com/containerd/continuity/fs" @@ -16,6 +17,8 @@ import ( "golang.org/x/sync/errgroup" ) +const maxAttestationBytes int64 = 40 << 20 + // ReadAll reads the content of an attestation. func ReadAll(ctx context.Context, s session.Group, att exporter.Attestation) ([]byte, error) { var content []byte @@ -41,7 +44,7 @@ func ReadAll(ctx context.Context, s session.Group, att exporter.Attestation) ([] if err != nil { return nil, err } - content, err = os.ReadFile(p) + content, err = readRegularFile(p) if err != nil { return nil, errors.Wrap(err, "cannot read in-toto attestation") } @@ -54,6 +57,51 @@ func ReadAll(ctx context.Context, s session.Group, att exporter.Attestation) ([] return content, nil } +func readRegularFile(p string) ([]byte, error) { + f, err := openRegularFile(p) + if err != nil { + return nil, err + } + defer f.Close() + + dt, err := readAllLimited(f, p, maxAttestationBytes) + if err != nil { + return nil, err + } + return dt, nil +} + +func readAllLimited(r io.Reader, name string, limit int64) ([]byte, error) { + limited := &io.LimitedReader{R: r, N: limit + 1} + dt, err := io.ReadAll(limited) + if err != nil { + return nil, errors.WithStack(err) + } + if limited.N == 0 { + return nil, errors.Errorf("%s exceeds %d bytes", name, limit) + } + return dt, nil +} + +func openRegularFile(p string) (*os.File, error) { + f, err := os.Open(p) + if err != nil { + return nil, errors.WithStack(err) + } + + st, err := f.Stat() + if err != nil { + f.Close() + return nil, errors.WithStack(err) + } + if !st.Mode().IsRegular() { + f.Close() + return nil, errors.Errorf("%s is not a regular file", p) + } + + return f, nil +} + // MakeInTotoStatements iterates over all provided result attestations and // generates intoto attestation statements. func MakeInTotoStatements(ctx context.Context, s session.Group, attestations []exporter.Attestation, defaultSubjects []intoto.Subject) ([]intoto.Statement, error) { diff --git a/exporter/attestation/unbundle.go b/exporter/attestation/unbundle.go index 22c37eeb57ff..d9f358fd6e1c 100644 --- a/exporter/attestation/unbundle.go +++ b/exporter/attestation/unbundle.go @@ -132,17 +132,15 @@ func unbundle(root string, bundle exporter.Attestation) ([]exporter.Attestation, if err != nil { return nil, err } - f, err := os.Open(p) + f, err := openRegularFile(p) if err != nil { return nil, err } - dec := json.NewDecoder(f) var stmt intoto.Statement - if err := dec.Decode(&stmt); err != nil { - return nil, errors.Wrap(err, "cannot decode in-toto statement") - } - if _, err := dec.Token(); !errors.Is(err, io.EOF) { - return nil, errors.New("in-toto statement is not a single JSON object") + stmt, err = decodeStatement(f, p) + f.Close() + if err != nil { + return nil, err } if bundle.InToto.PredicateType != "" && stmt.PredicateType != bundle.InToto.PredicateType { return nil, errors.Errorf("bundle entry %s does not match required predicate type %s", stmt.PredicateType, bundle.InToto.PredicateType) @@ -175,6 +173,29 @@ func unbundle(root string, bundle exporter.Attestation) ([]exporter.Attestation, return unbundled, nil } +func decodeStatement(r io.Reader, name string) (intoto.Statement, error) { + limited := &io.LimitedReader{R: r, N: maxAttestationBytes + 1} + dec := json.NewDecoder(limited) + + var stmt intoto.Statement + if err := dec.Decode(&stmt); err != nil { + if limited.N == 0 { + return stmt, errors.Errorf("%s exceeds %d bytes", name, maxAttestationBytes) + } + return stmt, errors.Wrap(err, "cannot decode in-toto statement") + } + if _, err := dec.Token(); !errors.Is(err, io.EOF) { + if limited.N == 0 { + return stmt, errors.Errorf("%s exceeds %d bytes", name, maxAttestationBytes) + } + return stmt, errors.New("in-toto statement is not a single JSON object") + } + if limited.N == 0 { + return stmt, errors.Errorf("%s exceeds %d bytes", name, maxAttestationBytes) + } + return stmt, nil +} + func Validate(atts []exporter.Attestation) error { for _, att := range atts { if err := validate(att); err != nil { diff --git a/solver/llbsolver/ops/build.go b/solver/llbsolver/ops/build.go index c4631fa919b4..2c5fb1b3c2fa 100644 --- a/solver/llbsolver/ops/build.go +++ b/solver/llbsolver/ops/build.go @@ -120,6 +120,15 @@ func (b *BuildOp) Exec(ctx context.Context, job solver.JobContext, inputs []solv if err != nil { return nil, errors.Wrapf(err, "failed to open %s", newfn) } + st, err := f.Stat() + if err != nil { + f.Close() + return nil, errors.WithStack(err) + } + if !st.Mode().IsRegular() { + f.Close() + return nil, errors.Errorf("%s is not a regular file", newfn) + } def, err := llb.ReadFrom(f) if err != nil {