diff options
-rw-r--r-- | pkg/sentry/fs/tty/BUILD | 1 | ||||
-rw-r--r-- | pkg/sentry/fs/tty/dir.go | 3 | ||||
-rw-r--r-- | pkg/sentry/fs/tty/master.go | 17 | ||||
-rw-r--r-- | pkg/sentry/fs/tty/slave.go | 13 | ||||
-rw-r--r-- | pkg/sentry/fs/tty/terminal.go | 92 | ||||
-rw-r--r-- | pkg/sentry/kernel/BUILD | 1 | ||||
-rw-r--r-- | pkg/sentry/kernel/sessions.go | 12 | ||||
-rw-r--r-- | pkg/sentry/kernel/task_start.go | 3 | ||||
-rw-r--r-- | pkg/sentry/kernel/thread_group.go | 179 | ||||
-rw-r--r-- | pkg/sentry/kernel/tty.go | 28 | ||||
-rw-r--r-- | test/syscalls/BUILD | 4 | ||||
-rw-r--r-- | test/syscalls/linux/BUILD | 19 | ||||
-rw-r--r-- | test/syscalls/linux/pty.cc | 351 | ||||
-rw-r--r-- | test/syscalls/linux/pty_root.cc | 68 | ||||
-rw-r--r-- | test/util/BUILD | 11 | ||||
-rw-r--r-- | test/util/pty_util.cc | 45 | ||||
-rw-r--r-- | test/util/pty_util.h | 30 |
17 files changed, 34 insertions, 843 deletions
diff --git a/pkg/sentry/fs/tty/BUILD b/pkg/sentry/fs/tty/BUILD index 291164986..5e9327aec 100644 --- a/pkg/sentry/fs/tty/BUILD +++ b/pkg/sentry/fs/tty/BUILD @@ -23,7 +23,6 @@ go_library( "//pkg/sentry/device", "//pkg/sentry/fs", "//pkg/sentry/fs/fsutil", - "//pkg/sentry/kernel", "//pkg/sentry/kernel/auth", "//pkg/sentry/safemem", "//pkg/sentry/socket/unix/transport", diff --git a/pkg/sentry/fs/tty/dir.go b/pkg/sentry/fs/tty/dir.go index 2f639c823..1d128532b 100644 --- a/pkg/sentry/fs/tty/dir.go +++ b/pkg/sentry/fs/tty/dir.go @@ -129,9 +129,6 @@ func newDir(ctx context.Context, m *fs.MountSource) *fs.Inode { // Release implements fs.InodeOperations.Release. func (d *dirInodeOperations) Release(ctx context.Context) { - d.mu.Lock() - defer d.mu.Unlock() - d.master.DecRef() if len(d.slaves) != 0 { panic(fmt.Sprintf("devpts directory still contains active terminals: %+v", d)) diff --git a/pkg/sentry/fs/tty/master.go b/pkg/sentry/fs/tty/master.go index 19b7557d5..92ec1ca18 100644 --- a/pkg/sentry/fs/tty/master.go +++ b/pkg/sentry/fs/tty/master.go @@ -172,19 +172,6 @@ func (mf *masterFileOperations) Ioctl(ctx context.Context, _ *fs.File, io userme return 0, mf.t.ld.windowSize(ctx, io, args) case linux.TIOCSWINSZ: return 0, mf.t.ld.setWindowSize(ctx, io, args) - case linux.TIOCSCTTY: - // Make the given terminal the controlling terminal of the - // calling process. - return 0, mf.t.setControllingTTY(ctx, io, args, true /* isMaster */) - case linux.TIOCNOTTY: - // Release this process's controlling terminal. - return 0, mf.t.releaseControllingTTY(ctx, io, args, true /* isMaster */) - case linux.TIOCGPGRP: - // Get the foreground process group. - return mf.t.foregroundProcessGroup(ctx, io, args, true /* isMaster */) - case linux.TIOCSPGRP: - // Set the foreground process group. - return mf.t.setForegroundProcessGroup(ctx, io, args, true /* isMaster */) default: maybeEmitUnimplementedEvent(ctx, cmd) return 0, syserror.ENOTTY @@ -198,6 +185,8 @@ func maybeEmitUnimplementedEvent(ctx context.Context, cmd uint32) { linux.TCSETS, linux.TCSETSW, linux.TCSETSF, + linux.TIOCGPGRP, + linux.TIOCSPGRP, linux.TIOCGWINSZ, linux.TIOCSWINSZ, linux.TIOCSETD, @@ -211,6 +200,8 @@ func maybeEmitUnimplementedEvent(ctx context.Context, cmd uint32) { linux.TIOCEXCL, linux.TIOCNXCL, linux.TIOCGEXCL, + linux.TIOCNOTTY, + linux.TIOCSCTTY, linux.TIOCGSID, linux.TIOCGETD, linux.TIOCVHANGUP, diff --git a/pkg/sentry/fs/tty/slave.go b/pkg/sentry/fs/tty/slave.go index 944c4ada1..e30266404 100644 --- a/pkg/sentry/fs/tty/slave.go +++ b/pkg/sentry/fs/tty/slave.go @@ -152,16 +152,9 @@ func (sf *slaveFileOperations) Ioctl(ctx context.Context, _ *fs.File, io usermem case linux.TIOCSCTTY: // Make the given terminal the controlling terminal of the // calling process. - return 0, sf.si.t.setControllingTTY(ctx, io, args, false /* isMaster */) - case linux.TIOCNOTTY: - // Release this process's controlling terminal. - return 0, sf.si.t.releaseControllingTTY(ctx, io, args, false /* isMaster */) - case linux.TIOCGPGRP: - // Get the foreground process group. - return sf.si.t.foregroundProcessGroup(ctx, io, args, false /* isMaster */) - case linux.TIOCSPGRP: - // Set the foreground process group. - return sf.si.t.setForegroundProcessGroup(ctx, io, args, false /* isMaster */) + // TODO(b/129283598): Implement once we have support for job + // control. + return 0, nil default: maybeEmitUnimplementedEvent(ctx, cmd) return 0, syserror.ENOTTY diff --git a/pkg/sentry/fs/tty/terminal.go b/pkg/sentry/fs/tty/terminal.go index ff8138820..b7cecb2ed 100644 --- a/pkg/sentry/fs/tty/terminal.go +++ b/pkg/sentry/fs/tty/terminal.go @@ -17,10 +17,7 @@ package tty import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/refs" - "gvisor.dev/gvisor/pkg/sentry/arch" "gvisor.dev/gvisor/pkg/sentry/context" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/usermem" ) // Terminal is a pseudoterminal. @@ -29,100 +26,23 @@ import ( type Terminal struct { refs.AtomicRefCount - // n is the terminal index. It is immutable. + // n is the terminal index. n uint32 - // d is the containing directory. It is immutable. + // d is the containing directory. d *dirInodeOperations - // ld is the line discipline of the terminal. It is immutable. + // ld is the line discipline of the terminal. ld *lineDiscipline - - // masterKTTY contains the controlling process of the master end of - // this terminal. This field is immutable. - masterKTTY *kernel.TTY - - // slaveKTTY contains the controlling process of the slave end of this - // terminal. This field is immutable. - slaveKTTY *kernel.TTY } func newTerminal(ctx context.Context, d *dirInodeOperations, n uint32) *Terminal { termios := linux.DefaultSlaveTermios t := Terminal{ - d: d, - n: n, - ld: newLineDiscipline(termios), - masterKTTY: &kernel.TTY{}, - slaveKTTY: &kernel.TTY{}, + d: d, + n: n, + ld: newLineDiscipline(termios), } t.EnableLeakCheck("tty.Terminal") return &t } - -// setControllingTTY makes tm the controlling terminal of the calling thread -// group. -func (tm *Terminal) setControllingTTY(ctx context.Context, io usermem.IO, args arch.SyscallArguments, isMaster bool) error { - task := kernel.TaskFromContext(ctx) - if task == nil { - panic("setControllingTTY must be called from a task context") - } - - return task.ThreadGroup().SetControllingTTY(tm.tty(isMaster), args[2].Int()) -} - -// releaseControllingTTY removes tm as the controlling terminal of the calling -// thread group. -func (tm *Terminal) releaseControllingTTY(ctx context.Context, io usermem.IO, args arch.SyscallArguments, isMaster bool) error { - task := kernel.TaskFromContext(ctx) - if task == nil { - panic("releaseControllingTTY must be called from a task context") - } - - return task.ThreadGroup().ReleaseControllingTTY(tm.tty(isMaster)) -} - -// foregroundProcessGroup gets the process group ID of tm's foreground process. -func (tm *Terminal) foregroundProcessGroup(ctx context.Context, io usermem.IO, args arch.SyscallArguments, isMaster bool) (uintptr, error) { - task := kernel.TaskFromContext(ctx) - if task == nil { - panic("foregroundProcessGroup must be called from a task context") - } - - ret, err := task.ThreadGroup().ForegroundProcessGroup(tm.tty(isMaster)) - if err != nil { - return 0, err - } - - // Write it out to *arg. - _, err = usermem.CopyObjectOut(ctx, io, args[2].Pointer(), int32(ret), usermem.IOOpts{ - AddressSpaceActive: true, - }) - return 0, err -} - -// foregroundProcessGroup sets tm's foreground process. -func (tm *Terminal) setForegroundProcessGroup(ctx context.Context, io usermem.IO, args arch.SyscallArguments, isMaster bool) (uintptr, error) { - task := kernel.TaskFromContext(ctx) - if task == nil { - panic("setForegroundProcessGroup must be called from a task context") - } - - // Read in the process group ID. - var pgid int32 - if _, err := usermem.CopyObjectIn(ctx, io, args[2].Pointer(), &pgid, usermem.IOOpts{ - AddressSpaceActive: true, - }); err != nil { - return 0, err - } - - ret, err := task.ThreadGroup().SetForegroundProcessGroup(tm.tty(isMaster), kernel.ProcessGroupID(pgid)) - return uintptr(ret), err -} - -func (tm *Terminal) tty(isMaster bool) *kernel.TTY { - if isMaster { - return tm.masterKTTY - } - return tm.slaveKTTY -} diff --git a/pkg/sentry/kernel/BUILD b/pkg/sentry/kernel/BUILD index 41bee9a22..e61d39c82 100644 --- a/pkg/sentry/kernel/BUILD +++ b/pkg/sentry/kernel/BUILD @@ -144,7 +144,6 @@ go_library( "threads.go", "timekeeper.go", "timekeeper_state.go", - "tty.go", "uts_namespace.go", "vdso.go", "version.go", diff --git a/pkg/sentry/kernel/sessions.go b/pkg/sentry/kernel/sessions.go index e5f297478..81fcd8258 100644 --- a/pkg/sentry/kernel/sessions.go +++ b/pkg/sentry/kernel/sessions.go @@ -47,11 +47,6 @@ type Session struct { // The id is immutable. id SessionID - // foreground is the foreground process group. - // - // This is protected by TaskSet.mu. - foreground *ProcessGroup - // ProcessGroups is a list of process groups in this Session. This is // protected by TaskSet.mu. processGroups processGroupList @@ -265,14 +260,12 @@ func (pg *ProcessGroup) SendSignal(info *arch.SignalInfo) error { func (tg *ThreadGroup) CreateSession() error { tg.pidns.owner.mu.Lock() defer tg.pidns.owner.mu.Unlock() - tg.signalHandlers.mu.Lock() - defer tg.signalHandlers.mu.Unlock() return tg.createSession() } // createSession creates a new session for a threadgroup. // -// Precondition: callers must hold TaskSet.mu and the signal mutex for writing. +// Precondition: callers must hold TaskSet.mu for writing. func (tg *ThreadGroup) createSession() error { // Get the ID for this thread in the current namespace. id := tg.pidns.tgids[tg] @@ -353,9 +346,6 @@ func (tg *ThreadGroup) createSession() error { ns.processGroups[ProcessGroupID(local)] = pg } - // Disconnect from the controlling terminal. - tg.tty = nil - return nil } diff --git a/pkg/sentry/kernel/task_start.go b/pkg/sentry/kernel/task_start.go index ae6fc4025..d60cd62c7 100644 --- a/pkg/sentry/kernel/task_start.go +++ b/pkg/sentry/kernel/task_start.go @@ -172,10 +172,9 @@ func (ts *TaskSet) newTask(cfg *TaskConfig) (*Task, error) { if parentPG := tg.parentPG(); parentPG == nil { tg.createSession() } else { - // Inherit the process group and terminal. + // Inherit the process group. parentPG.incRefWithParent(parentPG) tg.processGroup = parentPG - tg.tty = t.parent.tg.tty } } tg.tasks.PushBack(t) diff --git a/pkg/sentry/kernel/thread_group.go b/pkg/sentry/kernel/thread_group.go index 0eef24bfb..2a97e3e8e 100644 --- a/pkg/sentry/kernel/thread_group.go +++ b/pkg/sentry/kernel/thread_group.go @@ -19,13 +19,10 @@ import ( "sync/atomic" "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/sentry/arch" "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" "gvisor.dev/gvisor/pkg/sentry/limits" "gvisor.dev/gvisor/pkg/sentry/usage" - "gvisor.dev/gvisor/pkg/syserror" ) // A ThreadGroup is a logical grouping of tasks that has widespread @@ -248,12 +245,6 @@ type ThreadGroup struct { // // mounts is immutable. mounts *fs.MountNamespace - - // tty is the thread group's controlling terminal. If nil, there is no - // controlling terminal. - // - // tty is protected by the signal mutex. - tty *TTY } // newThreadGroup returns a new, empty thread group in PID namespace ns. The @@ -333,176 +324,6 @@ func (tg *ThreadGroup) forEachChildThreadGroupLocked(fn func(*ThreadGroup)) { } } -// SetControllingTTY sets tty as the controlling terminal of tg. -func (tg *ThreadGroup) SetControllingTTY(tty *TTY, arg int32) error { - tty.mu.Lock() - defer tty.mu.Unlock() - - // We might be asked to set the controlling terminal of multiple - // processes, so we lock both the TaskSet and SignalHandlers. - tg.pidns.owner.mu.Lock() - defer tg.pidns.owner.mu.Unlock() - tg.signalHandlers.mu.Lock() - defer tg.signalHandlers.mu.Unlock() - - // "The calling process must be a session leader and not have a - // controlling terminal already." - tty_ioctl(4) - if tg.processGroup.session.leader != tg || tg.tty != nil { - return syserror.EINVAL - } - - // "If this terminal is already the controlling terminal of a different - // session group, then the ioctl fails with EPERM, unless the caller - // has the CAP_SYS_ADMIN capability and arg equals 1, in which case the - // terminal is stolen, and all processes that had it as controlling - // terminal lose it." - tty_ioctl(4) - if tty.tg != nil && tg.processGroup.session != tty.tg.processGroup.session { - if !auth.CredentialsFromContext(tg.leader).HasCapability(linux.CAP_SYS_ADMIN) || arg != 1 { - return syserror.EPERM - } - // Steal the TTY away. Unlike TIOCNOTTY, don't send signals. - for othertg := range tg.pidns.owner.Root.tgids { - // This won't deadlock by locking tg.signalHandlers - // because at this point: - // - We only lock signalHandlers if it's in the same - // session as the tty's controlling thread group. - // - We know that the calling thread group is not in - // the same session as the tty's controlling thread - // group. - if othertg.processGroup.session == tty.tg.processGroup.session { - othertg.signalHandlers.mu.Lock() - othertg.tty = nil - othertg.signalHandlers.mu.Unlock() - } - } - } - - // Set the controlling terminal and foreground process group. - tg.tty = tty - tg.processGroup.session.foreground = tg.processGroup - // Set this as the controlling process of the terminal. - tty.tg = tg - - return nil -} - -// ReleaseControllingTTY gives up tty as the controlling tty of tg. -func (tg *ThreadGroup) ReleaseControllingTTY(tty *TTY) error { - tty.mu.Lock() - defer tty.mu.Unlock() - - // We might be asked to set the controlling terminal of multiple - // processes, so we lock both the TaskSet and SignalHandlers. - tg.pidns.owner.mu.RLock() - defer tg.pidns.owner.mu.RUnlock() - - // Just below, we may re-lock signalHandlers in order to send signals. - // Thus we can't defer Unlock here. - tg.signalHandlers.mu.Lock() - - if tg.tty == nil || tg.tty != tty { - tg.signalHandlers.mu.Unlock() - return syserror.ENOTTY - } - - // "If the process was session leader, then send SIGHUP and SIGCONT to - // the foreground process group and all processes in the current - // session lose their controlling terminal." - tty_ioctl(4) - // Remove tty as the controlling tty for each process in the session, - // then send them SIGHUP and SIGCONT. - - // If we're not the session leader, we don't have to do much. - if tty.tg != tg { - tg.tty = nil - tg.signalHandlers.mu.Unlock() - return nil - } - - tg.signalHandlers.mu.Unlock() - - // We're the session leader. SIGHUP and SIGCONT the foreground process - // group and remove all controlling terminals in the session. - var lastErr error - for othertg := range tg.pidns.owner.Root.tgids { - if othertg.processGroup.session == tg.processGroup.session { - othertg.signalHandlers.mu.Lock() - othertg.tty = nil - if othertg.processGroup == tg.processGroup.session.foreground { - if err := othertg.leader.sendSignalLocked(&arch.SignalInfo{Signo: int32(linux.SIGHUP)}, true /* group */); err != nil { - lastErr = err - } - if err := othertg.leader.sendSignalLocked(&arch.SignalInfo{Signo: int32(linux.SIGCONT)}, true /* group */); err != nil { - lastErr = err - } - } - othertg.signalHandlers.mu.Unlock() - } - } - - return lastErr -} - -// ForegroundProcessGroup returns the process group ID of the foreground -// process group. -func (tg *ThreadGroup) ForegroundProcessGroup(tty *TTY) (int32, error) { - tty.mu.Lock() - defer tty.mu.Unlock() - - tg.pidns.owner.mu.Lock() - defer tg.pidns.owner.mu.Unlock() - tg.signalHandlers.mu.Lock() - defer tg.signalHandlers.mu.Unlock() - - // "When fd does not refer to the controlling terminal of the calling - // process, -1 is returned" - tcgetpgrp(3) - if tg.tty != tty { - return -1, syserror.ENOTTY - } - - return int32(tg.processGroup.session.foreground.id), nil -} - -// SetForegroundProcessGroup sets the foreground process group of tty to pgid. -func (tg *ThreadGroup) SetForegroundProcessGroup(tty *TTY, pgid ProcessGroupID) (int32, error) { - tty.mu.Lock() - defer tty.mu.Unlock() - - tg.pidns.owner.mu.Lock() - defer tg.pidns.owner.mu.Unlock() - tg.signalHandlers.mu.Lock() - defer tg.signalHandlers.mu.Unlock() - - // TODO(b/129283598): "If tcsetpgrp() is called by a member of a - // background process group in its session, and the calling process is - // not blocking or ignoring SIGTTOU, a SIGTTOU signal is sent to all - // members of this background process group." - - // tty must be the controlling terminal. - if tg.tty != tty { - return -1, syserror.ENOTTY - } - - // pgid must be positive. - if pgid < 0 { - return -1, syserror.EINVAL - } - - // pg must not be empty. Empty process groups are removed from their - // pid namespaces. - pg, ok := tg.pidns.processGroups[pgid] - if !ok { - return -1, syserror.ESRCH - } - - // pg must be part of this process's session. - if tg.processGroup.session != pg.session { - return -1, syserror.EPERM - } - - tg.processGroup.session.foreground.id = pgid - return 0, nil -} - // itimerRealListener implements ktime.Listener for ITIMER_REAL expirations. // // +stateify savable diff --git a/pkg/sentry/kernel/tty.go b/pkg/sentry/kernel/tty.go deleted file mode 100644 index 34f84487a..000000000 --- a/pkg/sentry/kernel/tty.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package kernel - -import "sync" - -// TTY defines the relationship between a thread group and its controlling -// terminal. -// -// +stateify savable -type TTY struct { - mu sync.Mutex `state:"nosave"` - - // tg is protected by mu. - tg *ThreadGroup -} diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index 6947ddc25..a8a2e75d3 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -319,10 +319,6 @@ syscall_test( ) syscall_test( - test = "//test/syscalls/linux:pty_root_test", -) - -syscall_test( add_overlay = True, test = "//test/syscalls/linux:pwritev2_test", ) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index bb065aa4f..aeb4b405d 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -1278,10 +1278,8 @@ cc_binary( srcs = ["pty.cc"], linkstatic = 1, deps = [ - "//test/util:capability_util", "//test/util:file_descriptor", "//test/util:posix_error", - "//test/util:pty_util", "//test/util:test_main", "//test/util:test_util", "//test/util:thread_util", @@ -1294,23 +1292,6 @@ cc_binary( ) cc_binary( - name = "pty_root_test", - testonly = 1, - srcs = ["pty_root.cc"], - linkstatic = 1, - deps = [ - "//test/util:capability_util", - "//test/util:file_descriptor", - "//test/util:posix_error", - "//test/util:pty_util", - "//test/util:test_main", - "//test/util:thread_util", - "@com_google_absl//absl/base:core_headers", - "@com_google_googletest//:gtest", - ], -) - -cc_binary( name = "partial_bad_buffer_test", testonly = 1, srcs = ["partial_bad_buffer.cc"], diff --git a/test/syscalls/linux/pty.cc b/test/syscalls/linux/pty.cc index c605b6549..d1ab4703f 100644 --- a/test/syscalls/linux/pty.cc +++ b/test/syscalls/linux/pty.cc @@ -13,17 +13,13 @@ // limitations under the License. #include <fcntl.h> -#include <linux/capability.h> #include <linux/major.h> #include <poll.h> -#include <sched.h> -#include <signal.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/sysmacros.h> #include <sys/types.h> -#include <sys/wait.h> #include <termios.h> #include <unistd.h> @@ -35,10 +31,8 @@ #include "absl/synchronization/notification.h" #include "absl/time/clock.h" #include "absl/time/time.h" -#include "test/util/capability_util.h" #include "test/util/file_descriptor.h" #include "test/util/posix_error.h" -#include "test/util/pty_util.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" @@ -376,6 +370,25 @@ PosixErrorOr<size_t> PollAndReadFd(int fd, void* buf, size_t count, return PosixError(ETIMEDOUT, "Poll timed out"); } +// Opens the slave end of the passed master as R/W and nonblocking. +PosixErrorOr<FileDescriptor> OpenSlave(const FileDescriptor& master) { + // Get pty index. + int n; + int ret = ioctl(master.get(), TIOCGPTN, &n); + if (ret < 0) { + return PosixError(errno, "ioctl(TIOCGPTN) failed"); + } + + // Unlock pts. + int unlock = 0; + ret = ioctl(master.get(), TIOCSPTLCK, &unlock); + if (ret < 0) { + return PosixError(errno, "ioctl(TIOSPTLCK) failed"); + } + + return Open(absl::StrCat("/dev/pts/", n), O_RDWR | O_NONBLOCK); +} + TEST(BasicPtyTest, StatUnopenedMaster) { struct stat s; ASSERT_THAT(stat("/dev/ptmx", &s), SyscallSucceeds()); @@ -1220,332 +1233,6 @@ TEST_F(PtyTest, SetMasterWindowSize) { EXPECT_EQ(retrieved_ws.ws_col, kCols); } -class JobControlTest : public ::testing::Test { - protected: - void SetUp() override { - master_ = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR | O_NONBLOCK)); - slave_ = ASSERT_NO_ERRNO_AND_VALUE(OpenSlave(master_)); - - // Make this a session leader, which also drops the controlling terminal. - // In the gVisor test environment, this test will be run as the session - // leader already (as the sentry init process). - if (!IsRunningOnGvisor()) { - ASSERT_THAT(setsid(), SyscallSucceeds()); - } - } - - // Master and slave ends of the PTY. Non-blocking. - FileDescriptor master_; - FileDescriptor slave_; -}; - -TEST_F(JobControlTest, SetTTYMaster) { - ASSERT_THAT(ioctl(master_.get(), TIOCSCTTY, 0), SyscallSucceeds()); -} - -TEST_F(JobControlTest, SetTTY) { - ASSERT_THAT(ioctl(slave_.get(), TIOCSCTTY, 0), SyscallSucceeds()); -} - -TEST_F(JobControlTest, SetTTYNonLeader) { - // Fork a process that won't be the session leader. - pid_t child = fork(); - if (!child) { - // We shouldn't be able to set the terminal. - TEST_PCHECK(ioctl(slave_.get(), TIOCSCTTY, 0)); - _exit(0); - } - - int wstatus; - ASSERT_THAT(waitpid(child, &wstatus, 0), SyscallSucceedsWithValue(child)); - ASSERT_EQ(wstatus, 0); -} - -TEST_F(JobControlTest, SetTTYBadArg) { - // Despite the man page saying arg should be 0 here, Linux doesn't actually - // check. - ASSERT_THAT(ioctl(slave_.get(), TIOCSCTTY, 1), SyscallSucceeds()); -} - -TEST_F(JobControlTest, SetTTYDifferentSession) { - SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - ASSERT_THAT(ioctl(slave_.get(), TIOCSCTTY, 0), SyscallSucceeds()); - - // Fork, join a new session, and try to steal the parent's controlling - // terminal, which should fail. - pid_t child = fork(); - if (!child) { - TEST_PCHECK(setsid() >= 0); - // We shouldn't be able to steal the terminal. - TEST_PCHECK(ioctl(slave_.get(), TIOCSCTTY, 1)); - _exit(0); - } - - int wstatus; - ASSERT_THAT(waitpid(child, &wstatus, 0), SyscallSucceedsWithValue(child)); - ASSERT_EQ(wstatus, 0); -} - -TEST_F(JobControlTest, ReleaseTTY) { - ASSERT_THAT(ioctl(slave_.get(), TIOCSCTTY, 0), SyscallSucceeds()); - - // Make sure we're ignoring SIGHUP, which will be sent to this process once we - // disconnect they TTY. - struct sigaction sa = {}; - sa.sa_handler = SIG_IGN; - sigemptyset(&sa.sa_mask); - struct sigaction old_sa; - EXPECT_THAT(sigaction(SIGHUP, &sa, &old_sa), SyscallSucceeds()); - EXPECT_THAT(ioctl(slave_.get(), TIOCNOTTY), SyscallSucceeds()); - EXPECT_THAT(sigaction(SIGHUP, &old_sa, NULL), SyscallSucceeds()); -} - -TEST_F(JobControlTest, ReleaseUnsetTTY) { - ASSERT_THAT(ioctl(slave_.get(), TIOCNOTTY), SyscallFailsWithErrno(ENOTTY)); -} - -TEST_F(JobControlTest, ReleaseWrongTTY) { - ASSERT_THAT(ioctl(slave_.get(), TIOCSCTTY, 0), SyscallSucceeds()); - - ASSERT_THAT(ioctl(master_.get(), TIOCNOTTY), SyscallFailsWithErrno(ENOTTY)); -} - -TEST_F(JobControlTest, ReleaseTTYNonLeader) { - ASSERT_THAT(ioctl(slave_.get(), TIOCSCTTY, 0), SyscallSucceeds()); - - pid_t child = fork(); - if (!child) { - TEST_PCHECK(!ioctl(slave_.get(), TIOCNOTTY)); - _exit(0); - } - - int wstatus; - ASSERT_THAT(waitpid(child, &wstatus, 0), SyscallSucceedsWithValue(child)); - ASSERT_EQ(wstatus, 0); -} - -TEST_F(JobControlTest, ReleaseTTYDifferentSession) { - ASSERT_THAT(ioctl(slave_.get(), TIOCSCTTY, 0), SyscallSucceeds()); - - pid_t child = fork(); - if (!child) { - // Join a new session, then try to disconnect. - TEST_PCHECK(setsid() >= 0); - TEST_PCHECK(ioctl(slave_.get(), TIOCNOTTY)); - _exit(0); - } - - int wstatus; - ASSERT_THAT(waitpid(child, &wstatus, 0), SyscallSucceedsWithValue(child)); - ASSERT_EQ(wstatus, 0); -} - -// Used by the child process spawned in ReleaseTTYSignals to track received -// signals. -static int received; - -void sig_handler(int signum) { received |= signum; } - -// When the session leader releases its controlling terminal, the foreground -// process group gets SIGHUP, then SIGCONT. This test: -// - Spawns 2 threads -// - Has thread 1 return 0 if it gets both SIGHUP and SIGCONT -// - Has thread 2 leave the foreground process group, and return non-zero if it -// receives any signals. -// - Has the parent thread release its controlling terminal -// - Checks that thread 1 got both signals -// - Checks that thread 2 didn't get any signals. -TEST_F(JobControlTest, ReleaseTTYSignals) { - ASSERT_THAT(ioctl(slave_.get(), TIOCSCTTY, 0), SyscallSucceeds()); - - received = 0; - struct sigaction sa = {}; - sa.sa_handler = sig_handler; - sigemptyset(&sa.sa_mask); - sigaddset(&sa.sa_mask, SIGHUP); - sigaddset(&sa.sa_mask, SIGCONT); - sigprocmask(SIG_BLOCK, &sa.sa_mask, NULL); - - pid_t same_pgrp_child = fork(); - if (!same_pgrp_child) { - // The child will wait for SIGHUP and SIGCONT, then return 0. It begins with - // SIGHUP and SIGCONT blocked. We install signal handlers for those signals, - // then use sigsuspend to wait for those specific signals. - TEST_PCHECK(!sigaction(SIGHUP, &sa, NULL)); - TEST_PCHECK(!sigaction(SIGCONT, &sa, NULL)); - sigset_t mask; - sigfillset(&mask); - sigdelset(&mask, SIGHUP); - sigdelset(&mask, SIGCONT); - while (received != (SIGHUP | SIGCONT)) { - sigsuspend(&mask); - } - _exit(0); - } - - // We don't want to block these anymore. - sigprocmask(SIG_UNBLOCK, &sa.sa_mask, NULL); - - // This child will return non-zero if either SIGHUP or SIGCONT are received. - pid_t diff_pgrp_child = fork(); - if (!diff_pgrp_child) { - TEST_PCHECK(!setpgid(0, 0)); - TEST_PCHECK(pause()); - _exit(1); - } - - EXPECT_THAT(setpgid(diff_pgrp_child, diff_pgrp_child), SyscallSucceeds()); - - // Make sure we're ignoring SIGHUP, which will be sent to this process once we - // disconnect they TTY. - struct sigaction sighup_sa = {}; - sighup_sa.sa_handler = SIG_IGN; - sigemptyset(&sighup_sa.sa_mask); - struct sigaction old_sa; - EXPECT_THAT(sigaction(SIGHUP, &sighup_sa, &old_sa), SyscallSucceeds()); - - // Release the controlling terminal, sending SIGHUP and SIGCONT to all other - // processes in this process group. - EXPECT_THAT(ioctl(slave_.get(), TIOCNOTTY), SyscallSucceeds()); - - EXPECT_THAT(sigaction(SIGHUP, &old_sa, NULL), SyscallSucceeds()); - - // The child in the same process group will get signaled. - int wstatus; - EXPECT_THAT(waitpid(same_pgrp_child, &wstatus, 0), - SyscallSucceedsWithValue(same_pgrp_child)); - EXPECT_EQ(wstatus, 0); - - // The other child will not get signaled. - EXPECT_THAT(waitpid(diff_pgrp_child, &wstatus, WNOHANG), - SyscallSucceedsWithValue(0)); - EXPECT_THAT(kill(diff_pgrp_child, SIGKILL), SyscallSucceeds()); -} - -TEST_F(JobControlTest, GetForegroundProcessGroup) { - ASSERT_THAT(ioctl(slave_.get(), TIOCSCTTY, 0), SyscallSucceeds()); - pid_t foreground_pgid; - pid_t pid; - ASSERT_THAT(ioctl(slave_.get(), TIOCGPGRP, &foreground_pgid), - SyscallSucceeds()); - ASSERT_THAT(pid = getpid(), SyscallSucceeds()); - - ASSERT_EQ(foreground_pgid, pid); -} - -TEST_F(JobControlTest, GetForegroundProcessGroupNonControlling) { - // At this point there's no controlling terminal, so TIOCGPGRP should fail. - pid_t foreground_pgid; - ASSERT_THAT(ioctl(slave_.get(), TIOCGPGRP, &foreground_pgid), - SyscallFailsWithErrno(ENOTTY)); -} - -// This test: -// - sets itself as the foreground process group -// - creates a child process in a new process group -// - sets that child as the foreground process group -// - kills its child and sets itself as the foreground process group. -TEST_F(JobControlTest, SetForegroundProcessGroup) { - ASSERT_THAT(ioctl(slave_.get(), TIOCSCTTY, 0), SyscallSucceeds()); - - // Ignore SIGTTOU so that we don't stop ourself when calling tcsetpgrp. - struct sigaction sa = {}; - sa.sa_handler = SIG_IGN; - sigemptyset(&sa.sa_mask); - sigaction(SIGTTOU, &sa, NULL); - - // Set ourself as the foreground process group. - ASSERT_THAT(tcsetpgrp(slave_.get(), getpgid(0)), SyscallSucceeds()); - - // Create a new process that just waits to be signaled. - pid_t child = fork(); - if (!child) { - TEST_PCHECK(!pause()); - // We should never reach this. - _exit(1); - } - - // Make the child its own process group, then make it the controlling process - // group of the terminal. - ASSERT_THAT(setpgid(child, child), SyscallSucceeds()); - ASSERT_THAT(tcsetpgrp(slave_.get(), child), SyscallSucceeds()); - - // Sanity check - we're still the controlling session. - ASSERT_EQ(getsid(0), getsid(child)); - - // Signal the child, wait for it to exit, then retake the terminal. - ASSERT_THAT(kill(child, SIGTERM), SyscallSucceeds()); - int wstatus; - ASSERT_THAT(waitpid(child, &wstatus, 0), SyscallSucceedsWithValue(child)); - ASSERT_TRUE(WIFSIGNALED(wstatus)); - ASSERT_EQ(WTERMSIG(wstatus), SIGTERM); - - // Set ourself as the foreground process. - pid_t pgid; - ASSERT_THAT(pgid = getpgid(0), SyscallSucceeds()); - ASSERT_THAT(tcsetpgrp(slave_.get(), pgid), SyscallSucceeds()); -} - -TEST_F(JobControlTest, SetForegroundProcessGroupWrongTTY) { - pid_t pid = getpid(); - ASSERT_THAT(ioctl(slave_.get(), TIOCSPGRP, &pid), - SyscallFailsWithErrno(ENOTTY)); -} - -TEST_F(JobControlTest, SetForegroundProcessGroupNegPgid) { - ASSERT_THAT(ioctl(slave_.get(), TIOCSCTTY, 0), SyscallSucceeds()); - - pid_t pid = -1; - ASSERT_THAT(ioctl(slave_.get(), TIOCSPGRP, &pid), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_F(JobControlTest, SetForegroundProcessGroupEmptyProcessGroup) { - ASSERT_THAT(ioctl(slave_.get(), TIOCSCTTY, 0), SyscallSucceeds()); - - // Create a new process, put it in a new process group, make that group the - // foreground process group, then have the process wait. - pid_t child = fork(); - if (!child) { - TEST_PCHECK(!setpgid(0, 0)); - _exit(0); - } - - // Wait for the child to exit. - int wstatus; - EXPECT_THAT(waitpid(child, &wstatus, 0), SyscallSucceedsWithValue(child)); - // The child's process group doesn't exist anymore - this should fail. - ASSERT_THAT(ioctl(slave_.get(), TIOCSPGRP, &child), - SyscallFailsWithErrno(ESRCH)); -} - -TEST_F(JobControlTest, SetForegroundProcessGroupDifferentSession) { - ASSERT_THAT(ioctl(slave_.get(), TIOCSCTTY, 0), SyscallSucceeds()); - - // Create a new process and put it in a new session. - pid_t child = fork(); - if (!child) { - TEST_PCHECK(setsid() >= 0); - // Tell the parent we're in a new session. - TEST_PCHECK(!raise(SIGSTOP)); - TEST_PCHECK(!pause()); - _exit(1); - } - - // Wait for the child to tell us it's in a new session. - int wstatus; - EXPECT_THAT(waitpid(child, &wstatus, WUNTRACED), - SyscallSucceedsWithValue(child)); - EXPECT_TRUE(WSTOPSIG(wstatus)); - - // Child is in a new session, so we can't make it the foregroup process group. - EXPECT_THAT(ioctl(slave_.get(), TIOCSPGRP, &child), - SyscallFailsWithErrno(EPERM)); - - EXPECT_THAT(kill(child, SIGKILL), SyscallSucceeds()); -} - } // namespace } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/pty_root.cc b/test/syscalls/linux/pty_root.cc deleted file mode 100644 index d2a321a6e..000000000 --- a/test/syscalls/linux/pty_root.cc +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <sys/ioctl.h> -#include <termios.h> - -#include "gtest/gtest.h" -#include "absl/base/macros.h" -#include "test/util/capability_util.h" -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" -#include "test/util/pty_util.h" - -namespace gvisor { -namespace testing { - -// These tests should be run as root. -namespace { - -TEST(JobControlRootTest, StealTTY) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - - // Make this a session leader, which also drops the controlling terminal. - // In the gVisor test environment, this test will be run as the session - // leader already (as the sentry init process). - if (!IsRunningOnGvisor()) { - ASSERT_THAT(setsid(), SyscallSucceeds()); - } - - FileDescriptor master = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR | O_NONBLOCK)); - FileDescriptor slave = ASSERT_NO_ERRNO_AND_VALUE(OpenSlave(master)); - - // Make slave the controlling terminal. - ASSERT_THAT(ioctl(slave.get(), TIOCSCTTY, 0), SyscallSucceeds()); - - // Fork, join a new session, and try to steal the parent's controlling - // terminal, which should succeed when we have CAP_SYS_ADMIN and pass an arg - // of 1. - pid_t child = fork(); - if (!child) { - TEST_PCHECK(setsid() >= 0); - // We shouldn't be able to steal the terminal with the wrong arg value. - TEST_PCHECK(ioctl(slave.get(), TIOCSCTTY, 0)); - // We should be able to steal it here. - TEST_PCHECK(!ioctl(slave.get(), TIOCSCTTY, 1)); - _exit(0); - } - - int wstatus; - ASSERT_THAT(waitpid(child, &wstatus, 0), SyscallSucceedsWithValue(child)); - ASSERT_EQ(wstatus, 0); -} - -} // namespace -} // namespace testing -} // namespace gvisor diff --git a/test/util/BUILD b/test/util/BUILD index cfea029b2..8afd89d8d 100644 --- a/test/util/BUILD +++ b/test/util/BUILD @@ -190,17 +190,6 @@ cc_test( ) cc_library( - name = "pty_util", - testonly = 1, - srcs = ["pty_util.cc"], - hdrs = ["pty_util.h"], - deps = [ - ":file_descriptor", - ":posix_error", - ], -) - -cc_library( name = "signal_util", testonly = 1, srcs = ["signal_util.cc"], diff --git a/test/util/pty_util.cc b/test/util/pty_util.cc deleted file mode 100644 index c0fd9a095..000000000 --- a/test/util/pty_util.cc +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/util/pty_util.h" - -#include <sys/ioctl.h> -#include <termios.h> - -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" - -namespace gvisor { -namespace testing { - -PosixErrorOr<FileDescriptor> OpenSlave(const FileDescriptor& master) { - // Get pty index. - int n; - int ret = ioctl(master.get(), TIOCGPTN, &n); - if (ret < 0) { - return PosixError(errno, "ioctl(TIOCGPTN) failed"); - } - - // Unlock pts. - int unlock = 0; - ret = ioctl(master.get(), TIOCSPTLCK, &unlock); - if (ret < 0) { - return PosixError(errno, "ioctl(TIOSPTLCK) failed"); - } - - return Open(absl::StrCat("/dev/pts/", n), O_RDWR | O_NONBLOCK); -} - -} // namespace testing -} // namespace gvisor diff --git a/test/util/pty_util.h b/test/util/pty_util.h deleted file mode 100644 index 367b14f15..000000000 --- a/test/util/pty_util.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef GVISOR_TEST_UTIL_PTY_UTIL_H_ -#define GVISOR_TEST_UTIL_PTY_UTIL_H_ - -#include "test/util/file_descriptor.h" -#include "test/util/posix_error.h" - -namespace gvisor { -namespace testing { - -// Opens the slave end of the passed master as R/W and nonblocking. -PosixErrorOr<FileDescriptor> OpenSlave(const FileDescriptor& master); - -} // namespace testing -} // namespace gvisor - -#endif // GVISOR_TEST_UTIL_PTY_UTIL_H_ |