mirror of
https://github.com/moby/buildkit.git
synced 2026-08-09 01:01:08 +00:00
fs: reject special files for ref reads
Ref and rootfs helpers may read paths supplied by frontends or build outputs. Reject non-regular files before reading them into memory so device and FIFO paths cannot feed no-EOF streams into parsers or bundle injection logic. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
8
cache/util/fsutil.go
vendored
8
cache/util/fsutil.go
vendored
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user