vendor github.com/containers/image/v5@v5.2.0

See release notes:
	https://github.com/containers/image/releases/tag/v5.2.0

Fixes: #4877
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
Valentin Rothberg
2020-02-03 16:09:04 +01:00
parent 5092c078ec
commit 801977b40d
38 changed files with 804 additions and 255 deletions

View File

@ -709,7 +709,7 @@ func checkImageDestinationForCurrentRuntime(ctx context.Context, sys *types.Syst
wantedOS = sys.OSChoice
}
if wantedOS != c.OS {
return fmt.Errorf("Image operating system mismatch: image uses %q, expecting %q", c.OS, wantedOS)
logrus.Infof("Image operating system mismatch: image uses %q, expecting %q", c.OS, wantedOS)
}
wantedArch := runtime.GOARCH
@ -717,7 +717,7 @@ func checkImageDestinationForCurrentRuntime(ctx context.Context, sys *types.Syst
wantedArch = sys.ArchitectureChoice
}
if wantedArch != c.Architecture {
return fmt.Errorf("Image architecture mismatch: image uses %q, expecting %q", c.Architecture, wantedArch)
logrus.Infof("Image architecture mismatch: image uses %q, expecting %q", c.Architecture, wantedArch)
}
}
return nil

View File

@ -6,6 +6,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"github.com/containers/image/v5/types"
"github.com/opencontainers/go-digest"
@ -142,8 +143,11 @@ func (d *dirImageDestination) PutBlob(ctx context.Context, stream io.Reader, inp
return types.BlobInfo{}, err
}
succeeded := false
explicitClosed := false
defer func() {
blobFile.Close()
if !explicitClosed {
blobFile.Close()
}
if !succeeded {
os.Remove(blobFile.Name())
}
@ -164,10 +168,21 @@ func (d *dirImageDestination) PutBlob(ctx context.Context, stream io.Reader, inp
if err := blobFile.Sync(); err != nil {
return types.BlobInfo{}, err
}
if err := blobFile.Chmod(0644); err != nil {
return types.BlobInfo{}, err
// On POSIX systems, blobFile was created with mode 0600, so we need to make it readable.
// On Windows, the “permissions of newly created files” argument to syscall.Open is
// ignored and the file is already readable; besides, blobFile.Chmod, i.e. syscall.Fchmod,
// always fails on Windows.
if runtime.GOOS != "windows" {
if err := blobFile.Chmod(0644); err != nil {
return types.BlobInfo{}, err
}
}
blobPath := d.ref.layerPath(computedDigest)
// need to explicitly close the file, since a rename won't otherwise not work on Windows
blobFile.Close()
explicitClosed = true
if err := os.Rename(blobFile.Name(), blobPath); err != nil {
return types.BlobInfo{}, err
}

View File

@ -6,7 +6,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
@ -17,6 +16,7 @@ import (
"time"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/internal/iolimits"
"github.com/containers/image/v5/pkg/docker/config"
"github.com/containers/image/v5/pkg/sysregistriesv2"
"github.com/containers/image/v5/pkg/tlsclientconfig"
@ -45,6 +45,10 @@ const (
extensionSignatureSchemaVersion = 2 // extensionSignature.Version
extensionSignatureTypeAtomic = "atomic" // extensionSignature.Type
backoffNumIterations = 5
backoffInitialDelay = 2 * time.Second
backoffMaxDelay = 60 * time.Second
)
var systemPerHostCertDirPaths = [2]string{"/etc/containers/certs.d", "/etc/docker/certs.d"}
@ -277,7 +281,7 @@ func CheckAuth(ctx context.Context, sys *types.SystemContext, username, password
}
defer resp.Body.Close()
return httpResponseToError(resp)
return httpResponseToError(resp, "")
}
// SearchResult holds the information of each matching image
@ -351,7 +355,7 @@ func SearchRegistry(ctx context.Context, sys *types.SystemContext, registry, ima
} else {
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
logrus.Debugf("error getting search results from v1 endpoint %q: %v", registry, httpResponseToError(resp))
logrus.Debugf("error getting search results from v1 endpoint %q: %v", registry, httpResponseToError(resp, ""))
} else {
if err := json.NewDecoder(resp.Body).Decode(v1Res); err != nil {
return nil, err
@ -368,7 +372,7 @@ func SearchRegistry(ctx context.Context, sys *types.SystemContext, registry, ima
} else {
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
logrus.Errorf("error getting search results from v2 endpoint %q: %v", registry, httpResponseToError(resp))
logrus.Errorf("error getting search results from v2 endpoint %q: %v", registry, httpResponseToError(resp, ""))
} else {
if err := json.NewDecoder(resp.Body).Decode(v2Res); err != nil {
return nil, err
@ -400,74 +404,64 @@ func (c *dockerClient) makeRequest(ctx context.Context, method, path string, hea
return c.makeRequestToResolvedURL(ctx, method, url, headers, stream, -1, auth, extraScope)
}
// parseRetryAfter determines the delay required by the "Retry-After" header in res and returns it,
// silently falling back to fallbackDelay if the header is missing or invalid.
func parseRetryAfter(res *http.Response, fallbackDelay time.Duration) time.Duration {
after := res.Header.Get("Retry-After")
if after == "" {
return fallbackDelay
}
logrus.Debugf("Detected 'Retry-After' header %q", after)
// First, check if we have a numerical value.
if num, err := strconv.ParseInt(after, 10, 64); err == nil {
return time.Duration(num) * time.Second
}
// Second, check if we have an HTTP date.
// If the delta between the date and now is positive, use it.
// Otherwise, fall back to using the default exponential back off.
if t, err := http.ParseTime(after); err == nil {
delta := time.Until(t)
if delta > 0 {
return delta
}
logrus.Debugf("Retry-After date in the past, ignoring it")
return fallbackDelay
}
// If the header contents are bogus, fall back to using the default exponential back off.
logrus.Debugf("Invalid Retry-After format, ignoring it")
return fallbackDelay
}
// makeRequestToResolvedURL creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
// streamLen, if not -1, specifies the length of the data expected on stream.
// makeRequest should generally be preferred.
// In case of an http 429 status code in the response, it performs an exponential back off starting at 2 seconds for at most 5 iterations.
// If the `Retry-After` header is set in the response, the specified value or date is
// If the stream is non-nil, no back off will be performed.
// In case of an HTTP 429 status code in the response, it may automatically retry a few times.
// TODO(runcom): too many arguments here, use a struct
func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method, url string, headers map[string][]string, stream io.Reader, streamLen int64, auth sendAuth, extraScope *authScope) (*http.Response, error) {
var (
res *http.Response
err error
delay int64
)
delay = 2
const numIterations = 5
const maxDelay = 60
delay := backoffInitialDelay
attempts := 0
for {
res, err := c.makeRequestToResolvedURLOnce(ctx, method, url, headers, stream, streamLen, auth, extraScope)
attempts++
if res == nil || res.StatusCode != http.StatusTooManyRequests || // Only retry on StatusTooManyRequests, success or other failure is returned to caller immediately
stream != nil || // We can't retry with a body (which is not restartable in the general case)
attempts == backoffNumIterations {
return res, err
}
// math.Min() only supports float64, so have an anonymous func to avoid
// casting.
min := func(a int64, b int64) int64 {
if a < b {
return a
delay = parseRetryAfter(res, delay)
if delay > backoffMaxDelay {
delay = backoffMaxDelay
}
return b
logrus.Debugf("Too many requests to %s: sleeping for %f seconds before next attempt", url, delay.Seconds())
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(delay):
// Nothing
}
delay = delay * 2 // exponential back off
}
nextDelay := func(r *http.Response, delay int64) int64 {
after := res.Header.Get("Retry-After")
if after == "" {
return min(delay, maxDelay)
}
logrus.Debugf("detected 'Retry-After' header %q", after)
// First check if we have a numerical value.
if num, err := strconv.ParseInt(after, 10, 64); err == nil {
return min(num, maxDelay)
}
// Secondly check if we have an http date.
// If the delta between the date and now is positive, use it.
// Otherwise, fall back to using the default exponential back off.
if t, err := http.ParseTime(after); err == nil {
delta := int64(time.Until(t).Seconds())
if delta > 0 {
return min(delta, maxDelay)
}
logrus.Debugf("negative date: falling back to using %d seconds", delay)
return min(delay, maxDelay)
}
// If the header contains bogus, fall back to using the default
// exponential back off.
logrus.Debugf("invalid format: falling back to using %d seconds", delay)
return min(delay, maxDelay)
}
for i := 0; i < numIterations; i++ {
res, err = c.makeRequestToResolvedURLOnce(ctx, method, url, headers, stream, streamLen, auth, extraScope)
if stream == nil && res != nil && res.StatusCode == http.StatusTooManyRequests {
if i < numIterations-1 {
logrus.Errorf("HEADER %v", res.Header)
delay = nextDelay(res, delay) // compute next delay - does NOT exceed maxDelay
logrus.Debugf("too many request to %s: sleeping for %d seconds before next attempt", url, delay)
time.Sleep(time.Duration(delay) * time.Second)
delay = delay * 2 // exponential back off
}
continue
}
break
}
return res, err
}
// makeRequestToResolvedURLOnce creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
@ -597,7 +591,7 @@ func (c *dockerClient) getBearerToken(ctx context.Context, challenge challenge,
default:
return nil, errors.Errorf("unexpected http code: %d (%s), URL: %s", res.StatusCode, http.StatusText(res.StatusCode), authReq.URL)
}
tokenBlob, err := ioutil.ReadAll(res.Body)
tokenBlob, err := iolimits.ReadAtMost(res.Body, iolimits.MaxAuthTokenBodySize)
if err != nil {
return nil, err
}
@ -627,7 +621,7 @@ func (c *dockerClient) detectPropertiesHelper(ctx context.Context) error {
defer resp.Body.Close()
logrus.Debugf("Ping %s status %d", url, resp.StatusCode)
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusUnauthorized {
return httpResponseToError(resp)
return httpResponseToError(resp, "")
}
c.challenges = parseAuthHeader(resp.Header)
c.scheme = scheme
@ -690,7 +684,7 @@ func (c *dockerClient) getExtensionsSignatures(ctx context.Context, ref dockerRe
return nil, errors.Wrapf(clientLib.HandleErrorResponse(res), "Error downloading signatures for %s in %s", manifestDigest, ref.ref.Name())
}
body, err := ioutil.ReadAll(res.Body)
body, err := iolimits.ReadAtMost(res.Body, iolimits.MaxSignatureListBodySize)
if err != nil {
return nil, err
}

View File

@ -70,7 +70,7 @@ func GetRepositoryTags(ctx context.Context, sys *types.SystemContext, ref types.
return nil, err
}
defer res.Body.Close()
if err := httpResponseToError(res); err != nil {
if err := httpResponseToError(res, "Error fetching tags list"); err != nil {
return nil, err
}

View File

@ -15,6 +15,7 @@ import (
"strings"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/internal/iolimits"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/pkg/blobinfocache/none"
"github.com/containers/image/v5/types"
@ -58,14 +59,16 @@ func (d *dockerImageDestination) Close() error {
}
func (d *dockerImageDestination) SupportedManifestMIMETypes() []string {
return []string{
mimeTypes := []string{
imgspecv1.MediaTypeImageManifest,
manifest.DockerV2Schema2MediaType,
imgspecv1.MediaTypeImageIndex,
manifest.DockerV2ListMediaType,
manifest.DockerV2Schema1SignedMediaType,
manifest.DockerV2Schema1MediaType,
}
if d.c.sys == nil || !d.c.sys.DockerDisableDestSchema1MIMETypes {
mimeTypes = append(mimeTypes, manifest.DockerV2Schema1SignedMediaType, manifest.DockerV2Schema1MediaType)
}
return mimeTypes
}
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures.
@ -620,7 +623,7 @@ sigExists:
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
body, err := ioutil.ReadAll(res.Body)
body, err := iolimits.ReadAtMost(res.Body, iolimits.MaxErrorBodySize)
if err == nil {
logrus.Debugf("Error body %s", string(body))
}

View File

@ -10,8 +10,10 @@ import (
"net/url"
"os"
"strconv"
"strings"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/internal/iolimits"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/pkg/sysregistriesv2"
"github.com/containers/image/v5/types"
@ -53,43 +55,77 @@ func newImageSource(ctx context.Context, sys *types.SystemContext, ref dockerRef
// non-mirror original location last; this both transparently handles the case
// of no mirrors configured, and ensures we return the error encountered when
// acessing the upstream location if all endpoints fail.
manifestLoadErr := errors.New("Internal error: newImageSource returned without trying any endpoint")
pullSources, err := registry.PullSourcesFromReference(ref.ref)
if err != nil {
return nil, err
}
for _, pullSource := range pullSources {
logrus.Debugf("Trying to pull %q", pullSource.Reference)
dockerRef, err := newReference(pullSource.Reference)
if err != nil {
return nil, err
}
endpointSys := sys
// sys.DockerAuthConfig does not explicitly specify a registry; we must not blindly send the credentials intended for the primary endpoint to mirrors.
if endpointSys != nil && endpointSys.DockerAuthConfig != nil && reference.Domain(dockerRef.ref) != primaryDomain {
copy := *endpointSys
copy.DockerAuthConfig = nil
endpointSys = &copy
}
client, err := newDockerClientFromRef(endpointSys, dockerRef, false, "pull")
if err != nil {
return nil, err
}
client.tlsClientConfig.InsecureSkipVerify = pullSource.Endpoint.Insecure
testImageSource := &dockerImageSource{
ref: dockerRef,
c: client,
}
manifestLoadErr = testImageSource.ensureManifestIsLoaded(ctx)
if manifestLoadErr == nil {
return testImageSource, nil
}
type attempt struct {
ref reference.Named
err error
}
return nil, manifestLoadErr
attempts := []attempt{}
for _, pullSource := range pullSources {
logrus.Debugf("Trying to access %q", pullSource.Reference)
s, err := newImageSourceAttempt(ctx, sys, pullSource, primaryDomain)
if err == nil {
return s, nil
}
logrus.Debugf("Accessing %q failed: %v", pullSource.Reference, err)
attempts = append(attempts, attempt{
ref: pullSource.Reference,
err: err,
})
}
switch len(attempts) {
case 0:
return nil, errors.New("Internal error: newImageSource returned without trying any endpoint")
case 1:
return nil, attempts[0].err // If no mirrors are used, perfectly preserve the error type and add no noise.
default:
// Dont just build a string, try to preserve the typed error.
primary := &attempts[len(attempts)-1]
extras := []string{}
for i := 0; i < len(attempts)-1; i++ {
// This is difficult to fit into a single-line string, when the error can contain arbitrary strings including any metacharacters we decide to use.
// The paired [] at least have some chance of being unambiguous.
extras = append(extras, fmt.Sprintf("[%s: %v]", attempts[i].ref.String(), attempts[i].err))
}
return nil, errors.Wrapf(primary.err, "(Mirrors also failed: %s): %s", strings.Join(extras, "\n"), primary.ref.String())
}
}
// newImageSourceAttempt is an internal helper for newImageSource. Everyone else must call newImageSource.
// Given a pullSource and primaryDomain, return a dockerImageSource if it is reachable.
// The caller must call .Close() on the returned ImageSource.
func newImageSourceAttempt(ctx context.Context, sys *types.SystemContext, pullSource sysregistriesv2.PullSource, primaryDomain string) (*dockerImageSource, error) {
ref, err := newReference(pullSource.Reference)
if err != nil {
return nil, err
}
endpointSys := sys
// sys.DockerAuthConfig does not explicitly specify a registry; we must not blindly send the credentials intended for the primary endpoint to mirrors.
if endpointSys != nil && endpointSys.DockerAuthConfig != nil && reference.Domain(ref.ref) != primaryDomain {
copy := *endpointSys
copy.DockerAuthConfig = nil
endpointSys = &copy
}
client, err := newDockerClientFromRef(endpointSys, ref, false, "pull")
if err != nil {
return nil, err
}
client.tlsClientConfig.InsecureSkipVerify = pullSource.Endpoint.Insecure
s := &dockerImageSource{
ref: ref,
c: client,
}
if err := s.ensureManifestIsLoaded(ctx); err != nil {
return nil, err
}
return s, nil
}
// Reference returns the reference used to set up this source, _as specified by the user_
@ -156,7 +192,8 @@ func (s *dockerImageSource) fetchManifest(ctx context.Context, tagOrDigest strin
if res.StatusCode != http.StatusOK {
return nil, "", errors.Wrapf(client.HandleErrorResponse(res), "Error reading manifest %s in %s", tagOrDigest, s.ref.ref.Name())
}
manblob, err := ioutil.ReadAll(res.Body)
manblob, err := iolimits.ReadAtMost(res.Body, iolimits.MaxManifestBodySize)
if err != nil {
return nil, "", err
}
@ -239,7 +276,7 @@ func (s *dockerImageSource) GetBlob(ctx context.Context, info types.BlobInfo, ca
if err != nil {
return nil, 0, err
}
if err := httpResponseToError(res); err != nil {
if err := httpResponseToError(res, "Error fetching blob"); err != nil {
return nil, 0, err
}
cache.RecordKnownLocation(s.ref.Transport(), bicTransportScope(s.ref), info.Digest, newBICLocationReference(s.ref))
@ -342,7 +379,7 @@ func (s *dockerImageSource) getOneSignature(ctx context.Context, url *url.URL) (
} else if res.StatusCode != http.StatusOK {
return nil, false, errors.Errorf("Error reading signature from %s: status %d (%s)", url.String(), res.StatusCode, http.StatusText(res.StatusCode))
}
sig, err := ioutil.ReadAll(res.Body)
sig, err := iolimits.ReadAtMost(res.Body, iolimits.MaxSignatureBodySize)
if err != nil {
return nil, false, err
}
@ -401,7 +438,7 @@ func deleteImage(ctx context.Context, sys *types.SystemContext, ref dockerRefere
return err
}
defer get.Body.Close()
manifestBody, err := ioutil.ReadAll(get.Body)
manifestBody, err := iolimits.ReadAtMost(get.Body, iolimits.MaxManifestBodySize)
if err != nil {
return err
}
@ -424,7 +461,7 @@ func deleteImage(ctx context.Context, sys *types.SystemContext, ref dockerRefere
}
defer delete.Body.Close()
body, err := ioutil.ReadAll(delete.Body)
body, err := iolimits.ReadAtMost(delete.Body, iolimits.MaxErrorBodySize)
if err != nil {
return err
}

View File

@ -14,7 +14,7 @@ var (
// docker V1 registry.
ErrV1NotSupported = errors.New("can't talk to a V1 docker registry")
// ErrTooManyRequests is returned when the status code returned is 429
ErrTooManyRequests = errors.New("too many request to registry")
ErrTooManyRequests = errors.New("too many requests to registry")
)
// ErrUnauthorizedForCredentials is returned when the status code returned is 401
@ -26,9 +26,9 @@ func (e ErrUnauthorizedForCredentials) Error() string {
return fmt.Sprintf("unable to retrieve auth token: invalid username/password: %s", e.Err.Error())
}
// httpResponseToError translates the https.Response into an error. It returns
// httpResponseToError translates the https.Response into an error, possibly prefixing it with the supplied context. It returns
// nil if the response is not considered an error.
func httpResponseToError(res *http.Response) error {
func httpResponseToError(res *http.Response, context string) error {
switch res.StatusCode {
case http.StatusOK:
return nil
@ -38,6 +38,9 @@ func httpResponseToError(res *http.Response) error {
err := client.HandleErrorResponse(res)
return ErrUnauthorizedForCredentials{Err: err}
default:
return perrors.Errorf("invalid status code from registry %d (%s)", res.StatusCode, http.StatusText(res.StatusCode))
if context != "" {
context = context + ": "
}
return perrors.Errorf("%sinvalid status code from registry %d (%s)", context, res.StatusCode, http.StatusText(res.StatusCode))
}
}

View File

@ -13,6 +13,7 @@ import (
"time"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/internal/iolimits"
"github.com/containers/image/v5/internal/tmpdir"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/types"
@ -143,7 +144,7 @@ func (d *Destination) PutBlob(ctx context.Context, stream io.Reader, inputInfo t
}
if isConfig {
buf, err := ioutil.ReadAll(stream)
buf, err := iolimits.ReadAtMost(stream, iolimits.MaxConfigBodySize)
if err != nil {
return types.BlobInfo{}, errors.Wrap(err, "Error reading Config file stream")
}

View File

@ -11,6 +11,7 @@ import (
"path"
"sync"
"github.com/containers/image/v5/internal/iolimits"
"github.com/containers/image/v5/internal/tmpdir"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/pkg/compression"
@ -203,13 +204,13 @@ func findTarComponent(inputFile io.Reader, path string) (*tar.Reader, *tar.Heade
}
// readTarComponent returns full contents of componentPath.
func (s *Source) readTarComponent(path string) ([]byte, error) {
func (s *Source) readTarComponent(path string, limit int) ([]byte, error) {
file, err := s.openTarComponent(path)
if err != nil {
return nil, errors.Wrapf(err, "Error loading tar component %s", path)
}
defer file.Close()
bytes, err := ioutil.ReadAll(file)
bytes, err := iolimits.ReadAtMost(file, limit)
if err != nil {
return nil, err
}
@ -240,7 +241,7 @@ func (s *Source) ensureCachedDataIsPresentPrivate() error {
}
// Read and parse config.
configBytes, err := s.readTarComponent(tarManifest[0].Config)
configBytes, err := s.readTarComponent(tarManifest[0].Config, iolimits.MaxConfigBodySize)
if err != nil {
return err
}
@ -266,7 +267,7 @@ func (s *Source) ensureCachedDataIsPresentPrivate() error {
// loadTarManifest loads and decodes the manifest.json.
func (s *Source) loadTarManifest() ([]ManifestItem, error) {
// FIXME? Do we need to deal with the legacy format?
bytes, err := s.readTarComponent(manifestFileName)
bytes, err := s.readTarComponent(manifestFileName, iolimits.MaxTarFileManifestSize)
if err != nil {
return nil, err
}

View File

@ -7,10 +7,10 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"strings"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/internal/iolimits"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/pkg/blobinfocache/none"
"github.com/containers/image/v5/types"
@ -102,7 +102,7 @@ func (m *manifestSchema2) ConfigBlob(ctx context.Context) ([]byte, error) {
return nil, err
}
defer stream.Close()
blob, err := ioutil.ReadAll(stream)
blob, err := iolimits.ReadAtMost(stream, iolimits.MaxConfigBodySize)
if err != nil {
return nil, err
}

View File

@ -4,9 +4,9 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/internal/iolimits"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/pkg/blobinfocache/none"
"github.com/containers/image/v5/types"
@ -67,7 +67,7 @@ func (m *manifestOCI1) ConfigBlob(ctx context.Context) ([]byte, error) {
return nil, err
}
defer stream.Close()
blob, err := ioutil.ReadAll(stream)
blob, err := iolimits.ReadAtMost(stream, iolimits.MaxConfigBodySize)
if err != nil {
return nil, err
}

View File

@ -0,0 +1,60 @@
package iolimits
import (
"io"
"io/ioutil"
"github.com/pkg/errors"
)
// All constants below are intended to be used as limits for `ReadAtMost`. The
// immediate use-case for limiting the size of in-memory copied data is to
// protect against OOM DOS attacks as described inCVE-2020-1702. Instead of
// copying data until running out of memory, we error out after hitting the
// specified limit.
const (
// megaByte denotes one megabyte and is intended to be used as a limit in
// `ReadAtMost`.
megaByte = 1 << 20
// MaxManifestBodySize is the maximum allowed size of a manifest. The limit
// of 4 MB aligns with the one of a Docker registry:
// https://github.com/docker/distribution/blob/a8371794149d1d95f1e846744b05c87f2f825e5a/registry/handlers/manifests.go#L30
MaxManifestBodySize = 4 * megaByte
// MaxAuthTokenBodySize is the maximum allowed size of an auth token.
// The limit of 1 MB is considered to be greatly sufficient.
MaxAuthTokenBodySize = megaByte
// MaxSignatureListBodySize is the maximum allowed size of a signature list.
// The limit of 4 MB is considered to be greatly sufficient.
MaxSignatureListBodySize = 4 * megaByte
// MaxSignatureBodySize is the maximum allowed size of a signature.
// The limit of 4 MB is considered to be greatly sufficient.
MaxSignatureBodySize = 4 * megaByte
// MaxErrorBodySize is the maximum allowed size of an error-response body.
// The limit of 1 MB is considered to be greatly sufficient.
MaxErrorBodySize = megaByte
// MaxConfigBodySize is the maximum allowed size of a config blob.
// The limit of 4 MB is considered to be greatly sufficient.
MaxConfigBodySize = 4 * megaByte
// MaxOpenShiftStatusBody is the maximum allowed size of an OpenShift status body.
// The limit of 4 MB is considered to be greatly sufficient.
MaxOpenShiftStatusBody = 4 * megaByte
// MaxTarFileManifestSize is the maximum allowed size of a (docker save)-like manifest (which may contain multiple images)
// The limit of 1 MB is considered to be greatly sufficient.
MaxTarFileManifestSize = megaByte
)
// ReadAtMost reads from reader and errors out if the specified limit (in bytes) is exceeded.
func ReadAtMost(reader io.Reader, limit int) ([]byte, error) {
limitedReader := io.LimitReader(reader, int64(limit+1))
res, err := ioutil.ReadAll(limitedReader)
if err != nil {
return nil, err
}
if len(res) > limit {
return nil, errors.Errorf("exceeded maximum allowed size of %d bytes", limit)
}
return res, nil
}

View File

@ -7,13 +7,13 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/internal/iolimits"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/types"
"github.com/containers/image/v5/version"
@ -102,7 +102,7 @@ func (c *openshiftClient) doRequest(ctx context.Context, method, path string, re
return nil, err
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
body, err := iolimits.ReadAtMost(res.Body, iolimits.MaxOpenShiftStatusBody)
if err != nil {
return nil, err
}

View File

@ -139,7 +139,7 @@ func (m *gpgmeSigningMechanism) Sign(input []byte, keyIdentity string) ([]byte,
}
// Verify parses unverifiedSignature and returns the content and the signer's identity
func (m gpgmeSigningMechanism) Verify(unverifiedSignature []byte) (contents []byte, keyIdentity string, err error) {
func (m *gpgmeSigningMechanism) Verify(unverifiedSignature []byte) (contents []byte, keyIdentity string, err error) {
signedBuffer := bytes.Buffer{}
signedData, err := gpgme.NewDataWriter(&signedBuffer)
if err != nil {
@ -170,6 +170,6 @@ func (m gpgmeSigningMechanism) Verify(unverifiedSignature []byte) (contents []by
// WARNING: The short key identifier (which correponds to "Key ID" for OpenPGP keys)
// is NOT the same as a "key identity" used in other calls ot this interface, and
// the values may have no recognizable relationship if the public key is not available.
func (m gpgmeSigningMechanism) UntrustedSignatureContents(untrustedSignature []byte) (untrustedContents []byte, shortKeyIdentifier string, err error) {
func (m *gpgmeSigningMechanism) UntrustedSignatureContents(untrustedSignature []byte) (untrustedContents []byte, shortKeyIdentifier string, err error) {
return gpgUntrustedSignatureContents(untrustedSignature)
}

View File

@ -154,6 +154,6 @@ func (m *openpgpSigningMechanism) Verify(unverifiedSignature []byte) (contents [
// WARNING: The short key identifier (which correponds to "Key ID" for OpenPGP keys)
// is NOT the same as a "key identity" used in other calls ot this interface, and
// the values may have no recognizable relationship if the public key is not available.
func (m openpgpSigningMechanism) UntrustedSignatureContents(untrustedSignature []byte) (untrustedContents []byte, shortKeyIdentifier string, err error) {
func (m *openpgpSigningMechanism) UntrustedSignatureContents(untrustedSignature []byte) (untrustedContents []byte, shortKeyIdentifier string, err error) {
return gpgUntrustedSignatureContents(untrustedSignature)
}

View File

@ -547,6 +547,8 @@ type SystemContext struct {
// Note that this field is used mainly to integrate containers/image into projectatomic/docker
// in order to not break any existing docker's integration tests.
DockerDisableV1Ping bool
// If true, dockerImageDestination.SupportedManifestMIMETypes will omit the Schema1 media types from the supported list
DockerDisableDestSchema1MIMETypes bool
// Directory to use for OSTree temporary files
OSTreeTmpDirPath string

View File

@ -6,7 +6,7 @@ const (
// VersionMajor is for an API incompatible changes
VersionMajor = 5
// VersionMinor is for functionality in a backwards-compatible manner
VersionMinor = 1
VersionMinor = 2
// VersionPatch is for backwards-compatible bug fixes
VersionPatch = 0