Skip to content

Commit

Permalink
[3.0.1-rhel] handle corrupted images
Browse files Browse the repository at this point in the history
While various execution paths in Podman already handle corrupted
images, `podman-{create,image exists,run}` did not.

Some corruptions can only be detected when accessing the individual
data.  A reliable way of accessing such data is accessing its layers.
Hence, an image will only be listed to exist if a) it has been found
and b) can be inspected.  If the inspection fails, the image will be
reported to not exists but without an error; the error will only be
logged.  This allows for properly recovering and pull the image, even
in `podman-{create,run}`.

Podman will now behave as follows:
```
$ ./bin/podman run --rm nginx echo "it works!"
ERRO[0000] Image nginx exists in local storage but may be corrupted: layer not known
Resolved "nginx" as an alias (/home/vrothberg/.cache/containers/short-name-aliases.conf)
Trying to pull docker.io/library/nginx:latest...
Getting image source signatures
Copying blob 351ad75a6cfa skipped: already exists
Copying blob febe5bd23e98 skipped: already exists
Copying blob 30afc0b18f67 skipped: already exists
Copying blob 596b1d696923 skipped: already exists
Copying blob 8283eee92e2f skipped: already exists
Copying blob 69692152171a done
Copying config d1a364dc54 done
Writing manifest to image destination
Storing signatures
it works!
```

Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1966872
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
  • Loading branch information
vrothberg committed Jun 10, 2021
1 parent 1a6b776 commit 5764414
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 5 deletions.
32 changes: 30 additions & 2 deletions libpod/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,18 @@ func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile
if pullType != util.PullImageAlways {
newImage, err := ir.NewFromLocal(name)
if err == nil {
return newImage, nil
} else if pullType == util.PullImageNever {
// The image may be listed in the local storage but
// if it's corrupted, log an error and continue
// pulling.
// https://bugzilla.redhat.com/show_bug.cgi?id=1966872
if err := newImage.CheckCorrupted(ctx, name); err != nil {
logrus.Error(err.Error())
} else {
return newImage, nil
}
}

if pullType == util.PullImageNever {
return nil, err
}
}
Expand Down Expand Up @@ -714,6 +724,24 @@ func (ir *Runtime) getImages(rwOnly bool) ([]*Image, error) {
return newImages, nil
}

// CheckCorrupted checks if the image may be corrupted in the local storage.
func (i *Image) CheckCorrupted(ctx context.Context, name string) error {
// If it's a manifest list, we're good for now.
if _, err := i.getManifestList(); err == nil {
return nil
}

ref, err := is.Transport.ParseStoreReference(i.imageruntime.store, "@"+i.ID())
if err != nil {
return err
}

if _, err := ref.NewImage(ctx, nil); err != nil {
return errors.Errorf("Image %s exists in local storage but may be corrupted: %v", name, err)
}
return nil
}

// getImageDigest creates an image object and uses the hex value of the digest as the image ID
// for parsing the store reference
func getImageDigest(ctx context.Context, src types.ImageReference, sc *types.SystemContext) (string, error) {
Expand Down
14 changes: 11 additions & 3 deletions pkg/domain/infra/abi/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,25 @@ import (
"github.com/sirupsen/logrus"
)

func (ir *ImageEngine) Exists(_ context.Context, nameOrID string) (*entities.BoolReport, error) {
_, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID)
func (ir *ImageEngine) Exists(ctx context.Context, nameOrID string) (*entities.BoolReport, error) {
img, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID)
if err != nil {
if errors.Cause(err) == define.ErrMultipleImages {
return &entities.BoolReport{Value: true}, nil
}
if errors.Cause(err) != define.ErrNoSuchImage {
return nil, err
}
return &entities.BoolReport{Value: false}, nil
}
// https://bugzilla.redhat.com/show_bug.cgi?id=1966872 - only report an
// image to exist if it's not corrupted.
if err := img.CheckCorrupted(ctx, nameOrID); err != nil {
// Use Debug only since the pull backend already uses Error.
logrus.Debug(err.Error())
return &entities.BoolReport{Value: false}, nil
}
return &entities.BoolReport{Value: err == nil}, nil
return &entities.BoolReport{Value: true}, nil
}

func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) ([]*reports.PruneReport, error) {
Expand Down
88 changes: 88 additions & 0 deletions test/system/335-corrupt-layers.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env bats -*- bats -*-
#
# All tests in here perform nasty manipulations on image storage.
#

load helpers

###############################################################################
# BEGIN setup/teardown

# Create a scratch directory; this is what we'll use for image store and cache
if [ -z "${PODMAN_CORRUPT_TEST_WORKDIR}" ]; then
export PODMAN_CORRUPT_TEST_WORKDIR=$(mktemp -d --tmpdir=${BATS_TMPDIR:-${TMPDIR:-/tmp}} podman_corrupt_test.XXXXXX)
fi

# All tests in this file (and ONLY in this file) run with a custom rootdir
function setup() {
skip_if_remote "none of these tests run under podman-remote"
_PODMAN_TEST_OPTS="--storage-driver=vfs --root ${PODMAN_CORRUPT_TEST_WORKDIR}/root"
}

function teardown() {
# No other tests should ever run with this custom rootdir
unset _PODMAN_TEST_OPTS

is_remote && return

# Clean up
umount ${PODMAN_CORRUPT_TEST_WORKDIR}/root/overlay || true
if is_rootless; then
run_podman unshare rm -rf ${PODMAN_CORRUPT_TEST_WORKDIR}/root
else
rm -rf ${PODMAN_CORRUPT_TEST_WORKDIR}/root
fi
}

# END setup/teardown
###############################################################################
# BEGIN primary test helper

# This is our main action, invoked by every actual test. It:
# - creates a new empty rootdir
# - populates it with our crafted test image
# - removes [ manifest, blob ]
# - confirms that "podman images" throws an error
# - runs the specified command (rmi -a -f, prune, reset, etc)
# - confirms that it succeeds, and also emits expected warnings
function _corrupt_image_test() {
run_podman info --format "{{.Store.GraphRoot}}"
indexPath=$output/*-layers/layers.json

run_podman pull $IMAGE
run_podman image exists $IMAGE

run jq 'del(.[0])' $indexPath > $indexPath
# A corrupted image won't be marked as existing.
run_podman 1 image exists $IMAGE

# Run the requested command. Confirm it succeeds, with suitable warnings
run_podman $*
is "$output" ".*Image $IMAGE exists in local storage but may be corrupted: layer not known.*"
run_podman rmi -f $IMAGE
}

# END primary test helper
###############################################################################
# BEGIN actual tests

@test "podman corrupt images - run --rm" {
_corrupt_image_test "run --rm $IMAGE ls"
}

@test "podman corrupt images - create" {
_corrupt_image_test "create $IMAGE ls"
}

# END actual tests
###############################################################################
# BEGIN final cleanup

@test "podman corrupt images - cleanup" {
rm -rf ${PODMAN_CORRUPT_TEST_WORKDIR}
}

# END final cleanup
###############################################################################

# vim: filetype=sh

0 comments on commit 5764414

Please sign in to comment.