Skip to content

Commit 67aae8e

Browse files
Merge pull request #20866 from giuseppe/add-preserve-fds-list
podman: new option --preserve-fd
2 parents 2710eaf + 01d397a commit 67aae8e

File tree

19 files changed

+172
-23
lines changed

19 files changed

+172
-23
lines changed

cmd/podman/containers/exec.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ func execFlags(cmd *cobra.Command) {
8383
flags.UintVar(&execOpts.PreserveFDs, preserveFdsFlagName, 0, "Pass N additional file descriptors to the container")
8484
_ = cmd.RegisterFlagCompletionFunc(preserveFdsFlagName, completion.AutocompleteNone)
8585

86+
preserveFdFlagName := "preserve-fd"
87+
flags.UintSliceVar(&execOpts.PreserveFD, preserveFdFlagName, nil, "Pass a list of additional file descriptors to the container")
88+
_ = cmd.RegisterFlagCompletionFunc(preserveFdFlagName, completion.AutocompleteNone)
89+
8690
workdirFlagName := "workdir"
8791
flags.StringVarP(&execOpts.WorkDir, workdirFlagName, "w", "", "Working directory inside the container")
8892
_ = cmd.RegisterFlagCompletionFunc(workdirFlagName, completion.AutocompleteDefault)
@@ -139,6 +143,12 @@ func exec(cmd *cobra.Command, args []string) error {
139143

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

146+
for _, fd := range execOpts.PreserveFD {
147+
if !rootless.IsFdInherited(int(fd)) {
148+
return fmt.Errorf("file descriptor %d is not available - the preserve-fd option requires that file descriptors must be passed", fd)
149+
}
150+
}
151+
142152
for fd := 3; fd < int(3+execOpts.PreserveFDs); fd++ {
143153
if !rootless.IsFdInherited(fd) {
144154
return fmt.Errorf("file descriptor %d is not available - the preserve-fds option requires that file descriptors must be passed", fd)

cmd/podman/containers/run.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,13 @@ func runFlags(cmd *cobra.Command) {
6767
flags.BoolVar(&runRmi, "rmi", false, "Remove image unless used by other containers, implies --rm")
6868

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

73+
preserveFdFlagName := "preserve-fd"
74+
flags.UintSliceVar(&runOpts.PreserveFD, preserveFdFlagName, nil, "Pass a file descriptor into the container")
75+
_ = cmd.RegisterFlagCompletionFunc(preserveFdFlagName, completion.AutocompleteNone)
76+
7377
flags.BoolVarP(&runOpts.Detach, "detach", "d", false, "Run container in background and print container ID")
7478

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

8791
if registry.IsRemote() {
88-
_ = flags.MarkHidden("preserve-fds")
92+
_ = flags.MarkHidden(preserveFdsFlagName)
93+
_ = flags.MarkHidden(preserveFdFlagName)
8994
_ = flags.MarkHidden("conmon-pidfile")
9095
_ = flags.MarkHidden("pidfile")
9196
}
@@ -135,6 +140,11 @@ func run(cmd *cobra.Command, args []string) error {
135140
return err
136141
}
137142

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

198208
cliVals.PreserveFDs = runOpts.PreserveFDs
209+
cliVals.PreserveFD = runOpts.PreserveFD
199210
s := specgen.NewSpecGenerator(imageName, cliVals.RootFS)
200211
if err := specgenutil.FillOutSpecGen(s, &cliVals, args); err != nil {
201212
return err
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
####> This option file is used in:
2+
####> podman exec, run
3+
####> If file is edited, make sure the changes
4+
####> are applicable to all of those.
5+
#### **--preserve-fd**=*FD1[,FD2,...]*
6+
7+
Pass down to the process the additional file descriptors specified in the comma separated list. It can be specified multiple times.
8+
This option is only supported with the crun OCI runtime. It might be a security risk to use this option with other OCI runtimes.
9+
10+
(This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines)

docs/source/markdown/podman-exec.1.md.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ Start the exec session, but do not attach to it. The command runs in the backgro
2727

2828
@@option latest
2929

30+
@@option preserve-fd
31+
3032
@@option preserve-fds
3133

3234
@@option privileged

docs/source/markdown/podman-run.1.md.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,8 @@ This is used to override the Podman provided user setup in favor of entrypoint c
308308

309309
@@option pod-id-file.container
310310

311+
@@option preserve-fd
312+
311313
@@option preserve-fds
312314

313315
@@option privileged

libpod/container_config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,9 @@ type ContainerMiscConfig struct {
416416
// to 0, 1, 2) that will be passed to the executed process. The total FDs
417417
// passed will be 3 + PreserveFDs.
418418
PreserveFDs uint `json:"preserveFds,omitempty"`
419+
// PreserveFD is a list of additional file descriptors (in addition
420+
// to 0, 1, 2) that will be passed to the executed process.
421+
PreserveFD []uint `json:"preserveFd,omitempty"`
419422
// Timezone is the timezone inside the container.
420423
// Local means it has the same timezone as the host machine
421424
Timezone string `json:"timezone,omitempty"`

libpod/container_exec.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ type ExecConfig struct {
6666
// given is the number that will be passed into the exec session,
6767
// starting at 3.
6868
PreserveFDs uint `json:"preserveFds,omitempty"`
69+
// PreserveFD is a list of additional file descriptors (in addition
70+
// to 0, 1, 2) that will be passed to the executed process.
71+
PreserveFD []uint `json:"preserveFd,omitempty"`
6972
// ExitCommand is the exec session's exit command.
7073
// This command will be executed when the exec session exits.
7174
// If unset, no command will be executed.
@@ -1092,6 +1095,7 @@ func prepareForExec(c *Container, session *ExecSession) (*ExecOptions, error) {
10921095
opts.Cwd = session.Config.WorkDir
10931096
opts.User = session.Config.User
10941097
opts.PreserveFDs = session.Config.PreserveFDs
1098+
opts.PreserveFD = session.Config.PreserveFD
10951099
opts.DetachKeys = session.Config.DetachKeys
10961100
opts.ExitCommand = session.Config.ExitCommand
10971101
opts.ExitCommandDelay = session.Config.ExitCommandDelay

libpod/oci.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ type ExecOptions struct {
202202
// to 0, 1, 2) that will be passed to the executed process. The total FDs
203203
// passed will be 3 + PreserveFDs.
204204
PreserveFDs uint
205+
// PreserveFD is a list of additional file descriptors (in addition
206+
// to 0, 1, 2) that will be passed to the executed process.
207+
PreserveFD []uint
205208
// DetachKeys is a set of keys that, when pressed in sequence, will
206209
// detach from the container.
207210
// If not provided, the default keys will be used.

libpod/oci_conmon_common.go

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,39 @@ func (r *ConmonOCIRuntime) getLogTag(ctr *Container) (string, error) {
10381038
return b.String(), nil
10391039
}
10401040

1041+
func getPreserveFdExtraFiles(preserveFD []uint, preserveFDs uint) (uint, []*os.File, []*os.File, error) {
1042+
var filesToClose []*os.File
1043+
var extraFiles []*os.File
1044+
1045+
preserveFDsMap := make(map[uint]struct{})
1046+
for _, i := range preserveFD {
1047+
if i < 3 {
1048+
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)
1049+
}
1050+
if i-2 > preserveFDs {
1051+
// preserveFDs is the number of FDs above 2 to keep around.
1052+
// e.g. if the user specified FD=3, then preserveFDs must be 1.
1053+
preserveFDs = i - 2
1054+
}
1055+
preserveFDsMap[i] = struct{}{}
1056+
}
1057+
1058+
if preserveFDs > 0 {
1059+
for fd := 3; fd < int(3+preserveFDs); fd++ {
1060+
if len(preserveFDsMap) > 0 {
1061+
if _, ok := preserveFDsMap[uint(fd)]; !ok {
1062+
extraFiles = append(extraFiles, nil)
1063+
continue
1064+
}
1065+
}
1066+
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd))
1067+
filesToClose = append(filesToClose, f)
1068+
extraFiles = append(extraFiles, f)
1069+
}
1070+
}
1071+
return preserveFDs, filesToClose, extraFiles, nil
1072+
}
1073+
10411074
// createOCIContainer generates this container's main conmon instance and prepares it for starting
10421075
func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (int64, error) {
10431076
var stderrBuf bytes.Buffer
@@ -1114,10 +1147,11 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
11141147
args = append(args, []string{"--exit-command-arg", arg}...)
11151148
}
11161149

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

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

1192-
var filesToClose []*os.File
1193-
if preserveFDs > 0 {
1194-
for fd := 3; fd < int(3+preserveFDs); fd++ {
1195-
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd))
1196-
filesToClose = append(filesToClose, f)
1197-
cmd.ExtraFiles = append(cmd.ExtraFiles, f)
1198-
}
1199-
}
1230+
cmd.ExtraFiles = extraFiles
12001231

12011232
cmd.Env = r.conmonEnv
12021233
// we don't want to step on users fds they asked to preserve

libpod/oci_conmon_exec_common.go

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -391,8 +391,13 @@ func (r *ConmonOCIRuntime) startExec(c *Container, sessionID string, options *Ex
391391

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

394-
if options.PreserveFDs > 0 {
395-
args = append(args, formatRuntimeOpts("--preserve-fds", strconv.FormatUint(uint64(options.PreserveFDs), 10))...)
394+
preserveFDs, filesToClose, extraFiles, err := getPreserveFdExtraFiles(options.PreserveFD, options.PreserveFDs)
395+
if err != nil {
396+
return nil, nil, err
397+
}
398+
399+
if preserveFDs > 0 {
400+
args = append(args, formatRuntimeOpts("--preserve-fds", strconv.FormatUint(uint64(preserveFDs), 10))...)
396401
}
397402

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

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

454452
// we don't want to step on users fds they asked to preserve
455453
// Since 0-2 are used for stdio, start the fds we pass in at preserveFDs+3
456454
execCmd.Env = r.conmonEnv
457-
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))
455+
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))
458456
execCmd.Env = append(execCmd.Env, conmonEnv...)
459457

460458
execCmd.ExtraFiles = append(execCmd.ExtraFiles, childSyncPipe, childStartPipe, childAttachPipe)

libpod/options.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,6 +1555,18 @@ func WithPreserveFDs(fd uint) CtrCreateOption {
15551555
}
15561556
}
15571557

1558+
// WithPreserveFD forwards from the process running Libpod into the container
1559+
// the given list of extra FDs to the created container
1560+
func WithPreserveFD(fds []uint) CtrCreateOption {
1561+
return func(ctr *Container) error {
1562+
if ctr.valid {
1563+
return define.ErrCtrFinalized
1564+
}
1565+
ctr.config.PreserveFD = fds
1566+
return nil
1567+
}
1568+
}
1569+
15581570
// WithCreateCommand adds the full command plus arguments of the current
15591571
// process to the container config.
15601572
func WithCreateCommand(cmd []string) CtrCreateOption {

pkg/domain/entities/containers.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ type ExecOptions struct {
298298
Interactive bool
299299
Latest bool
300300
PreserveFDs uint
301+
PreserveFD []uint
301302
Privileged bool
302303
Tty bool
303304
User string
@@ -361,6 +362,7 @@ type ContainerRunOptions struct {
361362
InputStream *os.File
362363
OutputStream *os.File
363364
PreserveFDs uint
365+
PreserveFD []uint
364366
Rm bool
365367
SigProxy bool
366368
Spec *specgen.SpecGenerator

pkg/domain/entities/pods.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ type ContainerCreateOptions struct {
250250
PodIDFile string
251251
Personality string
252252
PreserveFDs uint
253+
PreserveFD []uint
253254
Privileged bool
254255
PublishAll bool
255256
Pull string

pkg/domain/infra/abi/containers.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,7 @@ func makeExecConfig(options entities.ExecOptions, rt *libpod.Runtime) (*libpod.E
822822
execConfig.WorkDir = options.WorkDir
823823
execConfig.DetachKeys = &options.DetachKeys
824824
execConfig.PreserveFDs = options.PreserveFDs
825+
execConfig.PreserveFD = options.PreserveFD
825826
execConfig.AttachStdin = options.Interactive
826827

827828
// Make an exit command
@@ -871,6 +872,7 @@ func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrID string, o
871872
if err != nil {
872873
return ec, err
873874
}
875+
874876
containers, err := getContainers(ic.Libpod, getContainersOptions{latest: options.Latest, names: []string{nameOrID}})
875877
if err != nil {
876878
return ec, err

pkg/specgen/generate/container_create.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,10 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l
355355
options = append(options, libpod.WithPreserveFDs(s.PreserveFDs))
356356
}
357357

358+
if s.PreserveFD != nil {
359+
options = append(options, libpod.WithPreserveFD(s.PreserveFD))
360+
}
361+
358362
if s.Stdin {
359363
options = append(options, libpod.WithStdin())
360364
}

pkg/specgen/specgen.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ type ContainerBasicConfig struct {
180180
// set tags as `json:"-"` for not supported remote
181181
// Optional.
182182
PreserveFDs uint `json:"-"`
183+
// PreserveFD is a list of additional file descriptors (in addition
184+
// to 0, 1, 2) that will be passed to the executed process.
185+
// set tags as `json:"-"` for not supported remote
186+
// Optional.
187+
PreserveFD []uint `json:"-"`
183188
// Timezone is the timezone inside the container.
184189
// Local means it has the same timezone as the host machine
185190
// Optional.

pkg/specgenutil/specgen.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,9 +838,17 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
838838
if len(s.Name) == 0 || len(c.Name) != 0 {
839839
s.Name = c.Name
840840
}
841+
842+
if c.PreserveFDs != 0 && c.PreserveFD != nil {
843+
return errors.New("cannot specify both --preserve-fds and --preserve-fd")
844+
}
845+
841846
if s.PreserveFDs == 0 || c.PreserveFDs != 0 {
842847
s.PreserveFDs = c.PreserveFDs
843848
}
849+
if s.PreserveFD == nil || c.PreserveFD != nil {
850+
s.PreserveFD = c.PreserveFD
851+
}
844852

845853
if s.OOMScoreAdj == nil || c.OOMScoreAdj != nil {
846854
s.OOMScoreAdj = c.OOMScoreAdj

test/system/030-run.bats

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,25 @@ echo $rand | 0 | $rand
8080
is "$output" "$content" "container read input from fd 4"
8181
}
8282

83+
# 'run --preserve-fd' passes a list of additional file descriptors into the container
84+
@test "podman run --preserve-fd" {
85+
skip_if_remote "preserve-fd is meaningless over remote"
86+
87+
runtime=$(podman_runtime)
88+
if [[ $runtime != "crun" ]]; then
89+
skip "runtime is $runtime; preserve-fd requires crun"
90+
fi
91+
92+
content=$(random_string 20)
93+
echo "$content" > $PODMAN_TMPDIR/tempfile
94+
95+
# /proc/self/fd will have 0 1 2, possibly 3 & 4, but no 2-digit fds other than 40
96+
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
97+
assert "${lines[0]}" !~ [123][0-9] "/proc/self/fd must not contain 10-39"
98+
assert "${lines[1]}" = "fd9" "cat from fd 9"
99+
assert "${lines[2]}" = "$content" "cat from fd 40"
100+
}
101+
83102
@test "podman run - uidmapping has no /sys/kernel mounts" {
84103
skip_if_cgroupsv1 "run --uidmap fails on cgroups v1 (issue 15025, wontfix)"
85104
skip_if_rootless "cannot umount as rootless"

0 commit comments

Comments
 (0)