Skip to content

Commit d22dedf

Browse files
nlacassegvisor-bot
authored andcommitted
Check all 3 stdio FDs to determine if terminal is connected to a pty.
Previously we were only looking at stdin, which could be a pty but other stdio fds might be redirected. In that case, we can incorrectly end up using the stdin fd as *the* console fd, and sending all stdout/stderr to that FD, ignoring the redirect. Note that the behavior was actually flaky because the mechanism for choosing which stdio fd to treat as *the* pty fd is non-deterministic (due to the map iteration in fdimport/fdimport.go:Import) and so sometimes we would choose the correct one. This CL also cleans up `argsFromProcess` and `argsFromCLI`, which were setting their `FilePayload` unnecessarily, since it is always set in `Execute`. Fixes #11350 Fixes #11349 PiperOrigin-RevId: 716733446
1 parent 25b1d71 commit d22dedf

File tree

5 files changed

+33
-50
lines changed

5 files changed

+33
-50
lines changed

pkg/sentry/fdimport/fdimport.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ package fdimport
1717

1818
import (
1919
"fmt"
20+
"maps"
21+
"slices"
2022

2123
"golang.org/x/sys/unix"
2224
"gvisor.dev/gvisor/pkg/context"
@@ -50,8 +52,11 @@ func Import(ctx context.Context, fdTable *kernel.FDTable, console bool, uid auth
5052
}
5153
}
5254

55+
// We iterate through the FDs in sorted order for better determinancy
56+
// in startup, but it shouldn't matter.
5357
var ttyFile *vfs.FileDescription
54-
for appFD, hostFD := range fds {
58+
for _, appFD := range slices.Sorted(maps.Keys(fds)) {
59+
hostFD := fds[appFD]
5560
fdOpts := host.NewFDOptions{
5661
Savable: true,
5762
}

runsc/cmd/do.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ func (c *Do) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcommand
177177
Args: f.Args(),
178178
Env: os.Environ(),
179179
Capabilities: specutils.AllCapabilities(),
180-
Terminal: console.IsPty(os.Stdin.Fd()),
180+
Terminal: console.StdioIsPty(),
181181
},
182182
Hostname: hostname,
183183
}

runsc/cmd/exec.go

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -375,12 +375,7 @@ func (ex *Exec) argsFromCLI(p *specs.Process, argv []string, enableRaw bool) (*c
375375
KGID: kgid,
376376
ExtraKGIDs: extraKGIDs,
377377
Capabilities: caps,
378-
StdioIsPty: ex.consoleSocket != "" || console.IsPty(os.Stdin.Fd()),
379-
FilePayload: control.NewFilePayload(map[int]*os.File{
380-
0: os.Stdin,
381-
1: os.Stdout,
382-
2: os.Stderr,
383-
}, nil),
378+
StdioIsPty: ex.consoleSocket != "" || console.StdioIsPty(),
384379
}, nil
385380
}
386381

@@ -447,11 +442,6 @@ func argsFromProcess(specProc *specs.Process, p *specs.Process, enableRaw bool)
447442
ExtraKGIDs: extraKGIDs,
448443
Capabilities: caps,
449444
StdioIsPty: p.Terminal,
450-
FilePayload: control.NewFilePayload(map[int]*os.File{
451-
0: os.Stdin,
452-
1: os.Stdout,
453-
2: os.Stderr,
454-
}, nil),
455445
}, nil
456446
}
457447

