Skip to content

Commit

Permalink
Merge pull request #20866 from giuseppe/add-preserve-fds-list
Browse files Browse the repository at this point in the history
podman: new option --preserve-fd
  • Loading branch information
openshift-merge-bot[bot] authored Dec 6, 2023
2 parents 2710eaf + 01d397a commit 67aae8e
Show file tree
Hide file tree
Showing 19 changed files with 172 additions and 23 deletions.
10 changes: 10 additions & 0 deletions cmd/podman/containers/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ func execFlags(cmd *cobra.Command) {
flags.UintVar(&execOpts.PreserveFDs, preserveFdsFlagName, 0, "Pass N additional file descriptors to the container")
_ = cmd.RegisterFlagCompletionFunc(preserveFdsFlagName, completion.AutocompleteNone)

preserveFdFlagName := "preserve-fd"
flags.UintSliceVar(&execOpts.PreserveFD, preserveFdFlagName, nil, "Pass a list of additional file descriptors to the container")
_ = cmd.RegisterFlagCompletionFunc(preserveFdFlagName, completion.AutocompleteNone)

workdirFlagName := "workdir"
flags.StringVarP(&execOpts.WorkDir, workdirFlagName, "w", "", "Working directory inside the container")
_ = cmd.RegisterFlagCompletionFunc(workdirFlagName, completion.AutocompleteDefault)
Expand Down Expand Up @@ -139,6 +143,12 @@ func exec(cmd *cobra.Command, args []string) error {

execOpts.Envs = envLib.Join(execOpts.Envs, cliEnv)

for _, fd := range execOpts.PreserveFD {
if !rootless.IsFdInherited(int(fd)) {
return fmt.Errorf("file descriptor %d is not available - the preserve-fd option requires that file descriptors must be passed", fd)
}
}

for fd := 3; fd < int(3+execOpts.PreserveFDs); fd++ {
if !rootless.IsFdInherited(fd) {
return fmt.Errorf("file descriptor %d is not available - the preserve-fds option requires that file descriptors must be passed", fd)
Expand Down
15 changes: 13 additions & 2 deletions cmd/podman/containers/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,13 @@ func runFlags(cmd *cobra.Command) {
flags.BoolVar(&runRmi, "rmi", false, "Remove image unless used by other containers, implies --rm")

preserveFdsFlagName := "preserve-fds"
flags.UintVar(&runOpts.PreserveFDs, "preserve-fds", 0, "Pass a number of additional file descriptors into the container")
flags.UintVar(&runOpts.PreserveFDs, preserveFdsFlagName, 0, "Pass a number of additional file descriptors into the container")
_ = cmd.RegisterFlagCompletionFunc(preserveFdsFlagName, completion.AutocompleteNone)

preserveFdFlagName := "preserve-fd"
flags.UintSliceVar(&runOpts.PreserveFD, preserveFdFlagName, nil, "Pass a file descriptor into the container")
_ = cmd.RegisterFlagCompletionFunc(preserveFdFlagName, completion.AutocompleteNone)

flags.BoolVarP(&runOpts.Detach, "detach", "d", false, "Run container in background and print container ID")

detachKeysFlagName := "detach-keys"
Expand All @@ -85,7 +89,8 @@ func runFlags(cmd *cobra.Command) {
flags.BoolVar(&runOpts.Passwd, passwdFlagName, true, "add entries to /etc/passwd and /etc/group")

if registry.IsRemote() {
_ = flags.MarkHidden("preserve-fds")
_ = flags.MarkHidden(preserveFdsFlagName)
_ = flags.MarkHidden(preserveFdFlagName)
_ = flags.MarkHidden("conmon-pidfile")
_ = flags.MarkHidden("pidfile")
}
Expand Down Expand Up @@ -135,6 +140,11 @@ func run(cmd *cobra.Command, args []string) error {
return err
}

for _, fd := range runOpts.PreserveFD {
if !rootless.IsFdInherited(int(fd)) {
return fmt.Errorf("file descriptor %d is not available - the preserve-fd option requires that file descriptors must be passed", fd)
}
}
for fd := 3; fd < int(3+runOpts.PreserveFDs); fd++ {
if !rootless.IsFdInherited(fd) {
return fmt.Errorf("file descriptor %d is not available - the preserve-fds option requires that file descriptors must be passed", fd)
Expand Down Expand Up @@ -196,6 +206,7 @@ func run(cmd *cobra.Command, args []string) error {
}

cliVals.PreserveFDs = runOpts.PreserveFDs
cliVals.PreserveFD = runOpts.PreserveFD
s := specgen.NewSpecGenerator(imageName, cliVals.RootFS)
if err := specgenutil.FillOutSpecGen(s, &cliVals, args); err != nil {
return err
Expand Down
10 changes: 10 additions & 0 deletions docs/source/markdown/options/preserve-fd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
####> This option file is used in:
####> podman exec, run
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--preserve-fd**=*FD1[,FD2,...]*

Pass down to the process the additional file descriptors specified in the comma separated list. It can be specified multiple times.
This option is only supported with the crun OCI runtime. It might be a security risk to use this option with other OCI runtimes.

(This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines)
2 changes: 2 additions & 0 deletions docs/source/markdown/podman-exec.1.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Start the exec session, but do not attach to it. The command runs in the backgro

@@option latest

@@option preserve-fd

@@option preserve-fds

@@option privileged
Expand Down
2 changes: 2 additions & 0 deletions docs/source/markdown/podman-run.1.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,8 @@ This is used to override the Podman provided user setup in favor of entrypoint c

@@option pod-id-file.container

@@option preserve-fd

@@option preserve-fds

@@option privileged
Expand Down
3 changes: 3 additions & 0 deletions libpod/container_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,9 @@ type ContainerMiscConfig struct {
// to 0, 1, 2) that will be passed to the executed process. The total FDs
// passed will be 3 + PreserveFDs.
PreserveFDs uint `json:"preserveFds,omitempty"`
// PreserveFD is a list of additional file descriptors (in addition
// to 0, 1, 2) that will be passed to the executed process.
PreserveFD []uint `json:"preserveFd,omitempty"`
// Timezone is the timezone inside the container.
// Local means it has the same timezone as the host machine
Timezone string `json:"timezone,omitempty"`
Expand Down
4 changes: 4 additions & 0 deletions libpod/container_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ type ExecConfig struct {
// given is the number that will be passed into the exec session,
// starting at 3.
PreserveFDs uint `json:"preserveFds,omitempty"`
// PreserveFD is a list of additional file descriptors (in addition
// to 0, 1, 2) that will be passed to the executed process.
PreserveFD []uint `json:"preserveFd,omitempty"`
// ExitCommand is the exec session's exit command.
// This command will be executed when the exec session exits.
// If unset, no command will be executed.
Expand Down Expand Up @@ -1092,6 +1095,7 @@ func prepareForExec(c *Container, session *ExecSession) (*ExecOptions, error) {
opts.Cwd = session.Config.WorkDir
opts.User = session.Config.User
opts.PreserveFDs = session.Config.PreserveFDs
opts.PreserveFD = session.Config.PreserveFD
opts.DetachKeys = session.Config.DetachKeys
opts.ExitCommand = session.Config.ExitCommand
opts.ExitCommandDelay = session.Config.ExitCommandDelay
Expand Down
3 changes: 3 additions & 0 deletions libpod/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,9 @@ type ExecOptions struct {
// to 0, 1, 2) that will be passed to the executed process. The total FDs
// passed will be 3 + PreserveFDs.
PreserveFDs uint
// PreserveFD is a list of additional file descriptors (in addition
// to 0, 1, 2) that will be passed to the executed process.
PreserveFD []uint
// DetachKeys is a set of keys that, when pressed in sequence, will
// detach from the container.
// If not provided, the default keys will be used.
Expand Down
51 changes: 41 additions & 10 deletions libpod/oci_conmon_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,39 @@ func (r *ConmonOCIRuntime) getLogTag(ctr *Container) (string, error) {
return b.String(), nil
}

func getPreserveFdExtraFiles(preserveFD []uint, preserveFDs uint) (uint, []*os.File, []*os.File, error) {
var filesToClose []*os.File
var extraFiles []*os.File

preserveFDsMap := make(map[uint]struct{})
for _, i := range preserveFD {
if i < 3 {
return 0, nil, nil, fmt.Errorf("cannot preserve FD %d, consider using the passthrough log-driver to pass STDIO streams into the container: %w", i, define.ErrInvalidArg)
}
if i-2 > preserveFDs {
// preserveFDs is the number of FDs above 2 to keep around.
// e.g. if the user specified FD=3, then preserveFDs must be 1.
preserveFDs = i - 2
}
preserveFDsMap[i] = struct{}{}
}

if preserveFDs > 0 {
for fd := 3; fd < int(3+preserveFDs); fd++ {
if len(preserveFDsMap) > 0 {
if _, ok := preserveFDsMap[uint(fd)]; !ok {
extraFiles = append(extraFiles, nil)
continue
}
}
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd))
filesToClose = append(filesToClose, f)
extraFiles = append(extraFiles, f)
}
}
return preserveFDs, filesToClose, extraFiles, nil
}

// createOCIContainer generates this container's main conmon instance and prepares it for starting
func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (int64, error) {
var stderrBuf bytes.Buffer
Expand Down Expand Up @@ -1114,10 +1147,11 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
args = append(args, []string{"--exit-command-arg", arg}...)
}

// Pass down the LISTEN_* environment (see #10443).
preserveFDs := ctr.config.PreserveFDs

// Pass down the LISTEN_* environment (see #10443).
if val := os.Getenv("LISTEN_FDS"); val != "" {
if ctr.config.PreserveFDs > 0 {
if preserveFDs > 0 || len(ctr.config.PreserveFD) > 0 {
logrus.Warnf("Ignoring LISTEN_FDS to preserve custom user-specified FDs")
} else {
fds, err := strconv.Atoi(val)
Expand All @@ -1128,6 +1162,10 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
}
}

preserveFDs, filesToClose, extraFiles, err := getPreserveFdExtraFiles(ctr.config.PreserveFD, preserveFDs)
if err != nil {
return 0, err
}
if preserveFDs > 0 {
args = append(args, formatRuntimeOpts("--preserve-fds", strconv.FormatUint(uint64(preserveFDs), 10))...)
}
Expand Down Expand Up @@ -1189,14 +1227,7 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
return 0, fmt.Errorf("configuring conmon env: %w", err)
}

var filesToClose []*os.File
if preserveFDs > 0 {
for fd := 3; fd < int(3+preserveFDs); fd++ {
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd))
filesToClose = append(filesToClose, f)
cmd.ExtraFiles = append(cmd.ExtraFiles, f)
}
}
cmd.ExtraFiles = extraFiles

cmd.Env = r.conmonEnv
// we don't want to step on users fds they asked to preserve
Expand Down
20 changes: 9 additions & 11 deletions libpod/oci_conmon_exec_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,8 +391,13 @@ func (r *ConmonOCIRuntime) startExec(c *Container, sessionID string, options *Ex

args := r.sharedConmonArgs(c, sessionID, c.execBundlePath(sessionID), c.execPidPath(sessionID), c.execLogPath(sessionID), c.execExitFileDir(sessionID), ociLog, define.NoLogging, c.config.LogTag)

if options.PreserveFDs > 0 {
args = append(args, formatRuntimeOpts("--preserve-fds", strconv.FormatUint(uint64(options.PreserveFDs), 10))...)
preserveFDs, filesToClose, extraFiles, err := getPreserveFdExtraFiles(options.PreserveFD, options.PreserveFDs)
if err != nil {
return nil, nil, err
}

if preserveFDs > 0 {
args = append(args, formatRuntimeOpts("--preserve-fds", strconv.FormatUint(uint64(preserveFDs), 10))...)
}

if options.Terminal {
Expand Down Expand Up @@ -442,19 +447,12 @@ func (r *ConmonOCIRuntime) startExec(c *Container, sessionID string, options *Ex
return nil, nil, fmt.Errorf("configuring conmon env: %w", err)
}

var filesToClose []*os.File
if options.PreserveFDs > 0 {
for fd := 3; fd < int(3+options.PreserveFDs); fd++ {
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd))
filesToClose = append(filesToClose, f)
execCmd.ExtraFiles = append(execCmd.ExtraFiles, f)
}
}
execCmd.ExtraFiles = extraFiles

// we don't want to step on users fds they asked to preserve
// Since 0-2 are used for stdio, start the fds we pass in at preserveFDs+3
execCmd.Env = r.conmonEnv
execCmd.Env = append(execCmd.Env, fmt.Sprintf("_OCI_SYNCPIPE=%d", options.PreserveFDs+3), fmt.Sprintf("_OCI_STARTPIPE=%d", options.PreserveFDs+4), fmt.Sprintf("_OCI_ATTACHPIPE=%d", options.PreserveFDs+5))
execCmd.Env = append(execCmd.Env, fmt.Sprintf("_OCI_SYNCPIPE=%d", preserveFDs+3), fmt.Sprintf("_OCI_STARTPIPE=%d", preserveFDs+4), fmt.Sprintf("_OCI_ATTACHPIPE=%d", preserveFDs+5))
execCmd.Env = append(execCmd.Env, conmonEnv...)

execCmd.ExtraFiles = append(execCmd.ExtraFiles, childSyncPipe, childStartPipe, childAttachPipe)
Expand Down
12 changes: 12 additions & 0 deletions libpod/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -1555,6 +1555,18 @@ func WithPreserveFDs(fd uint) CtrCreateOption {
}
}

// WithPreserveFD forwards from the process running Libpod into the container
// the given list of extra FDs to the created container
func WithPreserveFD(fds []uint) CtrCreateOption {
return func(ctr *Container) error {
if ctr.valid {
return define.ErrCtrFinalized
}
ctr.config.PreserveFD = fds
return nil
}
}

// WithCreateCommand adds the full command plus arguments of the current
// process to the container config.
func WithCreateCommand(cmd []string) CtrCreateOption {
Expand Down
2 changes: 2 additions & 0 deletions pkg/domain/entities/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ type ExecOptions struct {
Interactive bool
Latest bool
PreserveFDs uint
PreserveFD []uint
Privileged bool
Tty bool
User string
Expand Down Expand Up @@ -361,6 +362,7 @@ type ContainerRunOptions struct {
InputStream *os.File
OutputStream *os.File
PreserveFDs uint
PreserveFD []uint
Rm bool
SigProxy bool
Spec *specgen.SpecGenerator
Expand Down
1 change: 1 addition & 0 deletions pkg/domain/entities/pods.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ type ContainerCreateOptions struct {
PodIDFile string
Personality string
PreserveFDs uint
PreserveFD []uint
Privileged bool
PublishAll bool
Pull string
Expand Down
2 changes: 2 additions & 0 deletions pkg/domain/infra/abi/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,7 @@ func makeExecConfig(options entities.ExecOptions, rt *libpod.Runtime) (*libpod.E
execConfig.WorkDir = options.WorkDir
execConfig.DetachKeys = &options.DetachKeys
execConfig.PreserveFDs = options.PreserveFDs
execConfig.PreserveFD = options.PreserveFD
execConfig.AttachStdin = options.Interactive

// Make an exit command
Expand Down Expand Up @@ -871,6 +872,7 @@ func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrID string, o
if err != nil {
return ec, err
}

containers, err := getContainers(ic.Libpod, getContainersOptions{latest: options.Latest, names: []string{nameOrID}})
if err != nil {
return ec, err
Expand Down
4 changes: 4 additions & 0 deletions pkg/specgen/generate/container_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,10 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l
options = append(options, libpod.WithPreserveFDs(s.PreserveFDs))
}

if s.PreserveFD != nil {
options = append(options, libpod.WithPreserveFD(s.PreserveFD))
}

if s.Stdin {
options = append(options, libpod.WithStdin())
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/specgen/specgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ type ContainerBasicConfig struct {
// set tags as `json:"-"` for not supported remote
// Optional.
PreserveFDs uint `json:"-"`
// PreserveFD is a list of additional file descriptors (in addition
// to 0, 1, 2) that will be passed to the executed process.
// set tags as `json:"-"` for not supported remote
// Optional.
PreserveFD []uint `json:"-"`
// Timezone is the timezone inside the container.
// Local means it has the same timezone as the host machine
// Optional.
Expand Down
8 changes: 8 additions & 0 deletions pkg/specgenutil/specgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -838,9 +838,17 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
if len(s.Name) == 0 || len(c.Name) != 0 {
s.Name = c.Name
}

if c.PreserveFDs != 0 && c.PreserveFD != nil {
return errors.New("cannot specify both --preserve-fds and --preserve-fd")
}

if s.PreserveFDs == 0 || c.PreserveFDs != 0 {
s.PreserveFDs = c.PreserveFDs
}
if s.PreserveFD == nil || c.PreserveFD != nil {
s.PreserveFD = c.PreserveFD
}

if s.OOMScoreAdj == nil || c.OOMScoreAdj != nil {
s.OOMScoreAdj = c.OOMScoreAdj
Expand Down
19 changes: 19 additions & 0 deletions test/system/030-run.bats
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,25 @@ echo $rand | 0 | $rand
is "$output" "$content" "container read input from fd 4"
}

# 'run --preserve-fd' passes a list of additional file descriptors into the container
@test "podman run --preserve-fd" {
skip_if_remote "preserve-fd is meaningless over remote"

runtime=$(podman_runtime)
if [[ $runtime != "crun" ]]; then
skip "runtime is $runtime; preserve-fd requires crun"
fi

content=$(random_string 20)
echo "$content" > $PODMAN_TMPDIR/tempfile

# /proc/self/fd will have 0 1 2, possibly 3 & 4, but no 2-digit fds other than 40
run_podman run --rm -i --preserve-fd=9,40 $IMAGE sh -c '/bin/ls -C -w999 /proc/self/fd; cat <&9; cat <&40' 9<<<"fd9" 10</dev/null 40<$PODMAN_TMPDIR/tempfile
assert "${lines[0]}" !~ [123][0-9] "/proc/self/fd must not contain 10-39"
assert "${lines[1]}" = "fd9" "cat from fd 9"
assert "${lines[2]}" = "$content" "cat from fd 40"
}

@test "podman run - uidmapping has no /sys/kernel mounts" {
skip_if_cgroupsv1 "run --uidmap fails on cgroups v1 (issue 15025, wontfix)"
skip_if_rootless "cannot umount as rootless"
Expand Down
Loading

0 comments on commit 67aae8e

Please sign in to comment.