Skip to content

Commit

Permalink
make podman rmi more robust
Browse files Browse the repository at this point in the history
The c/storage library is subject to TOCTOUs as the central container and
image storage may be shared by many instances of many tools.  As shown
in containers#6510, it's fairly easy to have multiple instances of Podman running
in parallel and yield image-lookup errors when removing them.

The underlying issue is the TOCTOU of removal being split into multiple
stages of first reading the local images and then removing them.  Some
images may already have been removed in between the two stages. To make
image removal more robust, handle errors at stage two when a given image
is not present (anymore) in the storage.

Fixes: containers#6510
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
  • Loading branch information
vrothberg committed Feb 8, 2021
1 parent 69ddbde commit 5919220
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 4 deletions.
17 changes: 13 additions & 4 deletions pkg/domain/infra/abi/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -580,12 +580,21 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie
// without having to pass all local data around.
deleteImage := func(img *image.Image) error {
results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force)
if err != nil {
switch errors.Cause(err) {
case nil:
// Removal worked, so let's report it.
report.Deleted = append(report.Deleted, results.Deleted)
report.Untagged = append(report.Untagged, results.Untagged...)
return nil
case storage.ErrImageUnknown:
// The image must have been removed already (see #6510).
report.Deleted = append(report.Deleted, img.ID())
report.Untagged = append(report.Untagged, img.ID())
return nil
default:
// Fatal error.
return err
}
report.Deleted = append(report.Deleted, results.Deleted)
report.Untagged = append(report.Untagged, results.Untagged...)
return nil
}

// Delete all images from the local storage.
Expand Down
29 changes: 29 additions & 0 deletions test/e2e/rmi_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package integration

import (
"fmt"
"os"
"sync"

. "github.com/containers/podman/v2/test/utils"
. "github.com/onsi/ginkgo"
Expand Down Expand Up @@ -275,4 +277,31 @@ RUN find $LOCAL
match, _ := session.ErrorGrepString("image name or ID must be specified")
Expect(match).To(BeTrue())
})

It("podman image rm - concurrent with shared layers", func() {
// #6510 has shown a fairly simple reproducer to force storage
// errors during parallel image removal. Since it's subject to
// a race, we may not hit the condition a 100 percent of times
// but ocal reproducers hit it all the time.

var wg sync.WaitGroup

buildAndRemove := func(i int) {
defer wg.Done()
imageName := fmt.Sprintf("rmtest:%d", i)
containerfile := `FROM quay.io/libpod/cirros:latest
RUN ` + fmt.Sprintf("touch %s", imageName)

podmanTest.BuildImage(containerfile, imageName, "false")
session := podmanTest.Podman([]string{"rmi", "-a"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
}

wg.Add(10)
for i := 0; i < 10; i++ {
buildAndRemove(i)
}
wg.Wait()
})
})

0 comments on commit 5919220

Please sign in to comment.