mirror of https://github.com/containers/podman.git
				
				
				
			Merge pull request #23766 from Luap99/http-attach
fix races in the HTTP attach API
This commit is contained in:
		
						commit
						10363e1e81
					
				|  | @ -53,6 +53,11 @@ func init() { | ||||||
| 
 | 
 | ||||||
| 	flags.BoolVar(&cleanupOptions.Remove, "rm", false, "After cleanup, remove the container entirely") | 	flags.BoolVar(&cleanupOptions.Remove, "rm", false, "After cleanup, remove the container entirely") | ||||||
| 	flags.BoolVar(&cleanupOptions.RemoveImage, "rmi", false, "After cleanup, remove the image entirely") | 	flags.BoolVar(&cleanupOptions.RemoveImage, "rmi", false, "After cleanup, remove the image entirely") | ||||||
|  | 
 | ||||||
|  | 	stoppedOnlyFlag := "stopped-only" | ||||||
|  | 	flags.BoolVar(&cleanupOptions.StoppedOnly, stoppedOnlyFlag, false, "Only cleanup when the container is in the stopped state") | ||||||
|  | 	_ = flags.MarkHidden(stoppedOnlyFlag) | ||||||
|  | 
 | ||||||
| 	validate.AddLatestFlag(cleanupCommand, &cleanupOptions.Latest) | 	validate.AddLatestFlag(cleanupCommand, &cleanupOptions.Latest) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -40,7 +40,10 @@ func (c *Container) Init(ctx context.Context, recursive bool) error { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	return c.initUnlocked(ctx, recursive) | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|  | func (c *Container) initUnlocked(ctx context.Context, recursive bool) error { | ||||||
| 	if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateStopped, define.ContainerStateExited) { | 	if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateStopped, define.ContainerStateExited) { | ||||||
| 		return fmt.Errorf("container %s has already been created in runtime: %w", c.ID(), define.ErrCtrStateInvalid) | 		return fmt.Errorf("container %s has already been created in runtime: %w", c.ID(), define.ErrCtrStateInvalid) | ||||||
| 	} | 	} | ||||||
|  | @ -342,25 +345,36 @@ func (c *Container) HTTPAttach(r *http.Request, w http.ResponseWriter, streams * | ||||||
| 		close(hijackDone) | 		close(hijackDone) | ||||||
| 	}() | 	}() | ||||||
| 
 | 
 | ||||||
