From 8a24f200e9b134f0965d0f1cf4e051146742747e Mon Sep 17 00:00:00 2001 From: Fabricio Voznika Date: Mon, 18 Apr 2022 18:59:51 -0700 Subject: [PATCH] Use proto structs for seccheck points Given that in most cases points are serialized to another process, point data is now created diretly into protos. As part of this change, infrastructure to track optional and context fields was created to facilitate addition of lots of Points which is needed for upcomming of changes. Updates #4805 Currently the SST code is converting seccheck protos into SST protos in the sentry before sending it to the API. After this change, SST checker will be changed to send seccheck protos to the API and the API then converts these into SST on the way to pubsub. PiperOrigin-RevId: 442688320 --- pkg/sentry/kernel/BUILD | 2 + pkg/sentry/kernel/auth/BUILD | 2 + pkg/sentry/kernel/auth/credentials.go | 16 ++ pkg/sentry/kernel/seccheck.go | 67 ++++++++ pkg/sentry/kernel/task.go | 21 --- pkg/sentry/kernel/task_clone.go | 32 ++-- pkg/sentry/kernel/task_exec.go | 52 +++--- pkg/sentry/kernel/task_exit.go | 22 +-- pkg/sentry/seccheck/BUILD | 22 +-- pkg/sentry/seccheck/clone.go | 29 +--- pkg/sentry/seccheck/execve.go | 51 +----- pkg/sentry/seccheck/exit.go | 28 +--- pkg/sentry/seccheck/points/BUILD | 13 ++ pkg/sentry/seccheck/points/common.proto | 47 ++++++ pkg/sentry/seccheck/points/sentry.proto | 67 ++++++++ pkg/sentry/seccheck/seccheck.go | 133 +++++++++++----- pkg/sentry/seccheck/seccheck_test.go | 201 +++++++++++++++++------- pkg/sentry/seccheck/task.go | 39 ----- 18 files changed, 528 insertions(+), 316 deletions(-) create mode 100644 pkg/sentry/kernel/seccheck.go create mode 100644 pkg/sentry/seccheck/points/BUILD create mode 100644 pkg/sentry/seccheck/points/common.proto create mode 100644 pkg/sentry/seccheck/points/sentry.proto delete mode 100644 pkg/sentry/seccheck/task.go diff --git a/pkg/sentry/kernel/BUILD b/pkg/sentry/kernel/BUILD index e313d69fa..0d3093688 100644 --- a/pkg/sentry/kernel/BUILD +++ b/pkg/sentry/kernel/BUILD @@ -165,6 +165,7 @@ go_library( "ptrace_amd64.go", "ptrace_arm64.go", "rseq.go", + "seccheck.go", "seccomp.go", "seqatomic_taskgoroutineschedinfo_unsafe.go", "session_list.go", @@ -272,6 +273,7 @@ go_library( "//pkg/sentry/pgalloc", "//pkg/sentry/platform", "//pkg/sentry/seccheck", + "//pkg/sentry/seccheck/points:points_go_proto", "//pkg/sentry/socket/netlink/port", "//pkg/sentry/socket/unix/transport", "//pkg/sentry/time", diff --git a/pkg/sentry/kernel/auth/BUILD b/pkg/sentry/kernel/auth/BUILD index 9aa03f506..73c906136 100644 --- a/pkg/sentry/kernel/auth/BUILD +++ b/pkg/sentry/kernel/auth/BUILD @@ -65,6 +65,8 @@ go_library( "//pkg/context", "//pkg/errors/linuxerr", "//pkg/log", + "//pkg/sentry/seccheck", + "//pkg/sentry/seccheck/points:points_go_proto", "//pkg/sync", ], ) diff --git a/pkg/sentry/kernel/auth/credentials.go b/pkg/sentry/kernel/auth/credentials.go index fc245c54b..25ffb98ac 100644 --- a/pkg/sentry/kernel/auth/credentials.go +++ b/pkg/sentry/kernel/auth/credentials.go @@ -17,6 +17,8 @@ package auth import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/errors/linuxerr" + "gvisor.dev/gvisor/pkg/sentry/seccheck" + pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" ) // Credentials contains information required to authorize privileged operations @@ -260,3 +262,17 @@ func (c *Credentials) SetGID(gid GID) error { c.SavedKGID = kgid return nil } + +// LoadSeccheckData sets credential data based on mask. +func (c *Credentials) LoadSeccheckData(mask seccheck.FieldMask, info *pb.ContextData) { + if mask.Contains(seccheck.FieldCtxtCredentials) { + info.Credentials = &pb.Credentials{ + RealUid: uint32(c.RealKUID), + EffectiveUid: uint32(c.EffectiveKUID), + SavedUid: uint32(c.SavedKUID), + RealGid: uint32(c.RealKGID), + EffectiveGid: uint32(c.EffectiveKGID), + SavedGid: uint32(c.SavedKGID), + } + } +} diff --git a/pkg/sentry/kernel/seccheck.go b/pkg/sentry/kernel/seccheck.go new file mode 100644 index 000000000..91e17697b --- /dev/null +++ b/pkg/sentry/kernel/seccheck.go @@ -0,0 +1,67 @@ +// Copyright 2022 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 ( + "gvisor.dev/gvisor/pkg/sentry/seccheck" + pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" +) + +// LoadSeccheckData sets info from the task based on mask. +func LoadSeccheckData(t *Task, mask seccheck.FieldMask, info *pb.ContextData) { + t.k.tasks.mu.RLock() + defer t.k.tasks.mu.RUnlock() + LoadSeccheckDataLocked(t, mask, info) +} + +// LoadSeccheckDataLocked sets info from the task based on mask. +// +// Preconditions: The TaskSet mutex must be locked. +func LoadSeccheckDataLocked(t *Task, mask seccheck.FieldMask, info *pb.ContextData) { + if mask.Contains(seccheck.FieldCtxtTime) { + info.TimeNs = t.k.RealtimeClock().Now().Nanoseconds() + } + + if t == nil { + return + } + if mask.Contains(seccheck.FieldCtxtThreadID) { + info.ThreadId = int32(t.k.tasks.Root.tids[t]) + } + if mask.Contains(seccheck.FieldCtxtThreadStartTime) { + info.ThreadStartTimeNs = t.startTime.Nanoseconds() + } + if mask.Contains(seccheck.FieldCtxtThreadGroupID) { + info.ThreadGroupId = int32(t.k.tasks.Root.tgids[t.tg]) + } + if mask.Contains(seccheck.FieldCtxtThreadGroupStartTime) { + info.ThreadGroupStartTimeNs = t.tg.leader.startTime.Nanoseconds() + } + if mask.Contains(seccheck.FieldCtxtContainerID) { + info.ContainerId = t.tg.leader.ContainerID() + } + if mask.Contains(seccheck.FieldCtxtCwd) { + root := t.FSContext().RootDirectoryVFS2() + defer root.DecRef(t) + wd := t.FSContext().WorkingDirectoryVFS2() + defer wd.DecRef(t) + vfsObj := root.Mount().Filesystem().VirtualFilesystem() + info.Cwd, _ = vfsObj.PathnameWithDeleted(t, root, wd) + } + if mask.Contains(seccheck.FieldCtxtProcessName) { + info.ProcessName = t.Name() + } + t.Credentials().LoadSeccheckData(mask, info) +} diff --git a/pkg/sentry/kernel/task.go b/pkg/sentry/kernel/task.go index df682548b..fcaf802b2 100644 --- a/pkg/sentry/kernel/task.go +++ b/pkg/sentry/kernel/task.go @@ -31,7 +31,6 @@ import ( "gvisor.dev/gvisor/pkg/sentry/kernel/sched" ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" "gvisor.dev/gvisor/pkg/sentry/platform" - "gvisor.dev/gvisor/pkg/sentry/seccheck" "gvisor.dev/gvisor/pkg/sentry/usage" "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/sync" @@ -880,23 +879,3 @@ func (t *Task) ResetKcov() { t.kcov = nil } } - -// Preconditions: The TaskSet mutex must be locked. -func (t *Task) loadSeccheckInfoLocked(req seccheck.TaskFieldSet, mask *seccheck.TaskFieldSet, info *seccheck.TaskInfo) { - if req.Contains(seccheck.TaskFieldThreadID) { - info.ThreadID = int32(t.k.tasks.Root.tids[t]) - mask.Add(seccheck.TaskFieldThreadID) - } - if req.Contains(seccheck.TaskFieldThreadStartTime) { - info.ThreadStartTime = t.startTime - mask.Add(seccheck.TaskFieldThreadStartTime) - } - if req.Contains(seccheck.TaskFieldThreadGroupID) { - info.ThreadGroupID = int32(t.k.tasks.Root.tgids[t.tg]) - mask.Add(seccheck.TaskFieldThreadGroupID) - } - if req.Contains(seccheck.TaskFieldThreadGroupStartTime) { - info.ThreadGroupStartTime = t.tg.leader.startTime - mask.Add(seccheck.TaskFieldThreadGroupStartTime) - } -} diff --git a/pkg/sentry/kernel/task_clone.go b/pkg/sentry/kernel/task_clone.go index a84b88661..c824d6269 100644 --- a/pkg/sentry/kernel/task_clone.go +++ b/pkg/sentry/kernel/task_clone.go @@ -24,6 +24,7 @@ import ( "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/sentry/inet" "gvisor.dev/gvisor/pkg/sentry/seccheck" + pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" "gvisor.dev/gvisor/pkg/usermem" ) @@ -247,8 +248,8 @@ func (t *Task) Clone(args *linux.CloneArgs) (ThreadID, *SyscallControl, error) { defer nt.Start(tid) if seccheck.Global.Enabled(seccheck.PointClone) { - mask, info := getCloneSeccheckInfo(t, nt, args) - if err := seccheck.Global.Clone(t, mask, &info); err != nil { + mask, info := getCloneSeccheckInfo(t, nt) + if err := seccheck.Global.Clone(t, mask, info); err != nil { // nt has been visible to the rest of the system since NewTask, so // it may be blocking execve or a group stop, have been notified // for group signal delivery, had children reparented to it, etc. @@ -306,20 +307,23 @@ func (t *Task) Clone(args *linux.CloneArgs) (ThreadID, *SyscallControl, error) { return ntid, nil, nil } -func getCloneSeccheckInfo(t, nt *Task, args *linux.CloneArgs) (seccheck.CloneFieldSet, seccheck.CloneInfo) { - req := seccheck.Global.CloneReq() - info := seccheck.CloneInfo{ - Credentials: t.Credentials(), - Args: *args, - } - var mask seccheck.CloneFieldSet - mask.Add(seccheck.CloneFieldCredentials) - mask.Add(seccheck.CloneFieldArgs) +func getCloneSeccheckInfo(t, nt *Task) (seccheck.FieldSet, *pb.CloneInfo) { + fields := seccheck.Global.GetFieldSet(seccheck.PointClone) + t.k.tasks.mu.RLock() defer t.k.tasks.mu.RUnlock() - t.loadSeccheckInfoLocked(req.Invoker, &mask.Invoker, &info.Invoker) - nt.loadSeccheckInfoLocked(req.Created, &mask.Created, &info.Created) - return mask, info + info := &pb.CloneInfo{ + CreatedThreadId: int32(nt.k.tasks.Root.tids[nt]), + CreatedThreadGroupId: int32(nt.k.tasks.Root.tgids[nt.tg]), + CreatedThreadStartTimeNs: nt.startTime.Nanoseconds(), + } + + if !fields.Context.Empty() { + info.ContextData = &pb.ContextData{} + LoadSeccheckDataLocked(t, fields.Context, info.ContextData) + } + + return fields, info } // maybeBeginVforkStop checks if a previously-started vfork child is still diff --git a/pkg/sentry/kernel/task_exec.go b/pkg/sentry/kernel/task_exec.go index cadc3b848..0d74c2f21 100644 --- a/pkg/sentry/kernel/task_exec.go +++ b/pkg/sentry/kernel/task_exec.go @@ -69,9 +69,9 @@ import ( "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/fsbridge" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/mm" "gvisor.dev/gvisor/pkg/sentry/seccheck" + pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" "gvisor.dev/gvisor/pkg/sentry/vfs" ) @@ -97,7 +97,7 @@ func (t *Task) Execve(newImage *TaskImage, argv, env []string, executable fsbrid // We can't clearly hold kernel package locks while stat'ing executable. if seccheck.Global.Enabled(seccheck.PointExecve) { mask, info := getExecveSeccheckInfo(t, argv, env, executable, pathname) - if err := seccheck.Global.Execve(t, mask, &info); err != nil { + if err := seccheck.Global.Execve(t, mask, info); err != nil { newImage.release() return nil, err } @@ -302,44 +302,28 @@ func (t *Task) promoteLocked() { oldLeader.exitNotifyLocked(false) } -func getExecveSeccheckInfo(t *Task, argv, env []string, executable fsbridge.File, pathname string) (seccheck.ExecveFieldSet, seccheck.ExecveInfo) { - req := seccheck.Global.ExecveReq() - info := seccheck.ExecveInfo{ - Credentials: t.Credentials(), - Argv: argv, - Env: env, +func getExecveSeccheckInfo(t *Task, argv, env []string, executable fsbridge.File, pathname string) (seccheck.FieldSet, *pb.ExecveInfo) { + fields := seccheck.Global.GetFieldSet(seccheck.PointExecve) + info := &pb.ExecveInfo{ + Argv: argv, + Env: env, } - var mask seccheck.ExecveFieldSet - mask.Add(seccheck.ExecveFieldCredentials) - mask.Add(seccheck.ExecveFieldArgv) - mask.Add(seccheck.ExecveFieldEnv) if executable != nil { info.BinaryPath = pathname - mask.Add(seccheck.ExecveFieldBinaryPath) if vfs2bridgeFile, ok := executable.(*fsbridge.VFSFile); ok { - if req.Contains(seccheck.ExecveFieldBinaryMode) || req.Contains(seccheck.ExecveFieldBinaryUID) || req.Contains(seccheck.ExecveFieldBinaryGID) { - var statOpts vfs.StatOptions - if req.Contains(seccheck.ExecveFieldBinaryMode) { - statOpts.Mask |= linux.STATX_TYPE | linux.STATX_MODE - } - if req.Contains(seccheck.ExecveFieldBinaryUID) { - statOpts.Mask |= linux.STATX_UID - } - if req.Contains(seccheck.ExecveFieldBinaryGID) { - statOpts.Mask |= linux.STATX_GID + if fields.Local.Contains(seccheck.ExecveFieldBinaryInfo) { + statOpts := vfs.StatOptions{ + Mask: linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_UID | linux.STATX_GID, } if stat, err := vfs2bridgeFile.FileDescription().Stat(t, statOpts); err == nil { if stat.Mask&(linux.STATX_TYPE|linux.STATX_MODE) == (linux.STATX_TYPE | linux.STATX_MODE) { - info.BinaryMode = stat.Mode - mask.Add(seccheck.ExecveFieldBinaryMode) + info.BinaryMode = uint32(stat.Mode) } if stat.Mask&linux.STATX_UID != 0 { - info.BinaryUID = auth.KUID(stat.UID) - mask.Add(seccheck.ExecveFieldBinaryUID) + info.BinaryUid = stat.UID } if stat.Mask&linux.STATX_GID != 0 { - info.BinaryGID = auth.KGID(stat.GID) - mask.Add(seccheck.ExecveFieldBinaryGID) + info.BinaryGid = stat.GID } } } @@ -347,8 +331,10 @@ func getExecveSeccheckInfo(t *Task, argv, env []string, executable fsbridge.File // SHA256, which is very expensive. } } - t.k.tasks.mu.RLock() - defer t.k.tasks.mu.RUnlock() - t.loadSeccheckInfoLocked(req.Invoker, &mask.Invoker, &info.Invoker) - return mask, info + + if !fields.Context.Empty() { + info.ContextData = &pb.ContextData{} + LoadSeccheckData(t, fields.Context, info.ContextData) + } + return fields, info } diff --git a/pkg/sentry/kernel/task_exit.go b/pkg/sentry/kernel/task_exit.go index a74638711..c827914b5 100644 --- a/pkg/sentry/kernel/task_exit.go +++ b/pkg/sentry/kernel/task_exit.go @@ -33,6 +33,7 @@ import ( "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/seccheck" + pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" "gvisor.dev/gvisor/pkg/waiter" ) @@ -646,7 +647,7 @@ func (t *Task) exitNotifyLocked(fromPtraceDetach bool) { } if seccheck.Global.Enabled(seccheck.PointExitNotifyParent) { mask, info := getExitNotifyParentSeccheckInfo(t) - seccheck.Global.ExitNotifyParent(t, mask, &info) + seccheck.Global.ExitNotifyParent(t, mask, info) } } } @@ -697,15 +698,18 @@ func (t *Task) exitNotificationSignal(sig linux.Signal, receiver *Task) *linux.S } // Preconditions: The TaskSet mutex must be locked. -func getExitNotifyParentSeccheckInfo(t *Task) (seccheck.ExitNotifyParentFieldSet, seccheck.ExitNotifyParentInfo) { - req := seccheck.Global.ExitNotifyParentReq() - info := seccheck.ExitNotifyParentInfo{ - ExitStatus: t.tg.exitStatus, +func getExitNotifyParentSeccheckInfo(t *Task) (seccheck.FieldSet, *pb.ExitNotifyParentInfo) { + fields := seccheck.Global.GetFieldSet(seccheck.PointExitNotifyParent) + + info := &pb.ExitNotifyParentInfo{ + ExitStatus: int32(t.tg.exitStatus), } - var mask seccheck.ExitNotifyParentFieldSet - mask.Add(seccheck.ExitNotifyParentFieldExitStatus) - t.loadSeccheckInfoLocked(req.Exiter, &mask.Exiter, &info.Exiter) - return mask, info + if !fields.Context.Empty() { + info.ContextData = &pb.ContextData{} + LoadSeccheckDataLocked(t, fields.Context, info.ContextData) + } + + return fields, info } // ExitStatus returns t's exit status, which is only guaranteed to be diff --git a/pkg/sentry/seccheck/BUILD b/pkg/sentry/seccheck/BUILD index 35feb969f..f301bfdf1 100644 --- a/pkg/sentry/seccheck/BUILD +++ b/pkg/sentry/seccheck/BUILD @@ -1,21 +1,8 @@ load("//tools:defs.bzl", "go_library", "go_test") -load("//tools/go_fieldenum:defs.bzl", "go_fieldenum") load("//tools/go_generics:defs.bzl", "go_template_instance") licenses(["notice"]) -go_fieldenum( - name = "seccheck_fieldenum", - srcs = [ - "clone.go", - "execve.go", - "exit.go", - "task.go", - ], - out = "seccheck_fieldenum.go", - package = "seccheck", -) - go_template_instance( name = "seqatomic_checkerslice", out = "seqatomic_checkerslice_unsafe.go", @@ -34,17 +21,15 @@ go_library( "execve.go", "exit.go", "seccheck.go", - "seccheck_fieldenum.go", "seqatomic_checkerslice_unsafe.go", - "task.go", ], visibility = ["//:sandbox"], deps = [ "//pkg/abi/linux", "//pkg/context", "//pkg/gohacks", - "//pkg/sentry/kernel/auth", "//pkg/sentry/kernel/time", + "//pkg/sentry/seccheck/points:points_go_proto", "//pkg/sync", ], ) @@ -54,5 +39,8 @@ go_test( size = "small", srcs = ["seccheck_test.go"], library = ":seccheck", - deps = ["//pkg/context"], + deps = [ + "//pkg/context", + "//pkg/sentry/seccheck/points:points_go_proto", + ], ) diff --git a/pkg/sentry/seccheck/clone.go b/pkg/sentry/seccheck/clone.go index d635d8e59..9613e2c6f 100644 --- a/pkg/sentry/seccheck/clone.go +++ b/pkg/sentry/seccheck/clone.go @@ -15,37 +15,14 @@ package seccheck import ( - "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" + pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" ) -// CloneInfo contains information used by the Clone checkpoint. -// -// +fieldenum Clone -type CloneInfo struct { - // Invoker identifies the invoking thread. - Invoker TaskInfo - - // Credentials are the invoking thread's credentials. - Credentials *auth.Credentials - - // Args contains the arguments to kernel.Task.Clone(). - Args linux.CloneArgs - - // Created identifies the created thread. - Created TaskInfo -} - -// CloneReq returns fields required by the Clone checkpoint. -func (s *State) CloneReq() CloneFieldSet { - return s.cloneReq.Load() -} - // Clone is called at the Clone checkpoint. -func (s *State) Clone(ctx context.Context, mask CloneFieldSet, info *CloneInfo) error { +func (s *State) Clone(ctx context.Context, fields FieldSet, info *pb.CloneInfo) error { for _, c := range s.getCheckers() { - if err := c.Clone(ctx, mask, *info); err != nil { + if err := c.Clone(ctx, fields, info); err != nil { return err } } diff --git a/pkg/sentry/seccheck/execve.go b/pkg/sentry/seccheck/execve.go index ec9e0370b..83a40db59 100644 --- a/pkg/sentry/seccheck/execve.go +++ b/pkg/sentry/seccheck/execve.go @@ -16,54 +16,19 @@ package seccheck import ( "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" + pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" ) -// ExecveInfo contains information used by the Execve checkpoint. -// -// +fieldenum Execve -type ExecveInfo struct { - // Invoker identifies the invoking thread. - Invoker TaskInfo - - // Credentials are the invoking thread's credentials. - Credentials *auth.Credentials - - // BinaryPath is a path to the executable binary file being switched to in - // the mount namespace in which it was opened. - BinaryPath string - - // Argv is the new process image's argument vector. - Argv []string - - // Env is the new process image's environment variables. - Env []string - - // BinaryMode is the executable binary file's mode. - BinaryMode uint16 - - // BinaryUID is the executable binary file's owner. - BinaryUID auth.KUID - - // BinaryGID is the executable binary file's group. - BinaryGID auth.KGID - - // BinarySHA256 is the SHA-256 hash of the executable binary file. - // - // Note that this requires reading the entire file into memory, which is - // likely to be extremely slow. - BinarySHA256 [32]byte -} - -// ExecveReq returns fields required by the Execve checkpoint. -func (s *State) ExecveReq() ExecveFieldSet { - return s.execveReq.Load() -} +const ( + // ExecveFieldBinaryInfo is an optional field to collect information about the + // binary being executed. + ExecveFieldBinaryInfo Field = iota +) // Execve is called at the Execve checkpoint. -func (s *State) Execve(ctx context.Context, mask ExecveFieldSet, info *ExecveInfo) error { +func (s *State) Execve(ctx context.Context, mask FieldSet, info *pb.ExecveInfo) error { for _, c := range s.getCheckers() { - if err := c.Execve(ctx, mask, *info); err != nil { + if err := c.Execve(ctx, mask, info); err != nil { return err } } diff --git a/pkg/sentry/seccheck/exit.go b/pkg/sentry/seccheck/exit.go index 15bc5879b..7f28ce2ce 100644 --- a/pkg/sentry/seccheck/exit.go +++ b/pkg/sentry/seccheck/exit.go @@ -15,41 +15,19 @@ package seccheck import ( - "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/context" + pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" ) -// ExitNotifyParentInfo contains information used by the ExitNotifyParent -// checkpoint. -// -// +fieldenum ExitNotifyParent -type ExitNotifyParentInfo struct { - // Exiter identifies the exiting thread. Note that by the checkpoint's - // definition, Exiter.ThreadID == Exiter.ThreadGroupID and - // Exiter.ThreadStartTime == Exiter.ThreadGroupStartTime, so requesting - // ThreadGroup* fields is redundant. - Exiter TaskInfo - - // ExitStatus is the exiting thread group's exit status, as reported - // by wait*(). - ExitStatus linux.WaitStatus -} - -// ExitNotifyParentReq returns fields required by the ExitNotifyParent -// checkpoint. -func (s *State) ExitNotifyParentReq() ExitNotifyParentFieldSet { - return s.exitNotifyParentReq.Load() -} - // ExitNotifyParent is called at the ExitNotifyParent checkpoint. // // The ExitNotifyParent checkpoint occurs when a zombied thread group leader, // not waiting for exit acknowledgement from a non-parent ptracer, becomes the // last non-dead thread in its thread group and notifies its parent of its // exiting. -func (s *State) ExitNotifyParent(ctx context.Context, mask ExitNotifyParentFieldSet, info *ExitNotifyParentInfo) error { +func (s *State) ExitNotifyParent(ctx context.Context, fields FieldSet, info *pb.ExitNotifyParentInfo) error { for _, c := range s.getCheckers() { - if err := c.ExitNotifyParent(ctx, mask, *info); err != nil { + if err := c.ExitNotifyParent(ctx, fields, info); err != nil { return err } } diff --git a/pkg/sentry/seccheck/points/BUILD b/pkg/sentry/seccheck/points/BUILD new file mode 100644 index 000000000..0142b4b73 --- /dev/null +++ b/pkg/sentry/seccheck/points/BUILD @@ -0,0 +1,13 @@ +load("//tools:defs.bzl", "proto_library") + +licenses(["notice"]) + +package(default_visibility = ["//:sandbox"]) + +proto_library( + name = "points", + srcs = [ + "common.proto", + "sentry.proto", + ], +) diff --git a/pkg/sentry/seccheck/points/common.proto b/pkg/sentry/seccheck/points/common.proto new file mode 100644 index 000000000..b087f9619 --- /dev/null +++ b/pkg/sentry/seccheck/points/common.proto @@ -0,0 +1,47 @@ +// Copyright 2022 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. + +syntax = "proto3"; + +package gvisor.common; + +message Credentials { + uint32 real_uid = 1; + uint32 effective_uid = 2; + uint32 saved_uid = 3; + + uint32 real_gid = 4; + uint32 effective_gid = 5; + uint32 saved_gid = 6; +} + +message ContextData { + int64 time_ns = 1; + + int32 thread_id = 2; + + int64 thread_start_time_ns = 3; + + int32 thread_group_id = 4; + + int64 thread_group_start_time_ns = 5; + + string container_id = 6; + + Credentials credentials = 7; + + string cwd = 8; + + string process_name = 9; +} diff --git a/pkg/sentry/seccheck/points/sentry.proto b/pkg/sentry/seccheck/points/sentry.proto new file mode 100644 index 000000000..8a4f21056 --- /dev/null +++ b/pkg/sentry/seccheck/points/sentry.proto @@ -0,0 +1,67 @@ +// Copyright 2022 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. + +syntax = "proto3"; + +package gvisor.sentry; + +import "pkg/sentry/seccheck/points/common.proto"; + +// CloneInfo contains information used by the Clone checkpoint. +message CloneInfo { + gvisor.common.ContextData context_data = 1; + + // CreatedThreadID is the thread's ID in the root PID namespace. + int32 created_thread_id = 3; + + int32 created_thread_group_id = 4; + + // CreatedThreadStartTime is the thread's CLOCK_REALTIME start time. + int64 created_thread_start_time_ns = 5; +} + +// ExecveInfo contains information used by the Execve checkpoint. +message ExecveInfo { + gvisor.common.ContextData context_data = 1; + + // BinaryPath is a path to the executable binary file being switched to in + // the mount namespace in which it was opened. + string binary_path = 2; + + // Argv is the new process image's argument vector. + repeated string argv = 3; + + // Env is the new process image's environment variables. + repeated string env = 4; + + // BinaryMode is the executable binary file's mode. + uint32 binary_mode = 5; + + uint32 binary_uid = 6; + uint32 binary_gid = 7; + + // binary_sha256 is the SHA-256 hash of the executable binary file. + // + // Note that this requires reading the entire file into memory, which is + // likely to be extremely slow. + bytes binary_sha256 = 8; +} + +message ExitNotifyParentInfo { + gvisor.common.ContextData context_data = 1; + + // ExitStatus is the exiting thread group's exit status, as reported + // by wait*(). + int32 exit_status = 2; +} diff --git a/pkg/sentry/seccheck/seccheck.go b/pkg/sentry/seccheck/seccheck.go index 3156c103d..b64508ff5 100644 --- a/pkg/sentry/seccheck/seccheck.go +++ b/pkg/sentry/seccheck/seccheck.go @@ -20,6 +20,7 @@ import ( "sync/atomic" "gvisor.dev/gvisor/pkg/context" + pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto" "gvisor.dev/gvisor/pkg/sync" ) @@ -37,6 +38,71 @@ const ( numPointBitmaskUint32s = (int(pointLength)-1)/32 + 1 ) +// FieldCtxtX represents a data field that comes from the Context. +const ( + FieldCtxtTime Field = iota + FieldCtxtThreadID + FieldCtxtThreadStartTime + FieldCtxtThreadGroupID + FieldCtxtThreadGroupStartTime + FieldCtxtContainerID + FieldCtxtCredentials + FieldCtxtCwd + FieldCtxtProcessName +) + +// FieldSet contains all optional fields to be collected by a given Point. +type FieldSet struct { + // Local indicates which optional fields from the Point that needs to be + // collected, e.g. resolving path from an FD, or collecting a large field. + Local FieldMask + + // Context indicates which optional fields from the Context that needs to be + // collected, e.g. PID, credentials, current time. + Context FieldMask +} + +// Field represents the index of a single optional field to be collect for a +// Point. +type Field uint + +// FieldMask is a bitmask with a single bit representing an optional field to be +// collected. The meaning of each bit varies per point. The mask is currently +// limited to 64 fields. If more are needed, FieldMask can be expanded to +// support additional fields. +type FieldMask struct { + mask uint64 +} + +// MakeFieldMask creates a FieldMask from a set of Fields. +func MakeFieldMask(fields ...Field) FieldMask { + var m FieldMask + for _, field := range fields { + m.Add(field) + } + return m +} + +// Contains returns true if the mask contains the Field. +func (fm *FieldMask) Contains(field Field) bool { + return fm.mask&(1<