From d02b74a5dcfed4bfc8f2f8e545bca4d2afabb296 Mon Sep 17 00:00:00 2001
From: Googler <noreply@google.com>
Date: Fri, 27 Apr 2018 10:37:02 -0700
Subject: Check in gVisor.

PiperOrigin-RevId: 194583126
Change-Id: Ica1d8821a90f74e7e745962d71801c598c652463
---
 pkg/sentry/control/BUILD        |  39 ++++++
 pkg/sentry/control/control.go   |  17 +++
 pkg/sentry/control/proc.go      | 293 ++++++++++++++++++++++++++++++++++++++++
 pkg/sentry/control/proc_test.go | 164 ++++++++++++++++++++++
 4 files changed, 513 insertions(+)
 create mode 100644 pkg/sentry/control/BUILD
 create mode 100644 pkg/sentry/control/control.go
 create mode 100644 pkg/sentry/control/proc.go
 create mode 100644 pkg/sentry/control/proc_test.go

(limited to 'pkg/sentry/control')

diff --git a/pkg/sentry/control/BUILD b/pkg/sentry/control/BUILD
new file mode 100644
index 000000000..4d1d0d019
--- /dev/null
+++ b/pkg/sentry/control/BUILD
@@ -0,0 +1,39 @@
+package(licenses = ["notice"])  # Apache 2.0
+
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+    name = "control",
+    srcs = [
+        "control.go",
+        "proc.go",
+    ],
+    importpath = "gvisor.googlesource.com/gvisor/pkg/sentry/control",
+    visibility = [
+        "//pkg/sentry:internal",
+    ],
+    deps = [
+        "//pkg/abi/linux",
+        "//pkg/sentry/fs",
+        "//pkg/sentry/fs/host",
+        "//pkg/sentry/kernel",
+        "//pkg/sentry/kernel/auth",
+        "//pkg/sentry/kernel/kdefs",
+        "//pkg/sentry/kernel/time",
+        "//pkg/sentry/limits",
+        "//pkg/sentry/usage",
+        "//pkg/urpc",
+    ],
+)
+
+go_test(
+    name = "control_test",
+    size = "small",
+    srcs = ["proc_test.go"],
+    embed = [":control"],
+    deps = [
+        "//pkg/log",
+        "//pkg/sentry/kernel/time",
+        "//pkg/sentry/usage",
+    ],
+)
diff --git a/pkg/sentry/control/control.go b/pkg/sentry/control/control.go
new file mode 100644
index 000000000..a6ee6e649
--- /dev/null
+++ b/pkg/sentry/control/control.go
@@ -0,0 +1,17 @@
+// Copyright 2018 Google Inc.
+//
+// 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 control contains types that expose control server methods, and can
+// be used to configure and interact with a running sandbox process.
+package control
diff --git a/pkg/sentry/control/proc.go b/pkg/sentry/control/proc.go
new file mode 100644
index 000000000..7d06a1d04
--- /dev/null
+++ b/pkg/sentry/control/proc.go
@@ -0,0 +1,293 @@
+// Copyright 2018 Google Inc.
+//
+// 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 control
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"syscall"
+	"text/tabwriter"
+	"time"
+
+	"gvisor.googlesource.com/gvisor/pkg/abi/linux"
+	"gvisor.googlesource.com/gvisor/pkg/sentry/fs"
+	"gvisor.googlesource.com/gvisor/pkg/sentry/fs/host"
+	"gvisor.googlesource.com/gvisor/pkg/sentry/kernel"
+	"gvisor.googlesource.com/gvisor/pkg/sentry/kernel/auth"
+	"gvisor.googlesource.com/gvisor/pkg/sentry/kernel/kdefs"
+	ktime "gvisor.googlesource.com/gvisor/pkg/sentry/kernel/time"
+	"gvisor.googlesource.com/gvisor/pkg/sentry/limits"
+	"gvisor.googlesource.com/gvisor/pkg/sentry/usage"
+	"gvisor.googlesource.com/gvisor/pkg/urpc"
+)
+
+// Proc includes task-related functions.
+//
+// At the moment, this is limited to exec support.
+type Proc struct {
+	Kernel *kernel.Kernel
+}
+
+// ExecArgs is the set of arguments to exec.
+type ExecArgs struct {
+	// Filename is the filename to load.
+	//
+	// If this is provided as "", then the file will be guessed via Argv[0].
+	Filename string `json:"filename"`
+
+	// Argv is a list of arguments.
+	Argv []string `json:"argv"`
+
+	// Envv is a list of environment variables.
+	Envv []string `json:"envv"`
+
+	// WorkingDirectory defines the working directory for the new process.
+	WorkingDirectory string `json:"wd"`
+
+	// KUID is the UID to run with in the root user namespace. Defaults to
+	// root if not set explicitly.
+	KUID auth.KUID
+
+	// KGID is the GID to run with in the root user namespace. Defaults to
+	// the root group if not set explicitly.
+	KGID auth.KGID
+
+	// ExtraKGIDs is the list of additional groups to which the user
+	// belongs.
+	ExtraKGIDs []auth.KGID
+
+	// Capabilities is the list of capabilities to give to the process.
+	Capabilities *auth.TaskCapabilities
+
+	// Detach indicates whether Exec should detach once the process starts.
+	Detach bool
+
+	// FilePayload determines the files to give to the new process.
+	urpc.FilePayload
+}
+
+// Exec runs a new task.
+func (proc *Proc) Exec(args *ExecArgs, waitStatus *uint32) error {
+	// Import file descriptors.
+	l := limits.NewLimitSet()
+	fdm := proc.Kernel.NewFDMap()
+	defer fdm.DecRef()
+
+	creds := auth.NewUserCredentials(
+		args.KUID,
+		args.KGID,
+		args.ExtraKGIDs,
+		args.Capabilities,
+		proc.Kernel.RootUserNamespace())
+
+	initArgs := kernel.CreateProcessArgs{
+		Filename:             args.Filename,
+		Argv:                 args.Argv,
+		Envv:                 args.Envv,
+		WorkingDirectory:     args.WorkingDirectory,
+		Credentials:          creds,
+		FDMap:                fdm,
+		Umask:                0022,
+		Limits:               l,
+		MaxSymlinkTraversals: linux.MaxSymlinkTraversals,
+		UTSNamespace:         proc.Kernel.RootUTSNamespace(),
+		IPCNamespace:         proc.Kernel.RootIPCNamespace(),
+	}
+	ctx := initArgs.NewContext(proc.Kernel)
+	mounter := fs.FileOwnerFromContext(ctx)
+
+	for appFD, f := range args.FilePayload.Files {
+		// Copy the underlying FD.
+		newFD, err := syscall.Dup(int(f.Fd()))
+		if err != nil {
+			return err
+		}
+		f.Close()
+
+		// Install the given file as an FD.
+		file, err := host.NewFile(ctx, newFD, mounter)
+		if err != nil {
+			syscall.Close(newFD)
+			return err
+		}
+		defer file.DecRef()
+		if err := fdm.NewFDAt(kdefs.FD(appFD), file, kernel.FDFlags{}, l); err != nil {
+			return err
+		}
+	}
+
+	// Start the new task.
+	newTG, err := proc.Kernel.CreateProcess(initArgs)
+	if err != nil {
+		return err
+	}
+
+	// If we're supposed to detach, don't wait for the process to exit.
+	if args.Detach {
+		*waitStatus = 0
+		return nil
+	}
+
+	// Wait for completion.
+	newTG.WaitExited()
+	*waitStatus = newTG.ExitStatus().Status()
+	return nil
+}
+
+// PsArgs is the set of arguments to ps.
+type PsArgs struct {
+	// JSON will force calls to Ps to return the result as a JSON payload.
+	JSON bool
+}
+
+// Ps provides a process listing for the running kernel.
+func (proc *Proc) Ps(args *PsArgs, out *string) error {
+	var p []*Process
+	if e := Processes(proc.Kernel, &p); e != nil {
+		return e
+	}
+	if !args.JSON {
+		*out = ProcessListToTable(p)
+	} else {
+		s, e := ProcessListToJSON(p)
+		if e != nil {
+			return e
+		}
+		*out = s
+	}
+	return nil
+}
+
+// Process contains information about a single process in a Sandbox.
+// TODO: Implement TTY field.
+type Process struct {
+	UID auth.KUID       `json:"uid"`
+	PID kernel.ThreadID `json:"pid"`
+	// Parent PID
+	PPID kernel.ThreadID `json:"ppid"`
+	// Processor utilization
+	C int32 `json:"c"`
+	// Start time
+	STime string `json:"stime"`
+	// CPU time
+	Time string `json:"time"`
+	// Executable shortname (e.g. "sh" for /bin/sh)
+	Cmd string `json:"cmd"`
+}
+
+// ProcessListToTable prints a table with the following format:
+// UID       PID       PPID      C         STIME     TIME       CMD
+// 0         1         0         0         14:04     505262ns   tail
+func ProcessListToTable(pl []*Process) string {
+	var buf bytes.Buffer
+	tw := tabwriter.NewWriter(&buf, 10, 1, 3, ' ', 0)
+	fmt.Fprint(tw, "UID\tPID\tPPID\tC\tSTIME\tTIME\tCMD")
+	for _, d := range pl {
+		fmt.Fprintf(tw, "\n%d\t%d\t%d\t%d\t%s\t%s\t%s",
+			d.UID,
+			d.PID,
+			d.PPID,
+			d.C,
+			d.STime,
+			d.Time,
+			d.Cmd)
+	}
+	tw.Flush()
+	return buf.String()
+}
+
+// ProcessListToJSON will return the JSON representation of ps.
+func ProcessListToJSON(pl []*Process) (string, error) {
+	b, err := json.Marshal(pl)
+	if err != nil {
+		return "", fmt.Errorf("couldn't marshal process list %v: %v", pl, err)
+	}
+	return string(b), nil
+}
+
+// PrintPIDsJSON prints a JSON object containing only the PIDs in pl. This
+// behavior is the same as runc's.
+func PrintPIDsJSON(pl []*Process) (string, error) {
+	pids := make([]kernel.ThreadID, 0, len(pl))
+	for _, d := range pl {
+		pids = append(pids, d.PID)
+	}
+	b, err := json.Marshal(pids)
+	if err != nil {
+		return "", fmt.Errorf("couldn't marshal PIDs %v: %v", pids, err)
+	}
+	return string(b), nil
+}
+
+// Processes retrieves information about processes running in the sandbox.
+func Processes(k *kernel.Kernel, out *[]*Process) error {
+	ts := k.TaskSet()
+	now := k.RealtimeClock().Now()
+	for _, tg := range ts.Root.ThreadGroups() {
+		pid := ts.Root.IDOfThreadGroup(tg)
+		// If tg has already been reaped ignore it.
+		if pid == 0 {
+			continue
+		}
+
+		*out = append(*out, &Process{
+			UID: tg.Leader().Credentials().EffectiveKUID,
+			PID: pid,
+			// If Parent is null (i.e. tg is the init process), PPID will be 0.
+			PPID:  ts.Root.IDOfTask(tg.Leader().Parent()),
+			STime: formatStartTime(now, tg.Leader().StartTime()),
+			C:     percentCPU(tg.CPUStats(), tg.Leader().StartTime(), now),
+			Time:  tg.CPUStats().SysTime.String(),
+			Cmd:   tg.Leader().Name(),
+		})
+	}
+	return nil
+}
+
+// formatStartTime formats startTime depending on the current time:
+// - If startTime was today, HH:MM is used.
+// - If startTime was not today but was this year, MonDD is used (e.g. Jan02)
+// - If startTime was not this year, the year is used.
+func formatStartTime(now, startTime ktime.Time) string {
+	nowS, nowNs := now.Unix()
+	n := time.Unix(nowS, nowNs)
+	startTimeS, startTimeNs := startTime.Unix()
+	st := time.Unix(startTimeS, startTimeNs)
+	format := "15:04"
+	if st.YearDay() != n.YearDay() {
+		format = "Jan02"
+	}
+	if st.Year() != n.Year() {
+		format = "2006"
+	}
+	return st.Format(format)
+}
+
+func percentCPU(stats usage.CPUStats, startTime, now ktime.Time) int32 {
+	// Note: In procps, there is an option to include child CPU stats. As
+	// it is disabled by default, we do not include them.
+	total := stats.UserTime + stats.SysTime
+	lifetime := now.Sub(startTime)
+	if lifetime <= 0 {
+		return 0
+	}
+	percentCPU := total * 100 / lifetime
+	// Cap at 99% since procps does the same.
+	if percentCPU > 99 {
+		percentCPU = 99
+	}
+	return int32(percentCPU)
+}
diff --git a/pkg/sentry/control/proc_test.go b/pkg/sentry/control/proc_test.go
new file mode 100644
index 000000000..18286496f
--- /dev/null
+++ b/pkg/sentry/control/proc_test.go
@@ -0,0 +1,164 @@
+// Copyright 2018 Google Inc.
+//
+// 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 control
+
+import (
+	"testing"
+
+	"gvisor.googlesource.com/gvisor/pkg/log"
+	ktime "gvisor.googlesource.com/gvisor/pkg/sentry/kernel/time"
+	"gvisor.googlesource.com/gvisor/pkg/sentry/usage"
+)
+
+func init() {
+	log.SetLevel(log.Debug)
+}
+
+// Tests that ProcessData.Table() prints with the correct format.
+func TestProcessListTable(t *testing.T) {
+	testCases := []struct {
+		pl       []*Process
+		expected string
+	}{
+		{
+			pl:       []*Process{},
+			expected: "UID       PID       PPID      C         STIME     TIME      CMD",
+		},
+		{
+			pl: []*Process{
+				&Process{
+					UID:   0,
+					PID:   0,
+					PPID:  0,
+					C:     0,
+					STime: "0",
+					Time:  "0",
+					Cmd:   "zero",
+				},
+				&Process{
+					UID:   1,
+					PID:   1,
+					PPID:  1,
+					C:     1,
+					STime: "1",
+					Time:  "1",
+					Cmd:   "one",
+				},
+			},
+			expected: `UID       PID       PPID      C         STIME     TIME      CMD
+0         0         0         0         0         0         zero
+1         1         1         1         1         1         one`,
+		},
+	}
+
+	for _, tc := range testCases {
+		output := ProcessListToTable(tc.pl)
+
+		if tc.expected != output {
+			t.Errorf("PrintTable(%v): got:\n%s\nwant:\n%s", tc.pl, output, tc.expected)
+		}
+	}
+}
+
+func TestProcessListJSON(t *testing.T) {
+	testCases := []struct {
+		pl       []*Process
+		expected string
+	}{
+		{
+			pl:       []*Process{},
+			expected: "[]",
+		},
+		{
+			pl: []*Process{
+				&Process{
+					UID:   0,
+					PID:   0,
+					PPID:  0,
+					C:     0,
+					STime: "0",
+					Time:  "0",
+					Cmd:   "zero",
+				},
+				&Process{
+					UID:   1,
+					PID:   1,
+					PPID:  1,
+					C:     1,
+					STime: "1",
+					Time:  "1",
+					Cmd:   "one",
+				},
+			},
+			expected: "[0,1]",
+		},
+	}
+
+	for _, tc := range testCases {
+		output, err := PrintPIDsJSON(tc.pl)
+		if err != nil {
+			t.Errorf("failed to generate JSON: %v", err)
+		}
+
+		if tc.expected != output {
+			t.Errorf("PrintJSON(%v): got:\n%s\nwant:\n%s", tc.pl, output, tc.expected)
+		}
+	}
+}
+
+func TestPercentCPU(t *testing.T) {
+	testCases := []struct {
+		stats     usage.CPUStats
+		startTime ktime.Time
+		now       ktime.Time
+		expected  int32
+	}{
+		{
+			// Verify that 100% use is capped at 99.
+			stats:     usage.CPUStats{UserTime: 1e9, SysTime: 1e9},
+			startTime: ktime.FromNanoseconds(7e9),
+			now:       ktime.FromNanoseconds(9e9),
+			expected:  99,
+		},
+		{
+			// Verify that if usage > lifetime, we get at most 99%
+			// usage.
+			stats:     usage.CPUStats{UserTime: 2e9, SysTime: 2e9},
+			startTime: ktime.FromNanoseconds(7e9),
+			now:       ktime.FromNanoseconds(9e9),
+			expected:  99,
+		},
+		{
+			// Verify that 50% usage is reported correctly.
+			stats:     usage.CPUStats{UserTime: 1e9, SysTime: 1e9},
+			startTime: ktime.FromNanoseconds(12e9),
+			now:       ktime.FromNanoseconds(16e9),
+			expected:  50,
+		},
+		{
+			// Verify that 0% usage is reported correctly.
+			stats:     usage.CPUStats{UserTime: 0, SysTime: 0},
+			startTime: ktime.FromNanoseconds(12e9),
+			now:       ktime.FromNanoseconds(14e9),
+			expected:  0,
+		},
+	}
+
+	for _, tc := range testCases {
+		if pcpu := percentCPU(tc.stats, tc.startTime, tc.now); pcpu != tc.expected {
+			t.Errorf("percentCPU(%v, %v, %v): got %d, want %d", tc.stats, tc.startTime, tc.now, pcpu, tc.expected)
+		}
+	}
+}
-- 
cgit v1.2.3