|  | 	locked := false | ||||||
| 	if !c.batched { | 	if !c.batched { | ||||||
|  | 		locked = true | ||||||
| 		c.lock.Lock() | 		c.lock.Lock() | ||||||
|  | 		defer func() { | ||||||
|  | 			if locked { | ||||||
|  | 				c.lock.Unlock() | ||||||
|  | 			} | ||||||
|  | 		}() | ||||||
| 		if err := c.syncContainer(); err != nil { | 		if err := c.syncContainer(); err != nil { | ||||||
| 			c.lock.Unlock() |  | ||||||
| 
 |  | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		// We are NOT holding the lock for the duration of the function.
 |  | ||||||
| 		c.lock.Unlock() |  | ||||||
| 	} | 	} | ||||||
| 
 | 	// For Docker compatibility, we need to re-initialize containers in these states.
 | ||||||
| 	if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning) { | 	if c.ensureState(define.ContainerStateConfigured, define.ContainerStateExited, define.ContainerStateStopped) { | ||||||
| 		return fmt.Errorf("can only attach to created or running containers: %w", define.ErrCtrStateInvalid) | 		if err := c.initUnlocked(r.Context(), c.config.Pod != ""); err != nil { | ||||||
|  | 			return fmt.Errorf("preparing container %s for attach: %w", c.ID(), err) | ||||||
|  | 		} | ||||||
|  | 	} else if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning) { | ||||||
|  | 		return fmt.Errorf("can only attach to created or running containers - currently in state %s: %w", c.state.State.String(), define.ErrCtrStateInvalid) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !streamAttach && !streamLogs { | 	if !streamAttach && !streamLogs { | ||||||
| 		return fmt.Errorf("must specify at least one of stream or logs: %w", define.ErrInvalidArg) | 		return fmt.Errorf("must specify at least one of stream or logs: %w", define.ErrInvalidArg) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// We are NOT holding the lock for the duration of the function.
 | ||||||
|  | 	locked = false | ||||||
|  | 	c.lock.Unlock() | ||||||
|  | 
 | ||||||
| 	logrus.Infof("Performing HTTP Hijack attach to container %s", c.ID()) | 	logrus.Infof("Performing HTTP Hijack attach to container %s", c.ID()) | ||||||
| 
 | 
 | ||||||
| 	c.newContainerEvent(events.Attach) | 	c.newContainerEvent(events.Attach) | ||||||
|  | @ -771,8 +785,10 @@ func (c *Container) WaitForConditionWithInterval(ctx context.Context, waitTimeou | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Cleanup unmounts all mount points in container and cleans up container storage
 | // Cleanup unmounts all mount points in container and cleans up container storage
 | ||||||
| // It also cleans up the network stack
 | // It also cleans up the network stack.
 | ||||||
| func (c *Container) Cleanup(ctx context.Context) error { | // onlyStopped is set by the podman container cleanup to ensure we only cleanup a stopped container,
 | ||||||
|  | // all other states mean another process already called cleanup before us which is fine in such cases.
 | ||||||
|  | func (c *Container) Cleanup(ctx context.Context, onlyStopped bool) error { | ||||||
| 	if !c.batched { | 	if !c.batched { | ||||||
| 		c.lock.Lock() | 		c.lock.Lock() | ||||||
| 		defer c.lock.Unlock() | 		defer c.lock.Unlock() | ||||||
|  | @ -794,6 +810,9 @@ func (c *Container) Cleanup(ctx context.Context) error { | ||||||
| 	if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateCreated, define.ContainerStateStopped, define.ContainerStateStopping, define.ContainerStateExited) { | 	if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateCreated, define.ContainerStateStopped, define.ContainerStateStopping, define.ContainerStateExited) { | ||||||
| 		return fmt.Errorf("container %s is running or paused, refusing to clean up: %w", c.ID(), define.ErrCtrStateInvalid) | 		return fmt.Errorf("container %s is running or paused, refusing to clean up: %w", c.ID(), define.ErrCtrStateInvalid) | ||||||
| 	} | 	} | ||||||
|  | 	if onlyStopped && !c.ensureState(define.ContainerStateStopped) { | ||||||
|  | 		return fmt.Errorf("container %s is not stopped and only cleanup for a stopped container was requested: %w", c.ID(), define.ErrCtrStateInvalid) | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	// if the container was not created in the oci runtime or was already cleaned up, then do nothing
 | 	// if the container was not created in the oci runtime or was already cleaned up, then do nothing
 | ||||||
| 	if c.ensureState(define.ContainerStateConfigured, define.ContainerStateExited) { | 	if c.ensureState(define.ContainerStateConfigured, define.ContainerStateExited) { | ||||||
|  |  | ||||||
|  | @ -180,7 +180,7 @@ func (p *Pod) stopWithTimeout(ctx context.Context, cleanup bool, timeout int) (m | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if cleanup { | 			if cleanup { | ||||||
| 				err := c.Cleanup(ctx) | 				err := c.Cleanup(ctx, false) | ||||||
| 				if err != nil && !errors.Is(err, define.ErrCtrStateInvalid) && !errors.Is(err, define.ErrCtrStopped) { | 				if err != nil && !errors.Is(err, define.ErrCtrStateInvalid) && !errors.Is(err, define.ErrCtrStopped) { | ||||||
| 					return err | 					return err | ||||||
| 				} | 				} | ||||||
|  | @ -299,7 +299,7 @@ func (p *Pod) Cleanup(ctx context.Context) (map[string]error, error) { | ||||||
| 		c := ctr | 		c := ctr | ||||||
| 		logrus.Debugf("Adding parallel job to clean up container %s", c.ID()) | 		logrus.Debugf("Adding parallel job to clean up container %s", c.ID()) | ||||||
| 		retChan := parallel.Enqueue(ctx, func() error { | 		retChan := parallel.Enqueue(ctx, func() error { | ||||||
| 			return c.Cleanup(ctx) | 			return c.Cleanup(ctx, false) | ||||||
| 		}) | 		}) | ||||||
| 
 | 
 | ||||||
| 		ctrErrChan[c.ID()] = retChan | 		ctrErrChan[c.ID()] = retChan | ||||||
|  |  | ||||||
|  | @ -4,11 +4,9 @@ package compat | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" |  | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 
 | 
 | ||||||
| 	"github.com/containers/podman/v5/libpod" | 	"github.com/containers/podman/v5/libpod" | ||||||
| 	"github.com/containers/podman/v5/libpod/define" |  | ||||||
| 	"github.com/containers/podman/v5/pkg/api/handlers/utils" | 	"github.com/containers/podman/v5/pkg/api/handlers/utils" | ||||||
| 	"github.com/containers/podman/v5/pkg/api/server/idle" | 	"github.com/containers/podman/v5/pkg/api/server/idle" | ||||||
| 	api "github.com/containers/podman/v5/pkg/api/types" | 	api "github.com/containers/podman/v5/pkg/api/types" | ||||||
|  | @ -79,22 +77,6 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	state, err := ctr.State() |  | ||||||
| 	if err != nil { |  | ||||||
| 		utils.InternalServerError(w, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	// For Docker compatibility, we need to re-initialize containers in these states.
 |  | ||||||
| 	if state == define.ContainerStateConfigured || state == define.ContainerStateExited || state == define.ContainerStateStopped { |  | ||||||
| 		if err := ctr.Init(r.Context(), ctr.PodID() != ""); err != nil { |  | ||||||
| 			utils.Error(w, http.StatusConflict, fmt.Errorf("preparing container %s for attach: %w", ctr.ID(), err)) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} else if !(state == define.ContainerStateCreated || state == define.ContainerStateRunning) { |  | ||||||
| 		utils.InternalServerError(w, fmt.Errorf("can only attach to created or running containers - currently in state %s: %w", state.String(), define.ErrCtrStateInvalid)) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	logErr := func(e error) { | 	logErr := func(e error) { | ||||||
| 		logrus.Errorf("Error attaching to container %s: %v", ctr.ID(), e) | 		logrus.Errorf("Error attaching to container %s: %v", ctr.ID(), e) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -371,6 +371,7 @@ type ContainerCleanupOptions struct { | ||||||
| 	Latest      bool | 	Latest      bool | ||||||
| 	Remove      bool | 	Remove      bool | ||||||
| 	RemoveImage bool | 	RemoveImage bool | ||||||
|  | 	StoppedOnly bool // Only cleanup if the container is stopped, i.e. was running before
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ContainerCleanupReport describes the response from a
 | // ContainerCleanupReport describes the response from a
 | ||||||
|  |  | ||||||
|  | @ -313,7 +313,7 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			if err = c.Cleanup(ctx); err != nil { | 			if err = c.Cleanup(ctx, false); err != nil { | ||||||
| 				// The container could still have been removed, as we unlocked
 | 				// The container could still have been removed, as we unlocked
 | ||||||
| 				// after we stopped it.
 | 				// after we stopped it.
 | ||||||
| 				if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) { | 				if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) { | ||||||
|  | @ -1320,7 +1320,7 @@ func (ic *ContainerEngine) ContainerCleanup(ctx context.Context, namesOrIds []st | ||||||
| 				report.RmErr = fmt.Errorf("failed to clean up and remove container %v: %w", ctr.ID(), err) | 				report.RmErr = fmt.Errorf("failed to clean up and remove container %v: %w", ctr.ID(), err) | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			err := ctr.Cleanup(ctx) | 			err := ctr.Cleanup(ctx, options.StoppedOnly) | ||||||
| 			// ignore error if ctr is removed or cannot be cleaned up, likely the ctr was already restarted by another process
 | 			// ignore error if ctr is removed or cannot be cleaned up, likely the ctr was already restarted by another process
 | ||||||
| 			if err != nil && !errors.Is(err, define.ErrNoSuchCtr) && !errors.Is(err, define.ErrCtrStateInvalid) { | 			if err != nil && !errors.Is(err, define.ErrNoSuchCtr) && !errors.Is(err, define.ErrCtrStateInvalid) { | ||||||
| 				report.CleanErr = fmt.Errorf("failed to clean up container %v: %w", ctr.ID(), err) | 				report.CleanErr = fmt.Errorf("failed to clean up container %v: %w", ctr.ID(), err) | ||||||
|  |  | ||||||
|  | @ -311,7 +311,9 @@ func CreateExitCommandArgs(storageConfig storageTypes.StoreOptions, config *conf | ||||||
| 		command = append(command, "--module", module) | 		command = append(command, "--module", module) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	command = append(command, []string{"container", "cleanup"}...) | 	// --stopped-only is used to ensure we only cleanup stopped containers and do not race
 | ||||||
|  | 	// against other processes that did a cleanup() + init() again before we had the chance to run
 | ||||||
|  | 	command = append(command, []string{"container", "cleanup", "--stopped-only"}...) | ||||||
| 
 | 
 | ||||||
| 	if rm { | 	if rm { | ||||||
| 		command = append(command, "--rm") | 		command = append(command, "--rm") | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue