Files
moby/daemon/migration.go
Paweł Gronowski 26fea35942 daemon: Fix panic on Windows when restoring pre v28 container
The container platform migration tries to deduce the platform data from
the containerd content store if it's available.

However, on Windows we currently default to a non-containerd runtime
setup, so the containerd client is nil and accessing its content store
paniced:

```
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x0 pc=0x11b48e4]

goroutine 87 [running]:
github.com/containerd/containerd/v2/client.(*Client).ContentStore(0xc0003a0008?)
	/go/src/github.com/docker/docker/vendor/github.com/containerd/containerd/v2/client/client.go:645 +0x24
github.com/docker/docker/daemon.(*Daemon).load(0xc00026e488, {0xc000c13d40, 0x40})
	/go/src/github.com/docker/docker/daemon/container.go:84 +0x289
github.com/docker/docker/daemon.(*Daemon).restore.func1({0xc000c13d40, 0x40})
	/go/src/github.com/docker/docker/daemon/daemon.go:236 +0x207
created by github.com/docker/docker/daemon.(*Daemon).restore in goroutine 1
	/go/src/github.com/docker/docker/daemon/daemon.go:229 +0x1a7
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x0 pc=0x11b48e4]

goroutine 90 [running]:
github.com/containerd/containerd/v2/client.(*Client).ContentStore(0xc000313608?)
	/go/src/github.com/docker/docker/vendor/github.com/containerd/containerd/v2/client/client.go:645 +0x24
github.com/docker/docker/daemon.(*Daemon).load(0xc00026e488, {0xc000c13e00, 0x40})
	/go/src/github.com/docker/docker/daemon/container.go:84 +0x289
github.com/docker/docker/daemon.(*Daemon).restore.func1({0xc000c13e00, 0x40})
	/go/src/github.com/docker/docker/daemon/daemon.go:236 +0x207
created by github.com/docker/docker/daemon.(*Daemon).restore in goroutine 1
	/go/src/github.com/docker/docker/daemon/daemon.go:229 +0x1a7
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x0 pc=0x11b48e4]
```

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-03-11 12:25:42 +01:00

141 lines
4.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package daemon
import (
"context"
"encoding/json"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/log"
"github.com/containerd/platforms"
"github.com/docker/docker/api/types/backend"
"github.com/docker/docker/container"
"github.com/docker/docker/image"
"github.com/docker/docker/internal/multierror"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
func migrateContainerOS(ctx context.Context,
migration platformReader,
ctr *container.Container,
) {
deduced, err := deduceContainerPlatform(ctx, migration, ctr)
if err != nil {
log.G(ctx).WithFields(log.Fields{
"container": ctr.ID,
"error": err,
}).Warn("failed to deduce the container architecture")
ctr.ImagePlatform.OS = ctr.OS //nolint:staticcheck // ignore SA1019
return
}
ctr.ImagePlatform = deduced
}
type platformReader interface {
ReadPlatformFromConfigByImageManifest(ctx context.Context, desc ocispec.Descriptor) (ocispec.Platform, error)
ReadPlatformFromImage(ctx context.Context, id image.ID) (ocispec.Platform, error)
}
// deduceContainerPlatform tries to deduce `ctr`'s platform.
// If both `ctr.OS` and `ctr.ImageManifest` are empty, assume the image comes
// from a pre-OS times and use the host platform to match the behavior of
// [container.FromDisk].
// Otherwise:
// - `ctr.ImageManifest.Platform` is used, if it exists and is not empty.
// - The platform from the manifest's config is used, if `ctr.ImageManifest` exists
// and we're able to load its config from the content store.
// - The platform found by loading the image from the image service by ID (using
// `ctr.ImageID`) is used this looks for the best *present* matching manifest in
// the store.
func deduceContainerPlatform(
ctx context.Context,
migration platformReader,
ctr *container.Container,
) (ocispec.Platform, error) {
if ctr.OS == "" && ctr.ImageManifest == nil { //nolint:staticcheck // ignore SA1019 because we are testing deprecated field migration
return platforms.DefaultSpec(), nil
}
var errs []error
isValidPlatform := func(p ocispec.Platform) bool {
return p.OS != "" && p.Architecture != ""
}
if ctr.ImageManifest != nil {
if ctr.ImageManifest.Platform != nil {
return *ctr.ImageManifest.Platform, nil
}
if ctr.ImageManifest != nil {
p, err := migration.ReadPlatformFromConfigByImageManifest(ctx, *ctr.ImageManifest)
if err != nil {
errs = append(errs, err)
} else {
if isValidPlatform(p) {
return p, nil
}
errs = append(errs, errors.New("malformed image config obtained by ImageManifestDescriptor"))
}
}
}
if ctr.ImageID != "" {
p, err := migration.ReadPlatformFromImage(ctx, ctr.ImageID)
if err != nil {
errs = append(errs, err)
} else {
if isValidPlatform(p) {
return p, nil
}
errs = append(errs, errors.New("malformed image config obtained by image id"))
}
}
return ocispec.Platform{}, errors.Wrap(multierror.Join(errs...), "cannot deduce the container platform")
}
type daemonPlatformReader struct {
imageService ImageService
content content.Provider
}
func (r daemonPlatformReader) ReadPlatformFromConfigByImageManifest(
ctx context.Context,
desc ocispec.Descriptor,
) (ocispec.Platform, error) {
if r.content == nil {
return ocispec.Platform{}, errors.New("not an containerd image store")
}
b, err := content.ReadBlob(ctx, r.content, desc)
if err != nil {
return ocispec.Platform{}, err
}
var mfst ocispec.Manifest
if err := json.Unmarshal(b, &mfst); err != nil {
return ocispec.Platform{}, err
}
b, err = content.ReadBlob(ctx, r.content, mfst.Config)
if err != nil {
return ocispec.Platform{}, err
}
var plat ocispec.Platform
if err := json.Unmarshal(b, &plat); err != nil {
return ocispec.Platform{}, err
}
return plat, nil
}
func (r daemonPlatformReader) ReadPlatformFromImage(ctx context.Context, id image.ID) (ocispec.Platform, error) {
img, err := r.imageService.GetImage(ctx, id.String(), backend.GetImageOpts{})
if err != nil {
return ocispec.Platform{}, err
}
return img.Platform(), nil
}