Files
podman/pkg/api/handlers/compat/containers_attach.go
Paul Holzinger bf74797c69 fix races in the HTTP attach API
This is very similar to commit 3280da0500, we cannot check the state
then unlock to then lock again and do the action. Everything must
happen under one lock. To fix this move the code into the HTTPAttach
function in libpod. The locking here is a bit weird because attach
blocks for the lifetime of attach which can be very long so we must
unlock before performing the attach.

Fixes #23757

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
2024-08-27 15:00:08 +02:00

106 lines
2.9 KiB
Go

//go:build !remote
package compat
import (
"errors"
"net/http"
"github.com/containers/podman/v5/libpod"
"github.com/containers/podman/v5/pkg/api/handlers/utils"
"github.com/containers/podman/v5/pkg/api/server/idle"
api "github.com/containers/podman/v5/pkg/api/types"
"github.com/sirupsen/logrus"
)
func AttachContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := utils.GetDecoder(r)
query := struct {
DetachKeys string `schema:"detachKeys"`
Logs bool `schema:"logs"`
Stream bool `schema:"stream"`
Stdin bool `schema:"stdin"`
Stdout bool `schema:"stdout"`
Stderr bool `schema:"stderr"`
}{
Stream: true,
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusBadRequest, err)
return
}
// Detach keys: explicitly set to "" is very different from unset
// TODO: Our format for parsing these may be different from Docker.
var detachKeys *string
if _, found := r.URL.Query()["detachKeys"]; found {
detachKeys = &query.DetachKeys
}
streams := new(libpod.HTTPAttachStreams)
streams.Stdout = true
streams.Stderr = true
streams.Stdin = true
useStreams := false
if _, found := r.URL.Query()["stdin"]; found {
streams.Stdin = query.Stdin
useStreams = true
}
if _, found := r.URL.Query()["stdout"]; found {
streams.Stdout = query.Stdout
useStreams = true
}
if _, found := r.URL.Query()["stderr"]; found {
streams.Stderr = query.Stderr
useStreams = true
}
if !useStreams {
streams = nil
}
if useStreams && !streams.Stdout && !streams.Stderr && !streams.Stdin {
utils.Error(w, http.StatusBadRequest, errors.New("at least one of stdin, stdout, stderr must be true"))
return
}
// At least one of these must be set
if !query.Stream && !query.Logs {
utils.Error(w, http.StatusBadRequest, errors.New("at least one of Logs or Stream must be set"))
return
}
name := utils.GetName(r)
ctr, err := runtime.LookupContainer(name)
if err != nil {
utils.ContainerNotFound(w, name, err)
return
}
logErr := func(e error) {
logrus.Errorf("Error attaching to container %s: %v", ctr.ID(), e)
}
// Perform HTTP attach.
// HTTPAttach will handle everything about the connection from here on
// (including closing it and writing errors to it).
hijackChan := make(chan bool, 1)
err = ctr.HTTPAttach(r, w, streams, detachKeys, nil, query.Stream, query.Logs, hijackChan)
if <-hijackChan {
// If connection was Hijacked, we have to signal it's being closed
t := r.Context().Value(api.IdleTrackerKey).(*idle.Tracker)
defer t.Close()
if err != nil {
// Cannot report error to client as a 500 as the Upgrade set status to 101
logErr(err)
}
} else {
// If the Hijack failed we are going to assume we can still inform client of failure
utils.InternalServerError(w, err)
logErr(err)
}
logrus.Debugf("Attach for container %s completed successfully", ctr.ID())
}