runsc/cmd/exec_test.go

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,9 @@ func TestCLIArgs(t *testing.T) {
7979
Argv: []string{"ls", "/"},
8080
Envv: []string{"FOO=bar"},
8181
WorkingDirectory: "/foo/bar",
82-
FilePayload: control.NewFilePayload(map[int]*os.File{
83-
0: os.Stdin,
84-
1: os.Stdout,
85-
2: os.Stderr,
86-
}, nil),
87-
KUID: 2,
88-
KGID: 2,
89-
ExtraKGIDs: []auth.KGID{1, 2, 3},
82+
KUID: 2,
83+
KGID: 2,
84+
ExtraKGIDs: []auth.KGID{1, 2, 3},
9085
Capabilities: &auth.TaskCapabilities{
9186
BoundingCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
9287
InheritableCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
@@ -113,14 +108,9 @@ func TestCLIArgs(t *testing.T) {
113108
Argv: []string{"ls", "/"},
114109
Envv: []string{"FOO=bar", "BAZ=new"},
115110
WorkingDirectory: "/baz",
116-
FilePayload: control.NewFilePayload(map[int]*os.File{
117-
0: os.Stdin,
118-
1: os.Stdout,
119-
2: os.Stderr,
120-
}, nil),
121-
KUID: 4,
122-
KGID: 4,
123-
ExtraKGIDs: []auth.KGID{1, 2, 3, 4, 5, 6},
111+
KUID: 4,
112+
KGID: 4,
113+
ExtraKGIDs: []auth.KGID{1, 2, 3, 4, 5, 6},
124114
Capabilities: &auth.TaskCapabilities{
125115
BoundingCaps: auth.CapabilitySetOfMany([]linux.Capability{linux.CAP_DAC_OVERRIDE, linux.CAP_DAC_READ_SEARCH}),
126116
EffectiveCaps: auth.CapabilitySetOfMany([]linux.Capability{linux.CAP_DAC_READ_SEARCH}),
@@ -182,14 +172,9 @@ func TestJSONArgs(t *testing.T) {
182172
expected: control.ExecArgs{
183173
Argv: []string{"ls", "/"},
184174
WorkingDirectory: "/foo/bar",
185-
FilePayload: control.NewFilePayload(map[int]*os.File{
186-
0: os.Stdin,
187-
1: os.Stdout,
188-
2: os.Stderr,
189-
}, nil),
190-
KUID: 0,
191-
KGID: 0,
192-
ExtraKGIDs: []auth.KGID{1, 2, 3},
175+
KUID: 0,
176+
KGID: 0,
177+
ExtraKGIDs: []auth.KGID{1, 2, 3},
193178
Capabilities: &auth.TaskCapabilities{
194179
BoundingCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
195180
EffectiveCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
@@ -214,14 +199,9 @@ func TestJSONArgs(t *testing.T) {
214199
expected: control.ExecArgs{
215200
Argv: []string{"ls", "/"},
216201
WorkingDirectory: "/foo/bar",
217-
FilePayload: control.NewFilePayload(map[int]*os.File{
218-
0: os.Stdin,
219-
1: os.Stdout,
220-
2: os.Stderr,
221-
}, nil),
222-
KUID: 0,
223-
KGID: 0,
224-
ExtraKGIDs: []auth.KGID{},
202+
KUID: 0,
203+
KGID: 0,
204+
ExtraKGIDs: []auth.KGID{},
225205
Capabilities: &auth.TaskCapabilities{
226206
BoundingCaps: auth.CapabilitySetOf(linux.CAP_DAC_READ_SEARCH),
227207
},

runsc/console/pty_linux.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,18 @@
1717

1818
package console
1919

20-
import "golang.org/x/sys/unix"
20+
import (
21+
"os"
2122

22-
// IsPty returns true if FD is a PTY.
23-
func IsPty(fd uintptr) bool {
24-
_, err := unix.IoctlGetTermios(int(fd), unix.TCGETS)
25-
return err == nil
23+
"golang.org/x/sys/unix"
24+
)
25+
26+
// StdioIsPty returns true if all stdio FDs are ptys.
27+
func StdioIsPty() bool {
28+
for _, f := range []*os.File{os.Stdin, os.Stdout, os.Stderr} {
29+
if _, err := unix.IoctlGetTermios(int(f.Fd()), unix.TCGETS); err != nil {
30+
return false
31+
}
32+
}
33+
return true
2634
}

0 commit comments

Comments
 (0)